https://git.reactos.org/?p=reactos.git;a=commitdiff;h=079b36542c87b5f64eb54…
commit 079b36542c87b5f64eb549d1d3836297b61ea6c2
Author: Katayama Hirofumi MZ <katayama.hirofumi.mz(a)gmail.com>
AuthorDate: Wed Dec 13 21:37:15 2023 +0900
Commit: GitHub <noreply(a)github.com>
CommitDate: Wed Dec 13 21:37:15 2023 +0900
[CTFMON][SDK] Add ctfmon.exe (#6149)
ctfmon.exe will be a replacement of our
kbswitch.exe in the future. That is the
front-end of Language Bar. It is needed
to support TIPs.
- Add ctfmon.exe at base/applications/ctfmon.
- Add <cicero/cicbase.h>,
<cicero/CModulePath.h>, and
<cicero/osinfo.h> headers and use them.
CORE-19362
---
base/applications/CMakeLists.txt | 1 +
base/applications/ctfmon/CLoaderWnd.cpp | 100 ++++++++
base/applications/ctfmon/CLoaderWnd.h | 26 +++
base/applications/ctfmon/CMakeLists.txt | 14 ++
base/applications/ctfmon/CRegWatcher.cpp | 339 +++++++++++++++++++++++++++
base/applications/ctfmon/CRegWatcher.h | 45 ++++
base/applications/ctfmon/ctfmon.cpp | 378 +++++++++++++++++++++++++++++++
base/applications/ctfmon/ctfmon.rc | 19 ++
base/applications/ctfmon/precomp.h | 49 ++++
base/applications/ctfmon/res/ctfmon.ico | Bin 0 -> 22486 bytes
base/applications/ctfmon/resource.h | 4 +
sdk/include/reactos/cicero/CModulePath.h | 90 ++++++++
sdk/include/reactos/cicero/cicbase.h | 44 ++++
sdk/include/reactos/cicero/osinfo.h | 47 ++++
14 files changed, 1156 insertions(+)
diff --git a/base/applications/CMakeLists.txt b/base/applications/CMakeLists.txt
index 7679233e5f2..dccaaa80438 100644
--- a/base/applications/CMakeLists.txt
+++ b/base/applications/CMakeLists.txt
@@ -6,6 +6,7 @@ add_subdirectory(charmap)
add_subdirectory(clipbrd)
add_subdirectory(cmdutils)
add_subdirectory(control)
+add_subdirectory(ctfmon)
add_subdirectory(drwtsn32)
add_subdirectory(dxdiag)
add_subdirectory(extrac32)
diff --git a/base/applications/ctfmon/CLoaderWnd.cpp
b/base/applications/ctfmon/CLoaderWnd.cpp
new file mode 100644
index 00000000000..400941be008
--- /dev/null
+++ b/base/applications/ctfmon/CLoaderWnd.cpp
@@ -0,0 +1,100 @@
+/*
+ * PROJECT: ReactOS CTF Monitor
+ * LICENSE: LGPL-2.1-or-later (
https://spdx.org/licenses/LGPL-2.1-or-later)
+ * PURPOSE: Cicero TIP Bar loader window
+ * COPYRIGHT: Copyright 2023 Katayama Hirofumi MZ
<katayama.hirofumi.mz(a)gmail.com>
+ */
+
+#include "precomp.h"
+#include "CLoaderWnd.h"
+#include "CRegWatcher.h"
+
+BOOL CLoaderWnd::s_bUninitedSystem = FALSE;
+BOOL CLoaderWnd::s_bWndClassRegistered = FALSE;
+
+BOOL CLoaderWnd::Init()
+{
+ if (s_bWndClassRegistered)
+ return TRUE; // Already registered
+
+ // Register a window class
+ WNDCLASSEXW wc;
+ ZeroMemory(&wc, sizeof(wc));
+ wc.cbSize = sizeof(wc);
+ wc.style = CS_HREDRAW | CS_VREDRAW;
+ wc.hInstance = g_hInst;
+ wc.hCursor = LoadCursorW(NULL, (LPCWSTR)IDC_ARROW);
+ wc.lpfnWndProc = WindowProc;
+ wc.lpszClassName = L"CiCTipBarClass";
+ if (!::RegisterClassExW(&wc))
+ return FALSE;
+
+ s_bWndClassRegistered = TRUE; // Remember
+ return TRUE;
+}
+
+HWND CLoaderWnd::CreateWnd()
+{
+ m_hWnd = ::CreateWindowExW(0, L"CiCTipBarClass", NULL, WS_DISABLED,
+ 0, 0, 0, 0, NULL, NULL, g_hInst, NULL);
+ return m_hWnd;
+}
+
+LRESULT CALLBACK
+CLoaderWnd::WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ switch (uMsg)
+ {
+ case WM_CREATE:
+ break;
+
+ case WM_DESTROY:
+ ::PostQuitMessage(0);
+ break;
+
+ case WM_QUERYENDSESSION:
+ // NOTE: We don't support Win95/98/Me
+#ifdef SUPPORT_WIN9X
+ if (!(g_dwOsInfo & OSINFO_NT) && (!g_fWinLogon || (lParam &
ENDSESSION_LOGOFF)))
+ {
+ ClosePopupTipbar();
+ TF_UninitSystem();
+ CLoaderWnd::s_bUninitedSystem = TRUE;
+ }
+#endif
+ return TRUE;
+
+ case WM_ENDSESSION:
+ if (wParam) // The session is being ended?
+ {
+ if (!s_bUninitedSystem)
+ {
+ // Un-initialize now
+ UninitApp();
+ TF_UninitSystem();
+ s_bUninitedSystem = TRUE;
+ }
+ }
+ else if (s_bUninitedSystem) // Once un-initialized?
+ {
+ // Re-initialize
+ TF_InitSystem();
+ if (!g_bOnWow64)
+ GetPopupTipbar(hwnd, !!g_fWinLogon);
+
+ s_bUninitedSystem = FALSE;
+ }
+ break;
+
+ case WM_SYSCOLORCHANGE:
+ case WM_DISPLAYCHANGE:
+ if (!g_bOnWow64) // Is the system x86/x64 native?
+ CRegWatcher::StartSysColorChangeTimer();
+ break;
+
+ default:
+ return DefWindowProcW(hwnd, uMsg, wParam, lParam);
+ }
+
+ return 0;
+}
diff --git a/base/applications/ctfmon/CLoaderWnd.h
b/base/applications/ctfmon/CLoaderWnd.h
new file mode 100644
index 00000000000..9127a40155e
--- /dev/null
+++ b/base/applications/ctfmon/CLoaderWnd.h
@@ -0,0 +1,26 @@
+/*
+ * PROJECT: ReactOS CTF Monitor
+ * LICENSE: LGPL-2.1-or-later (
https://spdx.org/licenses/LGPL-2.1-or-later)
+ * PURPOSE: Cicero TIP Bar loader window
+ * COPYRIGHT: Copyright 2023 Katayama Hirofumi MZ
<katayama.hirofumi.mz(a)gmail.com>
+ */
+
+#pragma once
+
+class CLoaderWnd
+{
+public:
+ HWND m_hWnd;
+ static BOOL s_bUninitedSystem;
+ static BOOL s_bWndClassRegistered;
+
+ CLoaderWnd() : m_hWnd(NULL) { }
+ ~CLoaderWnd() { }
+
+ BOOL Init();
+ HWND CreateWnd();
+
+protected:
+ static LRESULT CALLBACK
+ WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
+};
diff --git a/base/applications/ctfmon/CMakeLists.txt
b/base/applications/ctfmon/CMakeLists.txt
new file mode 100644
index 00000000000..f88d8f5ad1a
--- /dev/null
+++ b/base/applications/ctfmon/CMakeLists.txt
@@ -0,0 +1,14 @@
+
+list(APPEND SOURCE
+ ctfmon.cpp
+ CLoaderWnd.cpp
+ CRegWatcher.cpp)
+
+add_rc_deps(ctfmon.rc ${CMAKE_CURRENT_SOURCE_DIR}/res/ctfmon.ico)
+add_executable(ctfmon ${SOURCE} ctfmon.rc)
+set_module_type(ctfmon win32gui UNICODE)
+add_dependencies(ctfmon msctf)
+target_link_libraries(ctfmon uuid)
+add_importlibs(ctfmon msctf advapi32 shell32 user32 msvcrt kernel32)
+add_pch(ctfmon precomp.h SOURCE)
+add_cd_file(TARGET ctfmon DESTINATION reactos/system32 FOR all)
diff --git a/base/applications/ctfmon/CRegWatcher.cpp
b/base/applications/ctfmon/CRegWatcher.cpp
new file mode 100644
index 00000000000..7782b5c096d
--- /dev/null
+++ b/base/applications/ctfmon/CRegWatcher.cpp
@@ -0,0 +1,339 @@
+/*
+ * PROJECT: ReactOS CTF Monitor
+ * LICENSE: LGPL-2.1-or-later (
https://spdx.org/licenses/LGPL-2.1-or-later)
+ * PURPOSE: Registry watcher
+ * COPYRIGHT: Copyright 2023 Katayama Hirofumi MZ
<katayama.hirofumi.mz(a)gmail.com>
+ */
+
+#include "precomp.h"
+#include "CRegWatcher.h"
+
+// The event handles to use in watching
+HANDLE CRegWatcher::s_ahWatchEvents[WATCHENTRY_MAX] = { NULL };
+
+// The registry entries to watch
+WATCHENTRY CRegWatcher::s_WatchEntries[WATCHENTRY_MAX] =
+{
+ { HKEY_CURRENT_USER, L"Keyboard Layout\\Toggle"
}, // WI_TOGGLE
+ { HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\CTF\\TIP"
}, // WI_MACHINE_TIF
+ { HKEY_CURRENT_USER, L"Keyboard Layout\\Preload"
}, // WI_PRELOAD
+ { HKEY_CURRENT_USER, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run"
}, // WI_RUN
+ { HKEY_CURRENT_USER, L"SOFTWARE\\Microsoft\\CTF\\TIP"
}, // WI_USER_TIF
+ { HKEY_CURRENT_USER, L"SOFTWARE\\Microsoft\\Speech"
}, // WI_USER_SPEECH
+ { HKEY_CURRENT_USER, L"Control Panel\\Appearance"
}, // WI_APPEARANCE
+ { HKEY_CURRENT_USER, L"Control Panel\\Colors"
}, // WI_COLORS
+ { HKEY_CURRENT_USER, L"Control Panel\\Desktop\\WindowMetrics"
}, // WI_WINDOW_METRICS
+ { HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Speech"
}, // WI_MACHINE_SPEECH
+ { HKEY_CURRENT_USER, L"Keyboard Layout"
}, // WI_KEYBOARD_LAYOUT
+ { HKEY_CURRENT_USER, L"SOFTWARE\\Microsoft\\CTF\\Assemblies"
}, // WI_ASSEMBLIES
+};
+
+// The timer IDs: For delaying ignitions
+UINT CRegWatcher::s_nSysColorTimerId = 0;
+UINT CRegWatcher::s_nKbdToggleTimerId = 0;
+UINT CRegWatcher::s_nRegImxTimerId = 0;
+
+// %WINDIR%/IME/sptip.dll!TF_CreateLangProfileUtil
+typedef HRESULT (WINAPI* FN_TF_CreateLangProfileUtil)(ITfFnLangProfileUtil**);
+
+BOOL
+CRegWatcher::Init()
+{
+ // NOTE: We don't support Win95/98/Me
+#ifdef SUPPORT_WIN9X
+ if (!(g_dwOsInfo & OSINFO_NT))
+ s_WatchEntries[WI_RUN].hRootKey = HKEY_LOCAL_MACHINE;
+#endif
+
+ // Create some nameless events and initialize them
+ for (SIZE_T iEvent = 0; iEvent < _countof(s_ahWatchEvents); ++iEvent)
+ {
+ s_ahWatchEvents[iEvent] = ::CreateEventW(NULL, TRUE, FALSE, NULL);
+ InitEvent(iEvent, FALSE);
+ }
+
+ // Internat.exe is an enemy of ctfmon.exe
+ KillInternat();
+
+ UpdateSpTip();
+
+ return TRUE;
+}
+
+VOID
+CRegWatcher::Uninit()
+{
+ for (SIZE_T iEvent = 0; iEvent < _countof(s_ahWatchEvents); ++iEvent)
+ {
+ // Close the key
+ WATCHENTRY& entry = s_WatchEntries[iEvent];
+ if (entry.hKey)
+ {
+ ::RegCloseKey(entry.hKey);
+ entry.hKey = NULL;
+ }
+
+ // Close the event handle
+ HANDLE& hEvent = s_ahWatchEvents[iEvent];
+ if (hEvent)
+ {
+ ::CloseHandle(hEvent);
+ hEvent = NULL;
+ }
+ }
+}
+
+BOOL
+CRegWatcher::InitEvent(
+ _In_ SIZE_T iEvent,
+ _In_ BOOL bResetEvent)
+{
+ // Reset the signal status
+ if (bResetEvent)
+ ::ResetEvent(s_ahWatchEvents[iEvent]);
+
+ // Close once to re-open
+ WATCHENTRY& entry = s_WatchEntries[iEvent];
+ if (entry.hKey)
+ {
+ ::RegCloseKey(entry.hKey);
+ entry.hKey = NULL;
+ }
+
+ // Open or create a registry key to watch registry key
+ LSTATUS error;
+ error = ::RegOpenKeyExW(entry.hRootKey, entry.pszSubKey, 0, KEY_READ,
&entry.hKey);
+ if (error != ERROR_SUCCESS)
+ {
+ error = ::RegCreateKeyExW(entry.hRootKey, entry.pszSubKey, 0, NULL, 0,
+ KEY_ALL_ACCESS, NULL, &entry.hKey, NULL);
+ if (error != ERROR_SUCCESS)
+ return FALSE;
+ }
+
+ // Start registry watching
+ error = RegNotifyChangeKeyValue(entry.hKey,
+ TRUE,
+ REG_NOTIFY_CHANGE_LAST_SET | REG_NOTIFY_CHANGE_NAME,
+ s_ahWatchEvents[iEvent],
+ TRUE);
+ return error == ERROR_SUCCESS;
+}
+
+VOID
+CRegWatcher::UpdateSpTip()
+{
+ // Post message 0x8002 to "SapiTipWorkerClass" windows
+ ::EnumWindows(EnumWndProc, 0);
+
+ // Clear "ProfileInitialized" value
+ HKEY hKey;
+ LSTATUS error = ::RegOpenKeyExW(HKEY_CURRENT_USER,
L"SOFTWARE\\Microsoft\\CTF\\Sapilayr",
+ 0, KEY_WRITE, &hKey);
+ if (error == ERROR_SUCCESS)
+ {
+ DWORD dwValue = 0, cbValue = sizeof(dwValue);
+ ::RegSetValueExW(hKey, L"ProfileInitialized", NULL, REG_DWORD,
(LPBYTE)&dwValue, cbValue);
+ ::RegCloseKey(hKey);
+ }
+
+ // Get %WINDIR%/IME/sptip.dll!TF_CreateLangProfileUtil function
+ HINSTANCE hSPTIP = LoadSystemLibrary(L"IME\\sptip.dll", TRUE);
+ FN_TF_CreateLangProfileUtil fnTF_CreateLangProfileUtil =
+ (FN_TF_CreateLangProfileUtil)::GetProcAddress(hSPTIP,
"TF_CreateLangProfileUtil");
+ if (fnTF_CreateLangProfileUtil)
+ {
+ // Call it
+ ITfFnLangProfileUtil *pProfileUtil = NULL;
+ HRESULT hr = fnTF_CreateLangProfileUtil(&pProfileUtil);
+ if ((hr == S_OK) && pProfileUtil) // Success!
+ {
+ // Register profile
+ hr = pProfileUtil->RegisterActiveProfiles();
+ if (hr == S_OK)
+ TF_InvalidAssemblyListCacheIfExist(); // Invalidate the assembly list
cache
+
+ pProfileUtil->Release();
+ }
+ }
+
+ if (hSPTIP)
+ ::FreeLibrary(hSPTIP);
+}
+
+VOID
+CRegWatcher::KillInternat()
+{
+ HKEY hKey;
+ WATCHENTRY& entry = s_WatchEntries[WI_RUN];
+
+ // Delete internat.exe from registry "Run" key
+ LSTATUS error = ::RegOpenKeyExW(entry.hRootKey, entry.pszSubKey, 0, KEY_ALL_ACCESS,
&hKey);
+ if (error == ERROR_SUCCESS)
+ {
+ ::RegDeleteValueW(hKey, L"internat.exe");
+ ::RegCloseKey(hKey);
+ }
+
+ // Kill the "Indicator" window (that internat.exe creates)
+ HWND hwndInternat = ::FindWindowW(L"Indicator", NULL);
+ if (hwndInternat)
+ ::PostMessageW(hwndInternat, WM_CLOSE, 0, 0);
+}
+
+// Post message 0x8002 to every "SapiTipWorkerClass" window.
+// Called from CRegWatcher::UpdateSpTip
+BOOL CALLBACK
+CRegWatcher::EnumWndProc(
+ _In_ HWND hWnd,
+ _In_ LPARAM lParam)
+{
+ WCHAR ClassName[MAX_PATH];
+
+ UNREFERENCED_PARAMETER(lParam);
+
+ if (::GetClassNameW(hWnd, ClassName, _countof(ClassName)) &&
+ _wcsicmp(ClassName, L"SapiTipWorkerClass") == 0)
+ {
+ PostMessageW(hWnd, 0x8002, 0, 0); // FIXME: Magic number
+ }
+
+ return TRUE;
+}
+
+VOID CALLBACK
+CRegWatcher::SysColorTimerProc(
+ _In_ HWND hwnd,
+ _In_ UINT uMsg,
+ _In_ UINT_PTR idEvent,
+ _In_ DWORD dwTime)
+{
+ UNREFERENCED_PARAMETER(hwnd);
+ UNREFERENCED_PARAMETER(uMsg);
+ UNREFERENCED_PARAMETER(idEvent);
+ UNREFERENCED_PARAMETER(dwTime);
+
+ // Cancel the timer
+ if (s_nSysColorTimerId)
+ {
+ ::KillTimer(NULL, s_nSysColorTimerId);
+ s_nSysColorTimerId = 0;
+ }
+
+ TF_PostAllThreadMsg(15, 16);
+}
+
+VOID
+CRegWatcher::StartSysColorChangeTimer()
+{
+ // Call SysColorTimerProc 0.5 seconds later (Delayed)
+ if (s_nSysColorTimerId)
+ {
+ ::KillTimer(NULL, s_nSysColorTimerId);
+ s_nSysColorTimerId = 0;
+ }
+ s_nSysColorTimerId = ::SetTimer(NULL, 0, 500, SysColorTimerProc);
+}
+
+VOID CALLBACK
+CRegWatcher::RegImxTimerProc(
+ _In_ HWND hwnd,
+ _In_ UINT uMsg,
+ _In_ UINT_PTR idEvent,
+ _In_ DWORD dwTime)
+{
+ UNREFERENCED_PARAMETER(hwnd);
+ UNREFERENCED_PARAMETER(uMsg);
+ UNREFERENCED_PARAMETER(idEvent);
+ UNREFERENCED_PARAMETER(dwTime);
+
+ // Cancel the timer
+ if (s_nRegImxTimerId)
+ {
+ ::KillTimer(NULL, s_nRegImxTimerId);
+ s_nRegImxTimerId = 0;
+ }
+
+ TF_InvalidAssemblyListCache();
+ TF_PostAllThreadMsg(12, 16);
+}
+
+VOID CALLBACK
+CRegWatcher::KbdToggleTimerProc(
+ _In_ HWND hwnd,
+ _In_ UINT uMsg,
+ _In_ UINT_PTR idEvent,
+ _In_ DWORD dwTime)
+{
+ UNREFERENCED_PARAMETER(hwnd);
+ UNREFERENCED_PARAMETER(uMsg);
+ UNREFERENCED_PARAMETER(idEvent);
+ UNREFERENCED_PARAMETER(dwTime);
+
+ // Cancel the timer
+ if (s_nKbdToggleTimerId)
+ {
+ ::KillTimer(NULL, s_nKbdToggleTimerId);
+ s_nKbdToggleTimerId = 0;
+ }
+
+ TF_PostAllThreadMsg(11, 16);
+}
+
+VOID
+CRegWatcher::OnEvent(
+ _In_ SIZE_T iEvent)
+{
+ InitEvent(iEvent, TRUE);
+
+ switch (iEvent)
+ {
+ case WI_TOGGLE:
+ {
+ // Call KbdToggleTimerProc 0.5 seconds later (Delayed)
+ if (s_nKbdToggleTimerId)
+ {
+ ::KillTimer(NULL, s_nKbdToggleTimerId);
+ s_nKbdToggleTimerId = 0;
+ }
+ s_nKbdToggleTimerId = ::SetTimer(NULL, 0, 500, KbdToggleTimerProc);
+ break;
+ }
+ case WI_MACHINE_TIF:
+ case WI_PRELOAD:
+ case WI_USER_TIF:
+ case WI_MACHINE_SPEECH:
+ case WI_KEYBOARD_LAYOUT:
+ case WI_ASSEMBLIES:
+ {
+ if (iEvent == WI_MACHINE_SPEECH)
+ UpdateSpTip();
+
+ // Call RegImxTimerProc 0.2 seconds later (Delayed)
+ if (s_nRegImxTimerId)
+ {
+ ::KillTimer(NULL, s_nRegImxTimerId);
+ s_nRegImxTimerId = 0;
+ }
+ s_nRegImxTimerId = ::SetTimer(NULL, 0, 200, RegImxTimerProc);
+ break;
+ }
+ case WI_RUN: // The "Run" key is changed
+ {
+ KillInternat(); // Deny internat.exe the right to live
+ break;
+ }
+ case WI_USER_SPEECH:
+ case WI_APPEARANCE:
+ case WI_COLORS:
+ case WI_WINDOW_METRICS:
+ {
+ StartSysColorChangeTimer();
+ break;
+ }
+ default:
+ {
+ break;
+ }
+ }
+}
diff --git a/base/applications/ctfmon/CRegWatcher.h
b/base/applications/ctfmon/CRegWatcher.h
new file mode 100644
index 00000000000..1f75cb7fb4b
--- /dev/null
+++ b/base/applications/ctfmon/CRegWatcher.h
@@ -0,0 +1,45 @@
+/*
+ * PROJECT: ReactOS CTF Monitor
+ * LICENSE: LGPL-2.1-or-later (
https://spdx.org/licenses/LGPL-2.1-or-later)
+ * PURPOSE: Registry watcher
+ * COPYRIGHT: Copyright 2023 Katayama Hirofumi MZ
<katayama.hirofumi.mz(a)gmail.com>
+ */
+
+#pragma once
+
+struct WATCHENTRY
+{
+ HKEY hRootKey;
+ LPCWSTR pszSubKey;
+ HKEY hKey;
+};
+
+#define WATCHENTRY_MAX 12
+
+struct CRegWatcher
+{
+ static HANDLE s_ahWatchEvents[WATCHENTRY_MAX];
+ static WATCHENTRY s_WatchEntries[WATCHENTRY_MAX];
+ static UINT s_nSysColorTimerId, s_nKbdToggleTimerId, s_nRegImxTimerId;
+
+ static BOOL Init();
+ static VOID Uninit();
+ static BOOL InitEvent(_In_ SIZE_T iEvent, _In_ BOOL bResetEvent);
+ static VOID UpdateSpTip();
+ static VOID KillInternat();
+ static VOID StartSysColorChangeTimer();
+ static VOID OnEvent(_In_ SIZE_T iEvent);
+
+protected:
+ static BOOL CALLBACK
+ EnumWndProc(_In_ HWND hWnd, _In_ LPARAM lParam);
+
+ static VOID CALLBACK
+ SysColorTimerProc(_In_ HWND hWnd, _In_ UINT uMsg, _In_ UINT_PTR idEvent, _In_ DWORD
dwTime);
+
+ static VOID CALLBACK
+ KbdToggleTimerProc(_In_ HWND hWnd, _In_ UINT uMsg, _In_ UINT_PTR idEvent, _In_ DWORD
dwTime);
+
+ static VOID CALLBACK
+ RegImxTimerProc(_In_ HWND hWnd, _In_ UINT uMsg, _In_ UINT_PTR idEvent, _In_ DWORD
dwTime);
+};
diff --git a/base/applications/ctfmon/ctfmon.cpp b/base/applications/ctfmon/ctfmon.cpp
new file mode 100644
index 00000000000..ebeae0fd933
--- /dev/null
+++ b/base/applications/ctfmon/ctfmon.cpp
@@ -0,0 +1,378 @@
+/*
+ * PROJECT: ReactOS CTF Monitor
+ * LICENSE: LGPL-2.1-or-later (
https://spdx.org/licenses/LGPL-2.1-or-later)
+ * PURPOSE: Providing Language Bar front-end
+ * COPYRIGHT: Copyright 2023 Katayama Hirofumi MZ
<katayama.hirofumi.mz(a)gmail.com>
+ */
+
+#include "precomp.h"
+#include "CRegWatcher.h"
+#include "CLoaderWnd.h"
+
+// ntdll!NtQueryInformationProcess
+typedef NTSTATUS (WINAPI *FN_NtQueryInformationProcess)(HANDLE, PROCESSINFOCLASS, PVOID,
ULONG, PULONG);
+FN_NtQueryInformationProcess g_fnNtQueryInformationProcess = NULL;
+
+// kernel32!SetProcessShutdownParameters
+typedef BOOL (WINAPI *FN_SetProcessShutdownParameters)(DWORD, DWORD);
+FN_SetProcessShutdownParameters g_fnSetProcessShutdownParameters = NULL;
+
+// kernel32!GetSystemWow64DirectoryW
+typedef UINT (WINAPI *FN_GetSystemWow64DirectoryW)(LPWSTR, UINT);
+FN_GetSystemWow64DirectoryW g_fnGetSystemWow64DirectoryW = NULL;
+
+HINSTANCE g_hInst = NULL; // The application instance
+HINSTANCE g_hKernel32 = NULL; // The "kernel32.dll" instance
+UINT g_uACP = CP_ACP; // The active codepage
+BOOL g_fWinLogon = FALSE; // Is it a log-on process?
+HANDLE g_hCicMutex = NULL; // The Cicero mutex
+BOOL g_bOnWow64 = FALSE; // Is the app running on WoW64?
+BOOL g_fNoRunKey = FALSE; // Don't write registry key "Run"?
+BOOL g_fJustRunKey = FALSE; // Just write registry key "Run"?
+DWORD g_dwOsInfo = 0; // The OS version info. See GetOSInfo
+CLoaderWnd* g_pLoaderWnd = NULL; // TIP Bar loader window
+
+// Is the current process on WoW64?
+static BOOL
+IsWow64(VOID)
+{
+ HMODULE hNTDLL = GetSystemModuleHandle(L"ntdll.dll", FALSE);
+ if (!hNTDLL)
+ return FALSE;
+
+ g_fnNtQueryInformationProcess =
+ (FN_NtQueryInformationProcess)::GetProcAddress(hNTDLL,
"NtQueryInformationProcess");
+ if (!g_fnNtQueryInformationProcess)
+ return FALSE;
+
+ ULONG_PTR Value = 0;
+ NTSTATUS Status = g_fnNtQueryInformationProcess(::GetCurrentProcess(),
ProcessWow64Information,
+ &Value, sizeof(Value), NULL);
+ if (!NT_SUCCESS(Status))
+ return FALSE;
+
+ return !!Value;
+}
+
+static VOID
+ParseCommandLine(
+ _In_ LPCWSTR pszCmdLine)
+{
+ g_fNoRunKey = g_fJustRunKey = FALSE;
+
+ for (LPCWSTR pch = pszCmdLine; *pch; ++pch)
+ {
+ // Skip space
+ while (*pch == L' ')
+ ++pch;
+
+ if (*pch == UNICODE_NULL)
+ return;
+
+ if ((*pch == L'-') || (*pch == L'/'))
+ {
+ ++pch;
+ switch (*pch)
+ {
+ case L'N': case L'n': // Found "/N" option
+ g_fNoRunKey = TRUE;
+ break;
+
+ case L'R': case L'r': // Found "/R" option
+ g_fJustRunKey = TRUE;
+ break;
+
+ case UNICODE_NULL:
+ return;
+
+ default:
+ break;
+ }
+ }
+ }
+}
+
+static VOID
+WriteRegRun(VOID)
+{
+ if (g_fNoRunKey) // If "/N" option is specified
+ return; // Don't write
+
+ // Open "Run" key
+ HKEY hKey;
+ LSTATUS error = ::RegCreateKeyW(HKEY_CURRENT_USER,
+
L"Software\\Microsoft\\Windows\\CurrentVersion\\Run",
+ &hKey);
+ if (error != ERROR_SUCCESS)
+ return;
+
+ // Write the module path
+ CModulePath ModPath;
+ if (ModPath.Init(L"ctfmon.exe", FALSE))
+ {
+ DWORD cbData = (ModPath.m_cchPath + 1) * sizeof(WCHAR);
+ ::RegSetValueExW(hKey, L"ctfmon.exe", 0, REG_SZ,
(BYTE*)ModPath.m_szPath, cbData);
+ }
+
+ ::RegCloseKey(hKey);
+}
+
+static HRESULT
+GetGlobalCompartment(
+ _In_ REFGUID guid,
+ _Inout_ ITfCompartment **ppComp)
+{
+ *ppComp = NULL;
+
+ ITfCompartmentMgr *pCompMgr = NULL;
+ HRESULT hr = TF_GetGlobalCompartment(&pCompMgr);
+ if (FAILED(hr))
+ return hr;
+
+ if (!pCompMgr)
+ return E_FAIL;
+
+ hr = pCompMgr->GetCompartment(guid, ppComp);
+ pCompMgr->Release();
+ return hr;
+}
+
+static HRESULT
+SetGlobalCompartmentDWORD(
+ _In_ REFGUID guid,
+ _In_ DWORD dwValue)
+{
+ HRESULT hr;
+ VARIANT vari;
+ ITfCompartment *pComp;
+
+ hr = GetGlobalCompartment(guid, &pComp);
+ if (FAILED(hr))
+ return hr;
+
+ V_VT(&vari) = VT_I4;
+ V_I4(&vari) = dwValue;
+ hr = pComp->SetValue(0, &vari);
+
+ pComp->Release();
+ return hr;
+}
+
+static BOOL
+CheckX64System(
+ _In_ LPWSTR lpCmdLine)
+{
+ // Is the system x64?
+ SYSTEM_INFO SystemInfo;
+ ::GetSystemInfo(&SystemInfo);
+ if (SystemInfo.wProcessorArchitecture != PROCESSOR_ARCHITECTURE_IA64 ||
+ SystemInfo.wProcessorArchitecture != PROCESSOR_ARCHITECTURE_AMD64)
+ {
+ return FALSE;
+ }
+
+ // Get GetSystemWow64DirectoryW function
+ g_hKernel32 = GetSystemModuleHandle(L"kernel32.dll", FALSE);
+ g_fnGetSystemWow64DirectoryW =
+ (FN_GetSystemWow64DirectoryW)::GetProcAddress(g_hKernel32,
"GetSystemWow64DirectoryW");
+ if (!g_fnGetSystemWow64DirectoryW)
+ return FALSE;
+
+ // Build WoW64 ctfmon.exe pathname
+ WCHAR szPath[MAX_PATH];
+ UINT cchPath = g_fnGetSystemWow64DirectoryW(szPath, _countof(szPath));
+ if (!cchPath && FAILED(StringCchCatW(szPath, _countof(szPath),
L"\\ctfmon.exe")))
+ return FALSE;
+
+ // Create a WoW64 ctfmon.exe process
+ PROCESS_INFORMATION pi;
+ STARTUPINFOW si = { sizeof(si) };
+ si.wShowWindow = SW_SHOWMINNOACTIVE;
+ if (!::CreateProcessW(szPath, lpCmdLine, NULL, NULL, FALSE, 0, NULL, NULL, &si,
&pi))
+ return FALSE;
+
+ ::CloseHandle(pi.hThread);
+ ::CloseHandle(pi.hProcess);
+ return TRUE;
+}
+
+static BOOL
+InitApp(
+ _In_ HINSTANCE hInstance,
+ _In_ LPWSTR lpCmdLine)
+{
+ g_hInst = hInstance; // Save the instance handle
+
+ g_uACP = ::GetACP(); // Save the active codepage
+ g_bOnWow64 = IsWow64(); // Is the current process on WoW64?
+ g_dwOsInfo = GetOSInfo(); // Get OS info
+
+ // Create a mutex for Cicero
+ g_hCicMutex = TF_CreateCicLoadMutex(&g_fWinLogon);
+ if (!g_hCicMutex)
+ return FALSE;
+
+ // Write to "Run" registry key for starting up
+ WriteRegRun();
+
+ // Call SetProcessShutdownParameters if possible
+ if (g_dwOsInfo & OSINFO_NT)
+ {
+ g_hKernel32 = GetSystemModuleHandle(L"kernel32.dll", FALSE);
+ g_fnSetProcessShutdownParameters =
+ (FN_SetProcessShutdownParameters)
+ ::GetProcAddress(g_hKernel32, "SetProcessShutdownParameters");
+ if (g_fnSetProcessShutdownParameters)
+ g_fnSetProcessShutdownParameters(0xF0, SHUTDOWN_NORETRY);
+ }
+
+ // Start text framework
+ TF_InitSystem();
+
+ // Start watching registry if x86/x64 native
+ if (!g_bOnWow64)
+ CRegWatcher::Init();
+
+ // Create TIP Bar loader window
+ g_pLoaderWnd = new CLoaderWnd();
+ if (!g_pLoaderWnd || !g_pLoaderWnd->Init())
+ return FALSE;
+
+ if (g_pLoaderWnd->CreateWnd())
+ {
+ // Go to the bottom of the hell
+ ::SetWindowPos(g_pLoaderWnd->m_hWnd, HWND_BOTTOM, 0, 0, 0, 0,
+ SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE);
+ }
+
+ // Display TIP Bar Popup if x86/x64 native
+ if (!g_bOnWow64)
+ GetPopupTipbar(g_pLoaderWnd->m_hWnd, g_fWinLogon);
+
+ // Do x64 stuffs
+ CheckX64System(lpCmdLine);
+
+ return TRUE;
+}
+
+VOID
+UninitApp(VOID)
+{
+ // Close TIP Bar Popup
+ ClosePopupTipbar();
+
+ // Close the mutex
+ ::CloseHandle(g_hCicMutex);
+ g_hCicMutex = NULL;
+
+ // Quit watching registry if x86/x64 native
+ if (!g_bOnWow64)
+ CRegWatcher::Uninit();
+}
+
+static INT
+DoMainLoop(VOID)
+{
+ MSG msg;
+
+ if (g_bOnWow64) // Is the current process on WoW64?
+ {
+ // Just a simple message loop
+ while (::GetMessageW(&msg, NULL, 0, 0))
+ {
+ ::TranslateMessage(&msg);
+ ::DispatchMessageW(&msg);
+ }
+ return (INT)msg.wParam;
+ }
+
+ // Open the existing event by the name
+ HANDLE hSwitchEvent = ::OpenEventW(SYNCHRONIZE, FALSE,
L"WinSta0_DesktopSwitch");
+
+ // The target events to watch
+ HANDLE ahEvents[WATCHENTRY_MAX + 1];
+
+ // Borrow some handles from CRegWatcher
+ CopyMemory(ahEvents, CRegWatcher::s_ahWatchEvents, WATCHENTRY_MAX * sizeof(HANDLE));
+
+ ahEvents[WI_DESKTOP_SWITCH] = hSwitchEvent; // Add it
+
+ // Another message loop
+ for (;;)
+ {
+ // Wait for target signal
+ DWORD dwWait = ::MsgWaitForMultipleObjects(_countof(ahEvents), ahEvents, 0,
INFINITE,
+ QS_ALLINPUT);
+ if (dwWait == (WAIT_OBJECT_0 + _countof(ahEvents))) // Is input available?
+ {
+ // Do the events
+ while (::PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE))
+ {
+ if (msg.message == WM_QUIT)
+ goto Quit;
+
+ ::TranslateMessage(&msg);
+ ::DispatchMessageW(&msg);
+ }
+ }
+ else if (dwWait == (WAIT_OBJECT_0 + WI_DESKTOP_SWITCH)) // Desktop switch?
+ {
+ SetGlobalCompartmentDWORD(GUID_COMPARTMENT_SPEECH_OPENCLOSE, 0);
+ ::ResetEvent(hSwitchEvent);
+ }
+ else // Do the other events
+ {
+ CRegWatcher::OnEvent(dwWait - WAIT_OBJECT_0);
+ }
+ }
+
+Quit:
+ ::CloseHandle(hSwitchEvent);
+
+ return (INT)msg.wParam;
+}
+
+// The main function for Unicode Win32
+EXTERN_C INT WINAPI
+wWinMain(
+ HINSTANCE hInstance,
+ HINSTANCE hPrevInst,
+ LPWSTR lpCmdLine,
+ INT nCmdShow)
+{
+ UNREFERENCED_PARAMETER(hPrevInst);
+ UNREFERENCED_PARAMETER(nCmdShow);
+
+ // Parse command line
+ ParseCommandLine(lpCmdLine);
+
+ if (g_fJustRunKey) // If "/R" option is specified
+ {
+ // Just write registry and exit
+ WriteRegRun();
+ return 1;
+ }
+
+ // Initialize the application
+ if (!InitApp(hInstance, lpCmdLine))
+ return 0;
+
+ // The main loop
+ INT ret = DoMainLoop();
+
+ // Clean up the loader
+ if (g_pLoaderWnd)
+ {
+ delete g_pLoaderWnd;
+ g_pLoaderWnd = NULL;
+ }
+
+ // Un-initialize app and text framework
+ if (!CLoaderWnd::s_bUninitedSystem)
+ {
+ UninitApp();
+ TF_UninitSystem();
+ }
+
+ return ret;
+}
diff --git a/base/applications/ctfmon/ctfmon.rc b/base/applications/ctfmon/ctfmon.rc
new file mode 100644
index 00000000000..fe628048326
--- /dev/null
+++ b/base/applications/ctfmon/ctfmon.rc
@@ -0,0 +1,19 @@
+/*
+ * PROJECT: ReactOS CTF Monitor
+ * LICENSE: LGPL-2.1-or-later (
https://spdx.org/licenses/LGPL-2.1-or-later)
+ * PURPOSE: Providing Language Bar front-end
+ * COPYRIGHT: Copyright 2023 Katayama Hirofumi MZ
<katayama.hirofumi.mz(a)gmail.com>
+ */
+
+#include <windef.h>
+
+#include "resource.h"
+
+LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
+
+#define REACTOS_STR_FILE_DESCRIPTION "ReactOS CTF Monitor"
+#define REACTOS_STR_INTERNAL_NAME "ctfmon"
+#define REACTOS_STR_ORIGINAL_FILENAME "ctfmon.exe"
+#include <reactos/version.rc>
+
+IDI_MAIN ICON "res/ctfmon.ico"
diff --git a/base/applications/ctfmon/precomp.h b/base/applications/ctfmon/precomp.h
new file mode 100644
index 00000000000..636739eb9e1
--- /dev/null
+++ b/base/applications/ctfmon/precomp.h
@@ -0,0 +1,49 @@
+/*
+ * PROJECT: ReactOS CTF Monitor
+ * LICENSE: LGPL-2.1-or-later (
https://spdx.org/licenses/LGPL-2.1-or-later)
+ * PURPOSE: Providing Language Bar front-end
+ * COPYRIGHT: Copyright 2023 Katayama Hirofumi MZ
<katayama.hirofumi.mz(a)gmail.com>
+ */
+
+#pragma once
+
+#define WIN32_NO_STATUS
+#include <windows.h>
+#include <ndk/pstypes.h>
+#include <shellapi.h>
+#include <shlwapi.h>
+#include <stdlib.h>
+#include <strsafe.h>
+#include <msctf.h>
+#include <ctfutb.h>
+#include <ctffunc.h>
+
+#include <cicero/cicbase.h>
+#include <cicero/osinfo.h>
+#include <cicero/CModulePath.h>
+
+#include "resource.h"
+
+extern HINSTANCE g_hInst;
+extern BOOL g_bOnWow64;
+extern BOOL g_fWinLogon;
+extern DWORD g_dwOsInfo;
+
+VOID UninitApp(VOID);
+
+typedef enum WATCH_INDEX
+{
+ WI_TOGGLE = 0,
+ WI_MACHINE_TIF = 1,
+ WI_PRELOAD = 2,
+ WI_RUN = 3,
+ WI_USER_TIF = 4,
+ WI_USER_SPEECH = 5,
+ WI_APPEARANCE = 6,
+ WI_COLORS = 7,
+ WI_WINDOW_METRICS = 8,
+ WI_MACHINE_SPEECH = 9,
+ WI_KEYBOARD_LAYOUT = 10,
+ WI_ASSEMBLIES = 11,
+ WI_DESKTOP_SWITCH = 12,
+} WATCH_INDEX;
diff --git a/base/applications/ctfmon/res/ctfmon.ico
b/base/applications/ctfmon/res/ctfmon.ico
new file mode 100644
index 00000000000..5d7c7626fbf
Binary files /dev/null and b/base/applications/ctfmon/res/ctfmon.ico differ
diff --git a/base/applications/ctfmon/resource.h b/base/applications/ctfmon/resource.h
new file mode 100644
index 00000000000..79f0b3152af
--- /dev/null
+++ b/base/applications/ctfmon/resource.h
@@ -0,0 +1,4 @@
+#pragma once
+
+/* Icons */
+#define IDI_MAIN 100
diff --git a/sdk/include/reactos/cicero/CModulePath.h
b/sdk/include/reactos/cicero/CModulePath.h
new file mode 100644
index 00000000000..2418b157cdd
--- /dev/null
+++ b/sdk/include/reactos/cicero/CModulePath.h
@@ -0,0 +1,90 @@
+/*
+ * PROJECT: ReactOS Cicero
+ * LICENSE: LGPL-2.1-or-later (
https://spdx.org/licenses/LGPL-2.1-or-later)
+ * PURPOSE: Manipulate module path
+ * COPYRIGHT: Copyright 2023 Katayama Hirofumi MZ
<katayama.hirofumi.mz(a)gmail.com>
+ */
+
+#pragma once
+
+struct CModulePath
+{
+ WCHAR m_szPath[MAX_PATH];
+ SIZE_T m_cchPath;
+
+ CModulePath()
+ {
+ m_szPath[0] = UNICODE_NULL;
+ m_cchPath = 0;
+ }
+
+ BOOL Init(_In_ LPCWSTR pszFileName, _In_ BOOL bSysWinDir);
+};
+
+// Get an instance handle that is already loaded
+static inline HINSTANCE
+GetSystemModuleHandle(
+ _In_ LPCWSTR pszFileName,
+ _In_ BOOL bSysWinDir)
+{
+ CModulePath ModPath;
+ if (!ModPath.Init(pszFileName, bSysWinDir))
+ return NULL;
+ return GetModuleHandleW(ModPath.m_szPath);
+}
+
+// Load a system library
+static inline HINSTANCE
+LoadSystemLibrary(
+ _In_ LPCWSTR pszFileName,
+ _In_ BOOL bSysWinDir)
+{
+ CModulePath ModPath;
+ if (!ModPath.Init(pszFileName, bSysWinDir))
+ return NULL;
+ return ::LoadLibraryW(ModPath.m_szPath);
+}
+
+/******************************************************************************/
+
+inline BOOL
+CModulePath::Init(
+ _In_ LPCWSTR pszFileName,
+ _In_ BOOL bSysWinDir)
+{
+ SIZE_T cchPath;
+ if (bSysWinDir)
+ {
+ // Usually C:\Windows or C:\ReactOS
+ cchPath = ::GetSystemWindowsDirectory(m_szPath, _countof(m_szPath));
+ }
+ else
+ {
+ // Usually C:\Windows\system32 or C:\ReactOS\system32
+ cchPath = ::GetSystemDirectoryW(m_szPath, _countof(m_szPath));
+ }
+
+ m_szPath[_countof(m_szPath) - 1] = UNICODE_NULL; // Avoid buffer overrun
+
+ if ((cchPath == 0) || (cchPath > _countof(m_szPath) - 2))
+ goto Failure;
+
+ // Add backslash if necessary
+ if ((cchPath > 0) && (m_szPath[cchPath - 1] != L'\\'))
+ {
+ m_szPath[cchPath + 0] = L'\\';
+ m_szPath[cchPath + 1] = UNICODE_NULL;
+ }
+
+ // Append pszFileName
+ if (FAILED(StringCchCatW(m_szPath, _countof(m_szPath), pszFileName)))
+ goto Failure;
+
+ m_cchPath = wcslen(m_szPath);
+ return TRUE;
+
+Failure:
+ m_szPath[0] = UNICODE_NULL;
+ m_cchPath = 0;
+ return FALSE;
+}
diff --git a/sdk/include/reactos/cicero/cicbase.h b/sdk/include/reactos/cicero/cicbase.h
new file mode 100644
index 00000000000..9048dcb865a
--- /dev/null
+++ b/sdk/include/reactos/cicero/cicbase.h
@@ -0,0 +1,44 @@
+/*
+ * PROJECT: ReactOS Cicero
+ * LICENSE: LGPL-2.1-or-later (
https://spdx.org/licenses/LGPL-2.1-or-later)
+ * PURPOSE: Cicero base
+ * COPYRIGHT: Copyright 2023 Katayama Hirofumi MZ
<katayama.hirofumi.mz(a)gmail.com>
+ */
+
+static inline LPVOID cicMemAllocClear(SIZE_T size)
+{
+ return LocalAlloc(LMEM_ZEROINIT, size);
+}
+
+static inline void cicMemFree(LPVOID ptr)
+{
+ if (ptr)
+ LocalFree(ptr);
+}
+
+#ifdef __cplusplus
+inline void* __cdecl operator new(size_t size) noexcept
+{
+ return cicMemAllocClear(size);
+}
+
+inline void __cdecl operator delete(void* ptr) noexcept
+{
+ cicMemFree(ptr);
+}
+
+inline void __cdecl operator delete(void* ptr, size_t size) noexcept
+{
+ cicMemFree(ptr);
+}
+#endif // __cplusplus
+
+// FIXME: Use msutb.dll and header
+static inline void ClosePopupTipbar(void)
+{
+}
+
+// FIXME: Use msutb.dll and header
+static inline void GetPopupTipbar(HWND hwnd, BOOL fWinLogon)
+{
+}
diff --git a/sdk/include/reactos/cicero/osinfo.h b/sdk/include/reactos/cicero/osinfo.h
new file mode 100644
index 00000000000..b260000dfbc
--- /dev/null
+++ b/sdk/include/reactos/cicero/osinfo.h
@@ -0,0 +1,47 @@
+/*
+ * PROJECT: ReactOS Cicero
+ * LICENSE: LGPL-2.1-or-later (
https://spdx.org/licenses/LGPL-2.1-or-later)
+ * PURPOSE: Getting OS information
+ * COPYRIGHT: Copyright 2023 Katayama Hirofumi MZ
<katayama.hirofumi.mz(a)gmail.com>
+ */
+
+#pragma once
+
+/* The flags of GetOSInfo() */
+#define OSINFO_NT 0x01
+#define OSINFO_CJK 0x10
+#define OSINFO_IMM 0x20
+#define OSINFO_DBCS 0x40
+
+static inline DWORD
+GetOSInfo(VOID)
+{
+ DWORD dwOsInfo = 0;
+
+ /* Check OS version info */
+ OSVERSIONINFOW VerInfo = { sizeof(VerInfo) };
+ GetVersionExW(&VerInfo);
+ if (VerInfo.dwPlatformId == DLLVER_PLATFORM_NT)
+ dwOsInfo |= OSINFO_NT;
+
+ /* Check codepage */
+ switch (GetACP())
+ {
+ case 932: /* Japanese (Japan) */
+ case 936: /* Chinese (PRC, Singapore) */
+ case 949: /* Korean (Korea) */
+ case 950: /* Chinese (Taiwan, Hong Kong) */
+ dwOsInfo |= OSINFO_CJK;
+ break;
+ }
+
+ if (GetSystemMetrics(SM_IMMENABLED))
+ dwOsInfo |= OSINFO_IMM;
+
+ if (GetSystemMetrics(SM_DBCSENABLED))
+ dwOsInfo |= OSINFO_DBCS;
+
+ /* I'm not interested in other flags */
+
+ return dwOsInfo;
+}