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