/* * 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 */ #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, TEXT("Keyboard Layout\\Toggle") }, // WI_TOGGLE { HKEY_LOCAL_MACHINE, TEXT("SOFTWARE\\Microsoft\\CTF\\TIP") }, // WI_MACHINE_TIF { HKEY_CURRENT_USER, TEXT("Keyboard Layout\\Preload") }, // WI_PRELOAD { HKEY_CURRENT_USER, TEXT("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run") }, // WI_RUN { HKEY_CURRENT_USER, TEXT("SOFTWARE\\Microsoft\\CTF\\TIP") }, // WI_USER_TIF { HKEY_CURRENT_USER, TEXT("SOFTWARE\\Microsoft\\Speech") }, // WI_USER_SPEECH { HKEY_CURRENT_USER, TEXT("Control Panel\\Appearance") }, // WI_APPEARANCE { HKEY_CURRENT_USER, TEXT("Control Panel\\Colors") }, // WI_COLORS { HKEY_CURRENT_USER, TEXT("Control Panel\\Desktop\\WindowMetrics") }, // WI_WINDOW_METRICS { HKEY_LOCAL_MACHINE, TEXT("SOFTWARE\\Microsoft\\Speech") }, // WI_MACHINE_SPEECH { HKEY_CURRENT_USER, TEXT("Keyboard Layout") }, // WI_KEYBOARD_LAYOUT { HKEY_CURRENT_USER, TEXT("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 & CIC_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] = ::CreateEvent(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; } } } // advapi32!RegNotifyChangeKeyValue typedef LONG (WINAPI *FN_RegNotifyChangeKeyValue)(HKEY, BOOL, DWORD, HANDLE, BOOL); LONG WINAPI DelayedRegNotifyChangeKeyValue( HKEY hKey, BOOL bWatchSubtree, DWORD dwNotifyFilter, HANDLE hEvent, BOOL fAsynchronous) { static FN_RegNotifyChangeKeyValue s_fnRegNotifyChangeKeyValue = NULL; if (!s_fnRegNotifyChangeKeyValue) { HINSTANCE hAdvApi32 = cicGetSystemModuleHandle(TEXT("advapi32.dll"), FALSE); s_fnRegNotifyChangeKeyValue = (FN_RegNotifyChangeKeyValue)GetProcAddress(hAdvApi32, "RegNotifyChangeKeyValue"); if (!s_fnRegNotifyChangeKeyValue) return ERROR_CALL_NOT_IMPLEMENTED; } return s_fnRegNotifyChangeKeyValue(hKey, bWatchSubtree, dwNotifyFilter, hEvent, fAsynchronous); } 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 = ::RegOpenKeyEx(entry.hRootKey, entry.pszSubKey, 0, KEY_READ, &entry.hKey); if (error != ERROR_SUCCESS) { error = ::RegCreateKeyEx(entry.hRootKey, entry.pszSubKey, 0, NULL, 0, KEY_ALL_ACCESS, NULL, &entry.hKey, NULL); if (error != ERROR_SUCCESS) return FALSE; } // Start registry watching error = DelayedRegNotifyChangeKeyValue(entry.hKey, TRUE, REG_NOTIFY_CHANGE_LAST_SET | REG_NOTIFY_CHANGE_NAME, s_ahWatchEvents[iEvent], TRUE); #ifndef NDEBUG if (error != ERROR_SUCCESS) OutputDebugStringA("RegNotifyChangeKeyValue failed\n"); #endif return error == ERROR_SUCCESS; } VOID CRegWatcher::UpdateSpTip() { // Post message 0x8002 to "SapiTipWorkerClass" windows ::EnumWindows(EnumWndProc, 0); // Clear "ProfileInitialized" value HKEY hKey; LSTATUS error = ::RegOpenKeyEx(HKEY_CURRENT_USER, TEXT("SOFTWARE\\Microsoft\\CTF\\Sapilayr"), 0, KEY_WRITE, &hKey); if (error == ERROR_SUCCESS) { DWORD dwValue = 0, cbValue = sizeof(dwValue); ::RegSetValueEx(hKey, TEXT("ProfileInitialized"), NULL, REG_DWORD, (LPBYTE)&dwValue, cbValue); ::RegCloseKey(hKey); } // Get %WINDIR%/IME/sptip.dll!TF_CreateLangProfileUtil function HINSTANCE hSPTIP = cicLoadSystemLibrary(TEXT("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 = ::RegOpenKeyEx(entry.hRootKey, entry.pszSubKey, 0, KEY_ALL_ACCESS, &hKey); if (error == ERROR_SUCCESS) { ::RegDeleteValue(hKey, TEXT("internat.exe")); ::RegCloseKey(hKey); } // Kill the "Indicator" window (that internat.exe creates) HWND hwndInternat = ::FindWindow(TEXT("Indicator"), NULL); if (hwndInternat) ::PostMessage(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) { TCHAR ClassName[MAX_PATH]; UNREFERENCED_PARAMETER(lParam); if (::GetClassName(hWnd, ClassName, _countof(ClassName)) && _tcsicmp(ClassName, TEXT("SapiTipWorkerClass")) == 0) { PostMessage(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; } } }