[KERNEL32][CONSRV] Retrieve the best-suited language ID corresponding to the active...
authorHermès Bélusca-Maïto <hermes.belusca-maito@reactos.org>
Fri, 14 Jan 2022 21:00:55 +0000 (22:00 +0100)
committerHermès Bélusca-Maïto <hermes.belusca-maito@reactos.org>
Tue, 8 Feb 2022 14:58:02 +0000 (15:58 +0100)
CORE-17601, CORE-17803
Replaces PR #4281.

Implement SrvGetConsoleLangId() (server-side) and set the new current
thread's locale after connecting to a console, or changing its output
code page.

Based on API tracing on Windows 2003, as well as on comments and code
gathered from: https://github.com/microsoft/terminal

Tests results are listed in PR #4301.

dll/win32/kernel32/client/console/console.c
dll/win32/kernel32/client/console/init.c
dll/win32/kernel32/include/console.h
sdk/include/reactos/subsys/win/conmsg.h
win32ss/user/winsrv/concfg/font.h
win32ss/user/winsrv/consrv/console.c

index 5c687df..7a5b3b6 100644 (file)
@@ -1375,8 +1375,6 @@ AllocConsole(VOID)
     ULONG AppNameLength = 128 * sizeof(WCHAR);
     ULONG CurDirLength  = (MAX_PATH + 1) * sizeof(WCHAR);
 
-    LCID lcid;
-
     RtlEnterCriticalSection(&ConsoleLock);
 
     if (NtCurrentPeb()->ProcessParameters->ConsoleHandle)
@@ -1427,8 +1425,8 @@ AllocConsole(VOID)
         /* Initialize Console Ctrl Handling */
         InitializeCtrlHandling();
 
-        /* Sets the current console locale for this thread */
-        SetTEBLangID(lcid);
+        /* Sync the current thread's LangId with the console's one */
+        SetTEBLangID();
     }
 
 Quit:
@@ -2500,6 +2498,9 @@ SetConsoleOutputCP(UINT wCodePageID)
         return FALSE;
     }
 
+    /* Sync the current thread's LangId with the console's one */
+    SetTEBLangID();
+
     return TRUE;
 }
 
@@ -2676,9 +2677,7 @@ AttachConsole(DWORD dwProcessId)
 {
     BOOL Success;
     CONSOLE_START_INFO ConsoleStartInfo;
-
     DWORD dummy;
-    LCID lcid;
 
     RtlEnterCriticalSection(&ConsoleLock);
 
@@ -2711,8 +2710,8 @@ AttachConsole(DWORD dwProcessId)
         /* Initialize Console Ctrl Handling */
         InitializeCtrlHandling();
 
-        /* Sets the current console locale for this thread */
-        SetTEBLangID(lcid);
+        /* Sync the current thread's LangId with the console's one */
+        SetTEBLangID();
     }
 
 Quit:
@@ -3067,6 +3066,48 @@ UnregisterConsoleIME(VOID)
     return FALSE;
 }
 
+/**
+ * @brief
+ * Internal helper function used to synchronize the current
+ * thread's language ID with the one from the console.
+ **/
+VOID
+SetTEBLangID(VOID)
+{
+    CONSOLE_API_MESSAGE ApiMessage;
+    PCONSOLE_GETLANGID LangIdRequest = &ApiMessage.Data.LangIdRequest;
+
+    /* Retrieve the "best-suited" language ID corresponding
+     * to the active console output code page. */
+    LangIdRequest->ConsoleHandle = NtCurrentPeb()->ProcessParameters->ConsoleHandle;
+
+    CsrClientCallServer((PCSR_API_MESSAGE)&ApiMessage,
+                        NULL,
+                        CSR_CREATE_API_NUMBER(CONSRV_SERVERDLL_INDEX, ConsolepGetLangId),
+                        sizeof(*LangIdRequest));
+    if (!NT_SUCCESS(ApiMessage.Status))
+    {
+        /*
+         * No best console language ID: keep the current thread's one.
+         * Since this internal function only modifies an optional setting,
+         * don't set any last error, as it could otherwise mess with the
+         * main last error set by the caller.
+         */
+        return;
+    }
+
+    /*
+     * We succeeded, set the current thread's language ID by
+     * modifying its locale -- Windows <= 2003 does not have
+     * the concept of a separate thread UI language.
+     * Ignore the returned value.
+     */
+    if (!SetThreadLocale(MAKELCID(LangIdRequest->LangId, SORT_DEFAULT)))
+    {
+        DPRINT1("SetTEBLangID: Could not set thread locale to console lang ID %lu\n",
+                LangIdRequest->LangId);
+    }
+}
 
 static
 BOOL
index 3186bf4..eb97ac7 100644 (file)
@@ -342,14 +342,13 @@ ConDllInitialize(IN ULONG Reason,
     PRTL_USER_PROCESS_PARAMETERS Parameters = NtCurrentPeb()->ProcessParameters;
     BOOLEAN InServerProcess = FALSE;
     CONSRV_API_CONNECTINFO ConnectInfo;
-    LCID lcid;
 
     if (Reason != DLL_PROCESS_ATTACH)
     {
         if ((Reason == DLL_THREAD_ATTACH) && IsConsoleApp())
         {
-            /* Sets the current console locale for the new thread */
-            SetTEBLangID(lcid);
+            /* Sync the new thread's LangId with the console's one */
+            SetTEBLangID();
         }
         else if (Reason == DLL_PROCESS_DETACH)
         {
@@ -522,8 +521,8 @@ ConDllInitialize(IN ULONG Reason,
 
         InputWaitHandle = ConnectInfo.ConsoleStartInfo.InputWaitHandle;
 
-        /* Sets the current console locale for this thread */
-        SetTEBLangID(lcid);
+        /* Sync the current thread's LangId with the console's one */
+        SetTEBLangID();
     }
 
     DPRINT("Console setup: 0x%p, 0x%p, 0x%p, 0x%p\n",
index b6462af..5fe84cb 100644 (file)
@@ -60,7 +60,8 @@ GetConsoleInputWaitHandle(VOID);
 HANDLE
 TranslateStdHandle(HANDLE hHandle);
 
-#define SetTEBLangID(p) (p)
+VOID
+SetTEBLangID(VOID);
 
 VOID
 SetUpConsoleInfo(IN BOOLEAN CaptureTitle,
index b96441d..dd50cb1 100644 (file)
@@ -859,6 +859,12 @@ typedef struct _CONSOLE_SETINPUTOUTPUTCP
     HANDLE EventHandle;
 } CONSOLE_SETINPUTOUTPUTCP, *PCONSOLE_SETINPUTOUTPUTCP;
 
+typedef struct _CONSOLE_GETLANGID
+{
+    HANDLE ConsoleHandle;
+    LANGID LangId;
+} CONSOLE_GETLANGID, *PCONSOLE_GETLANGID;
+
 typedef struct _CONSOLE_GETKBDLAYOUTNAME
 {
     HANDLE ConsoleHandle;
@@ -991,6 +997,7 @@ typedef struct _CONSOLE_API_MESSAGE
         /* Input and Output Code Pages; keyboard */
         CONSOLE_GETINPUTOUTPUTCP GetConsoleCPRequest;
         CONSOLE_SETINPUTOUTPUTCP SetConsoleCPRequest;
+        CONSOLE_GETLANGID LangIdRequest;
         CONSOLE_GETKBDLAYOUTNAME GetKbdLayoutNameRequest;
 
         /* Virtual DOS Machine */
index 15224b3..870b380 100644 (file)
 #define CP_GB2312   936  // Chinese Simplified (GB2312)
 #define CP_BIG5     950  // Chinese Traditional (Big5)
 
+/*
+ * "Human-understandable" names for the previous standard code pages.
+ * Taken from https://github.com/microsoft/terminal/blob/main/src/inc/unicode.hpp
+ */
+#define CP_JAPANESE             CP_SHIFTJIS
+#define CP_KOREAN               CP_HANGUL
+#define CP_CHINESE_SIMPLIFIED   CP_GB2312
+#define CP_CHINESE_TRADITIONAL  CP_BIG5
+
 /* IsFarEastCP(CodePage) */
 #define IsCJKCodePage(CodePage) \
     ((CodePage) == CP_SHIFTJIS || (CodePage) == CP_HANGUL || \
index 1e35fde..cc26960 100644 (file)
@@ -16,7 +16,7 @@
 #define COBJMACROS
 #include <shlobj.h>
 
-
+#include "../concfg/font.h"
 #include <alias.h>
 #include <history.h>
 #include "procinit.h"
@@ -2014,10 +2014,66 @@ CSR_API(SrvSetConsoleNlsMode)
 }
 
 /* API_NUMBER: ConsolepGetLangId */
-CSR_API(SrvGetConsoleLangId)
+CON_API(SrvGetConsoleLangId,
+        CONSOLE_GETLANGID, LangIdRequest)
 {
-    DPRINT1("%s not yet implemented\n", __FUNCTION__);
-    return STATUS_NOT_IMPLEMENTED;
+    /*
+     * Quoting MS Terminal, see function GetConsoleLangId() at
+     * https://github.com/microsoft/terminal/blob/main/src/host/srvinit.cpp#L655
+     * "Only attempt to return the Lang ID if the Windows ACP on console
+     * launch was an East Asian Code Page."
+     *
+     * The underlying logic is as follows:
+     *
+     * - When the current user's UI language is *not* CJK, the user expects
+     *   to not see any CJK output to the console by default, even if its
+     *   output has been set to a CJK code page (this is possible when CJK
+     *   fonts are installed on the system). That is, of course, unless if
+     *   the attached console program chooses to actually output CJK text.
+     *   Whatever current language of the running program's thread should
+     *   be kept: STATUS_NOT_SUPPORTED is returned.
+     *
+     * - When the current user's UI language *is* CJK, the user expects to
+     *   see CJK output to the console by default when its code page is CJK.
+     *   A valid LangId is returned in this case to ensure this.
+     *   However, if the console code page is not CJK, then it is evident
+     *   that CJK text will not be able to be correctly shown, and therefore
+     *   we should fall back to a standard language that can be shown, namely
+     *   en-US english, instead of keeping the current language.
+     */
+
+    BYTE UserCharSet = CodePageToCharSet(GetACP());
+    if (!IsCJKCharSet(UserCharSet))
+        return STATUS_NOT_SUPPORTED;
+
+    /* Return a "best-suited" language ID corresponding
+     * to the active console output code page. */
+    switch (Console->OutputCodePage)
+    {
+/** ReactOS-specific: do nothing if the code page is UTF-8. This will allow
+ ** programs to naturally output in whatever current language they are. **/
+    case CP_UTF8:
+        return STATUS_NOT_SUPPORTED;
+/** End ReactOS-specific **/
+    case CP_JAPANESE:
+        LangIdRequest->LangId = MAKELANGID(LANG_JAPANESE, SUBLANG_DEFAULT);
+        break;
+    case CP_KOREAN:
+        LangIdRequest->LangId = MAKELANGID(LANG_KOREAN, SUBLANG_KOREAN);
+        break;
+    case CP_CHINESE_SIMPLIFIED:
+        LangIdRequest->LangId = MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED);
+        break;
+    case CP_CHINESE_TRADITIONAL:
+        LangIdRequest->LangId = MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_TRADITIONAL);
+        break;
+    default:
+        /* Default to en-US english otherwise */
+        LangIdRequest->LangId = MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US);
+        break;
+    }
+
+    return STATUS_SUCCESS;
 }
 
 /* EOF */