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