1 /* 2 * PROJECT: ReactOS CTF Monitor 3 * LICENSE: LGPL-2.1-or-later (https://spdx.org/licenses/LGPL-2.1-or-later) 4 * PURPOSE: Registry watcher 5 * COPYRIGHT: Copyright 2023 Katayama Hirofumi MZ <katayama.hirofumi.mz@gmail.com> 6 */ 7 8 #include "precomp.h" 9 #include "CRegWatcher.h" 10 11 // The event handles to use in watching 12 HANDLE CRegWatcher::s_ahWatchEvents[WATCHENTRY_MAX] = { NULL }; 13 14 // The registry entries to watch 15 WATCHENTRY CRegWatcher::s_WatchEntries[WATCHENTRY_MAX] = 16 { 17 { HKEY_CURRENT_USER, TEXT("Keyboard Layout\\Toggle") }, // WI_TOGGLE 18 { HKEY_LOCAL_MACHINE, TEXT("SOFTWARE\\Microsoft\\CTF\\TIP") }, // WI_MACHINE_TIF 19 { HKEY_CURRENT_USER, TEXT("Keyboard Layout\\Preload") }, // WI_PRELOAD 20 { HKEY_CURRENT_USER, TEXT("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run") }, // WI_RUN 21 { HKEY_CURRENT_USER, TEXT("SOFTWARE\\Microsoft\\CTF\\TIP") }, // WI_USER_TIF 22 { HKEY_CURRENT_USER, TEXT("SOFTWARE\\Microsoft\\Speech") }, // WI_USER_SPEECH 23 { HKEY_CURRENT_USER, TEXT("Control Panel\\Appearance") }, // WI_APPEARANCE 24 { HKEY_CURRENT_USER, TEXT("Control Panel\\Colors") }, // WI_COLORS 25 { HKEY_CURRENT_USER, TEXT("Control Panel\\Desktop\\WindowMetrics") }, // WI_WINDOW_METRICS 26 { HKEY_LOCAL_MACHINE, TEXT("SOFTWARE\\Microsoft\\Speech") }, // WI_MACHINE_SPEECH 27 { HKEY_CURRENT_USER, TEXT("Keyboard Layout") }, // WI_KEYBOARD_LAYOUT 28 { HKEY_CURRENT_USER, TEXT("SOFTWARE\\Microsoft\\CTF\\Assemblies") }, // WI_ASSEMBLIES 29 }; 30 31 // The timer IDs: For delaying ignitions 32 UINT CRegWatcher::s_nSysColorTimerId = 0; 33 UINT CRegWatcher::s_nKbdToggleTimerId = 0; 34 UINT CRegWatcher::s_nRegImxTimerId = 0; 35 36 // %WINDIR%/IME/sptip.dll!TF_CreateLangProfileUtil 37 typedef HRESULT (WINAPI* FN_TF_CreateLangProfileUtil)(ITfFnLangProfileUtil**); 38 39 BOOL 40 CRegWatcher::Init() 41 { 42 // NOTE: We don't support Win95/98/Me 43 #ifdef SUPPORT_WIN9X 44 if (!(g_dwOsInfo & CIC_OSINFO_NT)) 45 s_WatchEntries[WI_RUN].hRootKey = HKEY_LOCAL_MACHINE; 46 #endif 47 48 // Create some nameless events and initialize them 49 for (SIZE_T iEvent = 0; iEvent < _countof(s_ahWatchEvents); ++iEvent) 50 { 51 s_ahWatchEvents[iEvent] = ::CreateEvent(NULL, TRUE, FALSE, NULL); 52 InitEvent(iEvent, FALSE); 53 } 54 55 // Internat.exe is an enemy of ctfmon.exe 56 KillInternat(); 57 58 UpdateSpTip(); 59 60 return TRUE; 61 } 62 63 VOID 64 CRegWatcher::Uninit() 65 { 66 for (SIZE_T iEvent = 0; iEvent < _countof(s_ahWatchEvents); ++iEvent) 67 { 68 // Close the key 69 WATCHENTRY& entry = s_WatchEntries[iEvent]; 70 if (entry.hKey) 71 { 72 ::RegCloseKey(entry.hKey); 73 entry.hKey = NULL; 74 } 75 76 // Close the event handle 77 HANDLE& hEvent = s_ahWatchEvents[iEvent]; 78 if (hEvent) 79 { 80 ::CloseHandle(hEvent); 81 hEvent = NULL; 82 } 83 } 84 } 85 86 // advapi32!RegNotifyChangeKeyValue 87 typedef LONG (WINAPI *FN_RegNotifyChangeKeyValue)(HKEY, BOOL, DWORD, HANDLE, BOOL); 88 89 LONG WINAPI 90 DelayedRegNotifyChangeKeyValue( 91 HKEY hKey, 92 BOOL bWatchSubtree, 93 DWORD dwNotifyFilter, 94 HANDLE hEvent, 95 BOOL fAsynchronous) 96 { 97 static FN_RegNotifyChangeKeyValue s_fnRegNotifyChangeKeyValue = NULL; 98 99 if (!s_fnRegNotifyChangeKeyValue) 100 { 101 HINSTANCE hAdvApi32 = cicGetSystemModuleHandle(TEXT("advapi32.dll"), FALSE); 102 s_fnRegNotifyChangeKeyValue = 103 (FN_RegNotifyChangeKeyValue)GetProcAddress(hAdvApi32, "RegNotifyChangeKeyValue"); 104 if (!s_fnRegNotifyChangeKeyValue) 105 return ERROR_CALL_NOT_IMPLEMENTED; 106 } 107 108 return s_fnRegNotifyChangeKeyValue(hKey, bWatchSubtree, dwNotifyFilter, hEvent, fAsynchronous); 109 } 110 111 BOOL 112 CRegWatcher::InitEvent( 113 _In_ SIZE_T iEvent, 114 _In_ BOOL bResetEvent) 115 { 116 // Reset the signal status 117 if (bResetEvent) 118 ::ResetEvent(s_ahWatchEvents[iEvent]); 119 120 // Close once to re-open 121 WATCHENTRY& entry = s_WatchEntries[iEvent]; 122 if (entry.hKey) 123 { 124 ::RegCloseKey(entry.hKey); 125 entry.hKey = NULL; 126 } 127 128 // Open or create a registry key to watch registry key 129 LSTATUS error; 130 error = ::RegOpenKeyEx(entry.hRootKey, entry.pszSubKey, 0, KEY_READ, &entry.hKey); 131 if (error != ERROR_SUCCESS) 132 { 133 error = ::RegCreateKeyEx(entry.hRootKey, entry.pszSubKey, 0, NULL, 0, 134 KEY_ALL_ACCESS, NULL, &entry.hKey, NULL); 135 if (error != ERROR_SUCCESS) 136 return FALSE; 137 } 138 139 // Start registry watching 140 error = DelayedRegNotifyChangeKeyValue(entry.hKey, 141 TRUE, 142 REG_NOTIFY_CHANGE_LAST_SET | REG_NOTIFY_CHANGE_NAME, 143 s_ahWatchEvents[iEvent], 144 TRUE); 145 #ifndef NDEBUG 146 if (error != ERROR_SUCCESS) 147 OutputDebugStringA("RegNotifyChangeKeyValue failed\n"); 148 #endif 149 return error == ERROR_SUCCESS; 150 } 151 152 VOID 153 CRegWatcher::UpdateSpTip() 154 { 155 // Post message 0x8002 to "SapiTipWorkerClass" windows 156 ::EnumWindows(EnumWndProc, 0); 157 158 // Clear "ProfileInitialized" value 159 HKEY hKey; 160 LSTATUS error = ::RegOpenKeyEx(HKEY_CURRENT_USER, 161 TEXT("SOFTWARE\\Microsoft\\CTF\\Sapilayr"), 162 0, KEY_WRITE, &hKey); 163 if (error == ERROR_SUCCESS) 164 { 165 DWORD dwValue = 0, cbValue = sizeof(dwValue); 166 ::RegSetValueEx(hKey, TEXT("ProfileInitialized"), NULL, REG_DWORD, (LPBYTE)&dwValue, cbValue); 167 ::RegCloseKey(hKey); 168 } 169 170 // Get %WINDIR%/IME/sptip.dll!TF_CreateLangProfileUtil function 171 HINSTANCE hSPTIP = cicLoadSystemLibrary(TEXT("IME\\sptip.dll"), TRUE); 172 FN_TF_CreateLangProfileUtil fnTF_CreateLangProfileUtil = 173 (FN_TF_CreateLangProfileUtil)::GetProcAddress(hSPTIP, "TF_CreateLangProfileUtil"); 174 if (fnTF_CreateLangProfileUtil) 175 { 176 // Call it 177 ITfFnLangProfileUtil *pProfileUtil = NULL; 178 HRESULT hr = fnTF_CreateLangProfileUtil(&pProfileUtil); 179 if ((hr == S_OK) && pProfileUtil) // Success! 180 { 181 // Register profile 182 hr = pProfileUtil->RegisterActiveProfiles(); 183 if (hr == S_OK) 184 TF_InvalidAssemblyListCacheIfExist(); // Invalidate the assembly list cache 185 186 pProfileUtil->Release(); 187 } 188 } 189 190 if (hSPTIP) 191 ::FreeLibrary(hSPTIP); 192 } 193 194 VOID 195 CRegWatcher::KillInternat() 196 { 197 HKEY hKey; 198 WATCHENTRY& entry = s_WatchEntries[WI_RUN]; 199 200 // Delete internat.exe from registry "Run" key 201 LSTATUS error = ::RegOpenKeyEx(entry.hRootKey, entry.pszSubKey, 0, KEY_ALL_ACCESS, &hKey); 202 if (error == ERROR_SUCCESS) 203 { 204 ::RegDeleteValue(hKey, TEXT("internat.exe")); 205 ::RegCloseKey(hKey); 206 } 207 208 // Kill the "Indicator" window (that internat.exe creates) 209 HWND hwndInternat = ::FindWindow(TEXT("Indicator"), NULL); 210 if (hwndInternat) 211 ::PostMessage(hwndInternat, WM_CLOSE, 0, 0); 212 } 213 214 // Post message 0x8002 to every "SapiTipWorkerClass" window. 215 // Called from CRegWatcher::UpdateSpTip 216 BOOL CALLBACK 217 CRegWatcher::EnumWndProc( 218 _In_ HWND hWnd, 219 _In_ LPARAM lParam) 220 { 221 TCHAR ClassName[MAX_PATH]; 222 223 UNREFERENCED_PARAMETER(lParam); 224 225 if (::GetClassName(hWnd, ClassName, _countof(ClassName)) && 226 _tcsicmp(ClassName, TEXT("SapiTipWorkerClass")) == 0) 227 { 228 PostMessage(hWnd, 0x8002, 0, 0); // FIXME: Magic number 229 } 230 231 return TRUE; 232 } 233 234 VOID CALLBACK 235 CRegWatcher::SysColorTimerProc( 236 _In_ HWND hwnd, 237 _In_ UINT uMsg, 238 _In_ UINT_PTR idEvent, 239 _In_ DWORD dwTime) 240 { 241 UNREFERENCED_PARAMETER(hwnd); 242 UNREFERENCED_PARAMETER(uMsg); 243 UNREFERENCED_PARAMETER(idEvent); 244 UNREFERENCED_PARAMETER(dwTime); 245 246 // Cancel the timer 247 if (s_nSysColorTimerId) 248 { 249 ::KillTimer(NULL, s_nSysColorTimerId); 250 s_nSysColorTimerId = 0; 251 } 252 253 TF_PostAllThreadMsg(15, 16); 254 } 255 256 VOID 257 CRegWatcher::StartSysColorChangeTimer() 258 { 259 // Call SysColorTimerProc 0.5 seconds later (Delayed) 260 if (s_nSysColorTimerId) 261 { 262 ::KillTimer(NULL, s_nSysColorTimerId); 263 s_nSysColorTimerId = 0; 264 } 265 s_nSysColorTimerId = ::SetTimer(NULL, 0, 500, SysColorTimerProc); 266 } 267 268 VOID CALLBACK 269 CRegWatcher::RegImxTimerProc( 270 _In_ HWND hwnd, 271 _In_ UINT uMsg, 272 _In_ UINT_PTR idEvent, 273 _In_ DWORD dwTime) 274 { 275 UNREFERENCED_PARAMETER(hwnd); 276 UNREFERENCED_PARAMETER(uMsg); 277 UNREFERENCED_PARAMETER(idEvent); 278 UNREFERENCED_PARAMETER(dwTime); 279 280 // Cancel the timer 281 if (s_nRegImxTimerId) 282 { 283 ::KillTimer(NULL, s_nRegImxTimerId); 284 s_nRegImxTimerId = 0; 285 } 286 287 TF_InvalidAssemblyListCache(); 288 TF_PostAllThreadMsg(12, 16); 289 } 290 291 VOID CALLBACK 292 CRegWatcher::KbdToggleTimerProc( 293 _In_ HWND hwnd, 294 _In_ UINT uMsg, 295 _In_ UINT_PTR idEvent, 296 _In_ DWORD dwTime) 297 { 298 UNREFERENCED_PARAMETER(hwnd); 299 UNREFERENCED_PARAMETER(uMsg); 300 UNREFERENCED_PARAMETER(idEvent); 301 UNREFERENCED_PARAMETER(dwTime); 302 303 // Cancel the timer 304 if (s_nKbdToggleTimerId) 305 { 306 ::KillTimer(NULL, s_nKbdToggleTimerId); 307 s_nKbdToggleTimerId = 0; 308 } 309 310 TF_PostAllThreadMsg(11, 16); 311 } 312 313 VOID 314 CRegWatcher::OnEvent( 315 _In_ SIZE_T iEvent) 316 { 317 InitEvent(iEvent, TRUE); 318 319 switch (iEvent) 320 { 321 case WI_TOGGLE: 322 { 323 // Call KbdToggleTimerProc 0.5 seconds later (Delayed) 324 if (s_nKbdToggleTimerId) 325 { 326 ::KillTimer(NULL, s_nKbdToggleTimerId); 327 s_nKbdToggleTimerId = 0; 328 } 329 s_nKbdToggleTimerId = ::SetTimer(NULL, 0, 500, KbdToggleTimerProc); 330 break; 331 } 332 case WI_MACHINE_TIF: 333 case WI_PRELOAD: 334 case WI_USER_TIF: 335 case WI_MACHINE_SPEECH: 336 case WI_KEYBOARD_LAYOUT: 337 case WI_ASSEMBLIES: 338 { 339 if (iEvent == WI_MACHINE_SPEECH) 340 UpdateSpTip(); 341 342 // Call RegImxTimerProc 0.2 seconds later (Delayed) 343 if (s_nRegImxTimerId) 344 { 345 ::KillTimer(NULL, s_nRegImxTimerId); 346 s_nRegImxTimerId = 0; 347 } 348 s_nRegImxTimerId = ::SetTimer(NULL, 0, 200, RegImxTimerProc); 349 break; 350 } 351 case WI_RUN: // The "Run" key is changed 352 { 353 KillInternat(); // Deny internat.exe the right to live 354 break; 355 } 356 case WI_USER_SPEECH: 357 case WI_APPEARANCE: 358 case WI_COLORS: 359 case WI_WINDOW_METRICS: 360 { 361 StartSysColorChangeTimer(); 362 break; 363 } 364 default: 365 { 366 break; 367 } 368 } 369 } 370