xref: /reactos/base/applications/ctfmon/ctfmon.cpp (revision bfa3e554)
1 /*
2  * PROJECT:     ReactOS CTF Monitor
3  * LICENSE:     LGPL-2.1-or-later (https://spdx.org/licenses/LGPL-2.1-or-later)
4  * PURPOSE:     Providing Language Bar front-end
5  * COPYRIGHT:   Copyright 2023 Katayama Hirofumi MZ <katayama.hirofumi.mz@gmail.com>
6  */
7 
8 #include "precomp.h"
9 #include "CRegWatcher.h"
10 #include "CLoaderWnd.h"
11 
12 // kernel32!SetProcessShutdownParameters
13 typedef BOOL (WINAPI *FN_SetProcessShutdownParameters)(DWORD, DWORD);
14 FN_SetProcessShutdownParameters g_fnSetProcessShutdownParameters = NULL;
15 
16 // kernel32!GetSystemWow64DirectoryA
17 typedef UINT (WINAPI *FN_GetSystemWow64DirectoryA)(LPSTR, UINT);
18 FN_GetSystemWow64DirectoryA g_fnGetSystemWow64DirectoryA = NULL;
19 // kernel32!GetSystemWow64DirectoryW
20 typedef UINT (WINAPI *FN_GetSystemWow64DirectoryW)(LPWSTR, UINT);
21 FN_GetSystemWow64DirectoryW g_fnGetSystemWow64DirectoryW = NULL;
22 
23 HINSTANCE   g_hInst         = NULL;     // The application instance
24 HINSTANCE   g_hKernel32     = NULL;     // The "kernel32.dll" instance
25 UINT        g_uACP          = CP_ACP;   // The active codepage
26 BOOL        g_fWinLogon     = FALSE;    // Is it a log-on process?
27 HANDLE      g_hCicMutex     = NULL;     // The Cicero mutex
28 BOOL        g_bOnWow64      = FALSE;    // Is the app running on WoW64?
29 BOOL        g_fNoRunKey     = FALSE;    // Don't write registry key "Run"?
30 BOOL        g_fJustRunKey   = FALSE;    // Just write registry key "Run"?
31 DWORD       g_dwOsInfo      = 0;        // The OS version info. See cicGetOSInfo
32 CLoaderWnd* g_pLoaderWnd    = NULL;     // Tipbar loader window
33 
34 static VOID
35 ParseCommandLine(
36     _In_ LPCTSTR pszCmdLine)
37 {
38     g_fNoRunKey = g_fJustRunKey = FALSE;
39 
40     for (LPCTSTR pch = pszCmdLine; *pch; ++pch)
41     {
42         // Skip space
43         while (*pch == TEXT(' '))
44             ++pch;
45 
46         if (*pch == TEXT('\0'))
47             return;
48 
49         if ((*pch == TEXT('-')) || (*pch == TEXT('/')))
50         {
51             ++pch;
52             switch (*pch)
53             {
54                 case TEXT('N'): case TEXT('n'): // Found "/N" option
55                     g_fNoRunKey = TRUE;
56                     break;
57 
58                 case TEXT('R'): case TEXT('r'): // Found "/R" option
59                     g_fJustRunKey = TRUE;
60                     break;
61 
62                 case UNICODE_NULL:
63                     return;
64 
65                 default:
66                     break;
67             }
68         }
69     }
70 }
71 
72 static VOID
73 WriteRegRun(VOID)
74 {
75     if (g_fNoRunKey) // If "/N" option is specified
76         return; // Don't write
77 
78     // Open "Run" key
79     HKEY hKey;
80     LSTATUS error = ::RegCreateKey(HKEY_CURRENT_USER,
81                                    TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Run"),
82                                    &hKey);
83     if (error != ERROR_SUCCESS)
84         return;
85 
86     // Write the module path
87     CicSystemModulePath ModPath;
88     if (ModPath.Init(TEXT("ctfmon.exe"), FALSE))
89     {
90         DWORD cbData = (ModPath.m_cchPath + 1) * sizeof(TCHAR);
91         ::RegSetValueEx(hKey, TEXT("ctfmon.exe"), 0, REG_SZ, (BYTE*)ModPath.m_szPath, cbData);
92     }
93 
94     ::RegCloseKey(hKey);
95 }
96 
97 static HRESULT
98 GetGlobalCompartment(
99     _In_ REFGUID guid,
100     _Inout_ ITfCompartment **ppComp)
101 {
102     *ppComp = NULL;
103 
104     ITfCompartmentMgr *pCompMgr = NULL;
105     HRESULT hr = TF_GetGlobalCompartment(&pCompMgr);
106     if (FAILED(hr))
107         return hr;
108 
109     if (!pCompMgr)
110         return E_FAIL;
111 
112     hr = pCompMgr->GetCompartment(guid, ppComp);
113     pCompMgr->Release();
114     return hr;
115 }
116 
117 static HRESULT
118 SetGlobalCompartmentDWORD(
119     _In_ REFGUID guid,
120     _In_ DWORD dwValue)
121 {
122     HRESULT hr;
123     VARIANT vari;
124     ITfCompartment *pComp;
125 
126     hr = GetGlobalCompartment(guid, &pComp);
127     if (FAILED(hr))
128         return hr;
129 
130     V_VT(&vari) = VT_I4;
131     V_I4(&vari) = dwValue;
132     hr = pComp->SetValue(0, &vari);
133 
134     pComp->Release();
135     return hr;
136 }
137 
138 static BOOL
139 CheckX64System(
140     _In_ LPTSTR lpCmdLine)
141 {
142     // Is the system x64?
143     SYSTEM_INFO SystemInfo;
144     ::GetSystemInfo(&SystemInfo);
145     if (SystemInfo.wProcessorArchitecture != PROCESSOR_ARCHITECTURE_IA64 ||
146         SystemInfo.wProcessorArchitecture != PROCESSOR_ARCHITECTURE_AMD64)
147     {
148         return FALSE;
149     }
150 
151     // Get GetSystemWow64DirectoryW function
152     g_hKernel32 = cicGetSystemModuleHandle(TEXT("kernel32.dll"), FALSE);
153 #ifdef UNICODE
154     g_fnGetSystemWow64DirectoryW =
155         (FN_GetSystemWow64DirectoryW)::GetProcAddress(g_hKernel32, "GetSystemWow64DirectoryW");
156     if (!g_fnGetSystemWow64DirectoryW)
157         return FALSE;
158 #else
159     g_fnGetSystemWow64DirectoryA =
160         (FN_GetSystemWow64DirectoryA)::GetProcAddress(g_hKernel32, "GetSystemWow64DirectoryA");
161     if (!g_fnGetSystemWow64DirectoryA)
162         return FALSE;
163 #endif
164 
165     // Build WoW64 ctfmon.exe pathname
166     TCHAR szPath[MAX_PATH];
167 #ifdef UNICODE
168     UINT cchPath = g_fnGetSystemWow64DirectoryW(szPath, _countof(szPath));
169 #else
170     UINT cchPath = g_fnGetSystemWow64DirectoryA(szPath, _countof(szPath));
171 #endif
172     if (!cchPath && FAILED(StringCchCat(szPath, _countof(szPath), TEXT("\\ctfmon.exe"))))
173         return FALSE;
174 
175     // Create a WoW64 ctfmon.exe process
176     PROCESS_INFORMATION pi;
177     STARTUPINFO si = { sizeof(si) };
178     si.wShowWindow = SW_SHOWMINNOACTIVE;
179     if (!::CreateProcess(szPath, lpCmdLine, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi))
180         return FALSE;
181 
182     ::CloseHandle(pi.hThread);
183     ::CloseHandle(pi.hProcess);
184     return TRUE;
185 }
186 
187 static BOOL
188 InitApp(
189     _In_ HINSTANCE hInstance,
190     _In_ LPTSTR lpCmdLine)
191 {
192     g_hInst     = hInstance;    // Save the instance handle
193 
194     g_bOnWow64  = cicIsWow64();   // Is the current process on WoW64?
195     cicGetOSInfo(&g_uACP, &g_dwOsInfo); // Get OS info
196 
197     // Initialize Cicero
198     TFInitLib();
199 
200     // Create a mutex for Cicero
201     g_hCicMutex = TF_CreateCicLoadMutex(&g_fWinLogon);
202     if (!g_hCicMutex)
203         return FALSE;
204 
205     // Write to "Run" registry key for starting up
206     WriteRegRun();
207 
208     // Call SetProcessShutdownParameters if possible
209     if (g_dwOsInfo & CIC_OSINFO_NT)
210     {
211         g_hKernel32 = cicGetSystemModuleHandle(TEXT("kernel32.dll"), FALSE);
212         g_fnSetProcessShutdownParameters =
213             (FN_SetProcessShutdownParameters)
214                 ::GetProcAddress(g_hKernel32, "SetProcessShutdownParameters");
215         if (g_fnSetProcessShutdownParameters)
216             g_fnSetProcessShutdownParameters(0xF0, SHUTDOWN_NORETRY);
217     }
218 
219     // Start text framework
220     TF_InitSystem();
221 
222     // Start watching registry if x86/x64 native
223     if (!g_bOnWow64)
224         CRegWatcher::Init();
225 
226     // Create Tipbar loader window
227     g_pLoaderWnd = new CLoaderWnd();
228     if (!g_pLoaderWnd || !g_pLoaderWnd->Init())
229         return FALSE;
230 
231     if (g_pLoaderWnd->CreateWnd())
232     {
233         // Go to the bottom of the hell
234         ::SetWindowPos(g_pLoaderWnd->m_hWnd, HWND_BOTTOM, 0, 0, 0, 0,
235                        SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE);
236     }
237 
238     // Display Tipbar Popup if x86/x64 native and necessary
239     if (!g_bOnWow64)
240         GetPopupTipbar(g_pLoaderWnd->m_hWnd, g_fWinLogon);
241 
242     // Do x64 stuffs
243     CheckX64System(lpCmdLine);
244 
245     return TRUE;
246 }
247 
248 VOID
249 UninitApp(VOID)
250 {
251     // Close Tipbar Popup
252     ClosePopupTipbar();
253 
254     // Release Cicero
255     TFUninitLib();
256 
257     // Close the mutex
258     ::CloseHandle(g_hCicMutex);
259     g_hCicMutex = NULL;
260 
261     // Quit watching registry if x86/x64 native
262     if (!g_bOnWow64)
263         CRegWatcher::Uninit();
264 }
265 
266 static INT
267 DoMainLoop(VOID)
268 {
269     MSG msg;
270 
271     if (g_bOnWow64) // Is the current process on WoW64?
272     {
273         // Just a simple message loop
274         while (::GetMessage(&msg, NULL, 0, 0))
275         {
276             ::TranslateMessage(&msg);
277             ::DispatchMessage(&msg);
278         }
279         return (INT)msg.wParam;
280     }
281 
282     // Open the existing event by the name
283     HANDLE hSwitchEvent = ::OpenEvent(SYNCHRONIZE, FALSE, TEXT("WinSta0_DesktopSwitch"));
284 
285     // The target events to watch
286     HANDLE ahEvents[WATCHENTRY_MAX + 1];
287 
288     // Borrow some handles from CRegWatcher
289     CopyMemory(ahEvents, CRegWatcher::s_ahWatchEvents, WATCHENTRY_MAX * sizeof(HANDLE));
290 
291     ahEvents[WI_DESKTOP_SWITCH] = hSwitchEvent; // Add it
292 
293     // Another message loop
294     for (;;)
295     {
296         // Wait for target signal
297         DWORD dwWait = ::MsgWaitForMultipleObjects(_countof(ahEvents), ahEvents, 0, INFINITE,
298                                                    QS_ALLINPUT);
299         if (dwWait == (WAIT_OBJECT_0 + _countof(ahEvents))) // Is input available?
300         {
301             // Do the events
302             while (::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
303             {
304                 if (msg.message == WM_QUIT)
305                     goto Quit;
306 
307                 ::TranslateMessage(&msg);
308                 ::DispatchMessage(&msg);
309             }
310         }
311         else if (dwWait == (WAIT_OBJECT_0 + WI_DESKTOP_SWITCH)) // Desktop switch?
312         {
313             SetGlobalCompartmentDWORD(GUID_COMPARTMENT_SPEECH_OPENCLOSE, 0);
314             ::ResetEvent(hSwitchEvent);
315         }
316         else // Do the other events
317         {
318             CRegWatcher::OnEvent(dwWait - WAIT_OBJECT_0);
319         }
320     }
321 
322 Quit:
323     ::CloseHandle(hSwitchEvent);
324 
325     return (INT)msg.wParam;
326 }
327 
328 // The main function for Unicode Win32
329 EXTERN_C INT WINAPI
330 _tWinMain(
331     HINSTANCE hInstance,
332     HINSTANCE hPrevInst,
333     LPTSTR lpCmdLine,
334     INT nCmdShow)
335 {
336     UNREFERENCED_PARAMETER(hPrevInst);
337     UNREFERENCED_PARAMETER(nCmdShow);
338 
339     // Parse command line
340     ParseCommandLine(lpCmdLine);
341 
342     if (g_fJustRunKey) // If "/R" option is specified
343     {
344         // Just write registry and exit
345         WriteRegRun();
346         return 1;
347     }
348 
349     // Initialize the application
350     if (!InitApp(hInstance, lpCmdLine))
351         return 0;
352 
353     // The main loop
354     INT ret = DoMainLoop();
355 
356     // Clean up the loader
357     if (g_pLoaderWnd)
358     {
359         delete g_pLoaderWnd;
360         g_pLoaderWnd = NULL;
361     }
362 
363     // Un-initialize app and text framework
364     if (!CLoaderWnd::s_bUninitedSystem)
365     {
366         UninitApp();
367         TF_UninitSystem();
368     }
369 
370     return ret;
371 }
372