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
Init()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
Uninit()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
DelayedRegNotifyChangeKeyValue(HKEY hKey,BOOL bWatchSubtree,DWORD dwNotifyFilter,HANDLE hEvent,BOOL fAsynchronous)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
InitEvent(_In_ SIZE_T iEvent,_In_ BOOL bResetEvent)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
UpdateSpTip()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
KillInternat()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
EnumWndProc(_In_ HWND hWnd,_In_ LPARAM lParam)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
SysColorTimerProc(_In_ HWND hwnd,_In_ UINT uMsg,_In_ UINT_PTR idEvent,_In_ DWORD dwTime)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
StartSysColorChangeTimer()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
RegImxTimerProc(_In_ HWND hwnd,_In_ UINT uMsg,_In_ UINT_PTR idEvent,_In_ DWORD dwTime)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
KbdToggleTimerProc(_In_ HWND hwnd,_In_ UINT uMsg,_In_ UINT_PTR idEvent,_In_ DWORD dwTime)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
OnEvent(_In_ SIZE_T iEvent)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