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, L"Keyboard Layout\\Toggle" }, // WI_TOGGLE 18 { HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\CTF\\TIP" }, // WI_MACHINE_TIF 19 { HKEY_CURRENT_USER, L"Keyboard Layout\\Preload" }, // WI_PRELOAD 20 { HKEY_CURRENT_USER, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run" }, // WI_RUN 21 { HKEY_CURRENT_USER, L"SOFTWARE\\Microsoft\\CTF\\TIP" }, // WI_USER_TIF 22 { HKEY_CURRENT_USER, L"SOFTWARE\\Microsoft\\Speech" }, // WI_USER_SPEECH 23 { HKEY_CURRENT_USER, L"Control Panel\\Appearance" }, // WI_APPEARANCE 24 { HKEY_CURRENT_USER, L"Control Panel\\Colors" }, // WI_COLORS 25 { HKEY_CURRENT_USER, L"Control Panel\\Desktop\\WindowMetrics" }, // WI_WINDOW_METRICS 26 { HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Speech" }, // WI_MACHINE_SPEECH 27 { HKEY_CURRENT_USER, L"Keyboard Layout" }, // WI_KEYBOARD_LAYOUT 28 { HKEY_CURRENT_USER, L"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 & 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] = ::CreateEventW(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 BOOL 87 CRegWatcher::InitEvent( 88 _In_ SIZE_T iEvent, 89 _In_ BOOL bResetEvent) 90 { 91 // Reset the signal status 92 if (bResetEvent) 93 ::ResetEvent(s_ahWatchEvents[iEvent]); 94 95 // Close once to re-open 96 WATCHENTRY& entry = s_WatchEntries[iEvent]; 97 if (entry.hKey) 98 { 99 ::RegCloseKey(entry.hKey); 100 entry.hKey = NULL; 101 } 102 103 // Open or create a registry key to watch registry key 104 LSTATUS error; 105 error = ::RegOpenKeyExW(entry.hRootKey, entry.pszSubKey, 0, KEY_READ, &entry.hKey); 106 if (error != ERROR_SUCCESS) 107 { 108 error = ::RegCreateKeyExW(entry.hRootKey, entry.pszSubKey, 0, NULL, 0, 109 KEY_ALL_ACCESS, NULL, &entry.hKey, NULL); 110 if (error != ERROR_SUCCESS) 111 return FALSE; 112 } 113 114 // Start registry watching 115 error = RegNotifyChangeKeyValue(entry.hKey, 116 TRUE, 117 REG_NOTIFY_CHANGE_LAST_SET | REG_NOTIFY_CHANGE_NAME, 118 s_ahWatchEvents[iEvent], 119 TRUE); 120 return error == ERROR_SUCCESS; 121 } 122 123 VOID 124 CRegWatcher::UpdateSpTip() 125 { 126 // Post message 0x8002 to "SapiTipWorkerClass" windows 127 ::EnumWindows(EnumWndProc, 0); 128 129 // Clear "ProfileInitialized" value 130 HKEY hKey; 131 LSTATUS error = ::RegOpenKeyExW(HKEY_CURRENT_USER, L"SOFTWARE\\Microsoft\\CTF\\Sapilayr", 132 0, KEY_WRITE, &hKey); 133 if (error == ERROR_SUCCESS) 134 { 135 DWORD dwValue = 0, cbValue = sizeof(dwValue); 136 ::RegSetValueExW(hKey, L"ProfileInitialized", NULL, REG_DWORD, (LPBYTE)&dwValue, cbValue); 137 ::RegCloseKey(hKey); 138 } 139 140 // Get %WINDIR%/IME/sptip.dll!TF_CreateLangProfileUtil function 141 HINSTANCE hSPTIP = LoadSystemLibrary(L"IME\\sptip.dll", TRUE); 142 FN_TF_CreateLangProfileUtil fnTF_CreateLangProfileUtil = 143 (FN_TF_CreateLangProfileUtil)::GetProcAddress(hSPTIP, "TF_CreateLangProfileUtil"); 144 if (fnTF_CreateLangProfileUtil) 145 { 146 // Call it 147 ITfFnLangProfileUtil *pProfileUtil = NULL; 148 HRESULT hr = fnTF_CreateLangProfileUtil(&pProfileUtil); 149 if ((hr == S_OK) && pProfileUtil) // Success! 150 { 151 // Register profile 152 hr = pProfileUtil->RegisterActiveProfiles(); 153 if (hr == S_OK) 154 TF_InvalidAssemblyListCacheIfExist(); // Invalidate the assembly list cache 155 156 pProfileUtil->Release(); 157 } 158 } 159 160 if (hSPTIP) 161 ::FreeLibrary(hSPTIP); 162 } 163 164 VOID 165 CRegWatcher::KillInternat() 166 { 167 HKEY hKey; 168 WATCHENTRY& entry = s_WatchEntries[WI_RUN]; 169 170 // Delete internat.exe from registry "Run" key 171 LSTATUS error = ::RegOpenKeyExW(entry.hRootKey, entry.pszSubKey, 0, KEY_ALL_ACCESS, &hKey); 172 if (error == ERROR_SUCCESS) 173 { 174 ::RegDeleteValueW(hKey, L"internat.exe"); 175 ::RegCloseKey(hKey); 176 } 177 178 // Kill the "Indicator" window (that internat.exe creates) 179 HWND hwndInternat = ::FindWindowW(L"Indicator", NULL); 180 if (hwndInternat) 181 ::PostMessageW(hwndInternat, WM_CLOSE, 0, 0); 182 } 183 184 // Post message 0x8002 to every "SapiTipWorkerClass" window. 185 // Called from CRegWatcher::UpdateSpTip 186 BOOL CALLBACK 187 CRegWatcher::EnumWndProc( 188 _In_ HWND hWnd, 189 _In_ LPARAM lParam) 190 { 191 WCHAR ClassName[MAX_PATH]; 192 193 UNREFERENCED_PARAMETER(lParam); 194 195 if (::GetClassNameW(hWnd, ClassName, _countof(ClassName)) && 196 _wcsicmp(ClassName, L"SapiTipWorkerClass") == 0) 197 { 198 PostMessageW(hWnd, 0x8002, 0, 0); // FIXME: Magic number 199 } 200 201 return TRUE; 202 } 203 204 VOID CALLBACK 205 CRegWatcher::SysColorTimerProc( 206 _In_ HWND hwnd, 207 _In_ UINT uMsg, 208 _In_ UINT_PTR idEvent, 209 _In_ DWORD dwTime) 210 { 211 UNREFERENCED_PARAMETER(hwnd); 212 UNREFERENCED_PARAMETER(uMsg); 213 UNREFERENCED_PARAMETER(idEvent); 214 UNREFERENCED_PARAMETER(dwTime); 215 216 // Cancel the timer 217 if (s_nSysColorTimerId) 218 { 219 ::KillTimer(NULL, s_nSysColorTimerId); 220 s_nSysColorTimerId = 0; 221 } 222 223 TF_PostAllThreadMsg(15, 16); 224 } 225 226 VOID 227 CRegWatcher::StartSysColorChangeTimer() 228 { 229 // Call SysColorTimerProc 0.5 seconds later (Delayed) 230 if (s_nSysColorTimerId) 231 { 232 ::KillTimer(NULL, s_nSysColorTimerId); 233 s_nSysColorTimerId = 0; 234 } 235 s_nSysColorTimerId = ::SetTimer(NULL, 0, 500, SysColorTimerProc); 236 } 237 238 VOID CALLBACK 239 CRegWatcher::RegImxTimerProc( 240 _In_ HWND hwnd, 241 _In_ UINT uMsg, 242 _In_ UINT_PTR idEvent, 243 _In_ DWORD dwTime) 244 { 245 UNREFERENCED_PARAMETER(hwnd); 246 UNREFERENCED_PARAMETER(uMsg); 247 UNREFERENCED_PARAMETER(idEvent); 248 UNREFERENCED_PARAMETER(dwTime); 249 250 // Cancel the timer 251 if (s_nRegImxTimerId) 252 { 253 ::KillTimer(NULL, s_nRegImxTimerId); 254 s_nRegImxTimerId = 0; 255 } 256 257 TF_InvalidAssemblyListCache(); 258 TF_PostAllThreadMsg(12, 16); 259 } 260 261 VOID CALLBACK 262 CRegWatcher::KbdToggleTimerProc( 263 _In_ HWND hwnd, 264 _In_ UINT uMsg, 265 _In_ UINT_PTR idEvent, 266 _In_ DWORD dwTime) 267 { 268 UNREFERENCED_PARAMETER(hwnd); 269 UNREFERENCED_PARAMETER(uMsg); 270 UNREFERENCED_PARAMETER(idEvent); 271 UNREFERENCED_PARAMETER(dwTime); 272 273 // Cancel the timer 274 if (s_nKbdToggleTimerId) 275 { 276 ::KillTimer(NULL, s_nKbdToggleTimerId); 277 s_nKbdToggleTimerId = 0; 278 } 279 280 TF_PostAllThreadMsg(11, 16); 281 } 282 283 VOID 284 CRegWatcher::OnEvent( 285 _In_ SIZE_T iEvent) 286 { 287 InitEvent(iEvent, TRUE); 288 289 switch (iEvent) 290 { 291 case WI_TOGGLE: 292 { 293 // Call KbdToggleTimerProc 0.5 seconds later (Delayed) 294 if (s_nKbdToggleTimerId) 295 { 296 ::KillTimer(NULL, s_nKbdToggleTimerId); 297 s_nKbdToggleTimerId = 0; 298 } 299 s_nKbdToggleTimerId = ::SetTimer(NULL, 0, 500, KbdToggleTimerProc); 300 break; 301 } 302 case WI_MACHINE_TIF: 303 case WI_PRELOAD: 304 case WI_USER_TIF: 305 case WI_MACHINE_SPEECH: 306 case WI_KEYBOARD_LAYOUT: 307 case WI_ASSEMBLIES: 308 { 309 if (iEvent == WI_MACHINE_SPEECH) 310 UpdateSpTip(); 311 312 // Call RegImxTimerProc 0.2 seconds later (Delayed) 313 if (s_nRegImxTimerId) 314 { 315 ::KillTimer(NULL, s_nRegImxTimerId); 316 s_nRegImxTimerId = 0; 317 } 318 s_nRegImxTimerId = ::SetTimer(NULL, 0, 200, RegImxTimerProc); 319 break; 320 } 321 case WI_RUN: // The "Run" key is changed 322 { 323 KillInternat(); // Deny internat.exe the right to live 324 break; 325 } 326 case WI_USER_SPEECH: 327 case WI_APPEARANCE: 328 case WI_COLORS: 329 case WI_WINDOW_METRICS: 330 { 331 StartSysColorChangeTimer(); 332 break; 333 } 334 default: 335 { 336 break; 337 } 338 } 339 } 340