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: 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
13typedef BOOL (WINAPI *FN_SetProcessShutdownParameters)(DWORD, DWORD);
14FN_SetProcessShutdownParameters g_fnSetProcessShutdownParameters = NULL;
15
16// kernel32!GetSystemWow64DirectoryA
17typedef UINT (WINAPI *FN_GetSystemWow64DirectoryA)(LPSTR, UINT);
18FN_GetSystemWow64DirectoryA g_fnGetSystemWow64DirectoryA = NULL;
19// kernel32!GetSystemWow64DirectoryW
20typedef UINT (WINAPI *FN_GetSystemWow64DirectoryW)(LPWSTR, UINT);
21FN_GetSystemWow64DirectoryW g_fnGetSystemWow64DirectoryW = NULL;
22
23HINSTANCE g_hInst = NULL; // The application instance
24HINSTANCE g_hKernel32 = NULL; // The "kernel32.dll" instance
25UINT g_uACP = CP_ACP; // The active codepage
26BOOL g_fWinLogon = FALSE; // Is it a log-on process?
27HANDLE g_hCicMutex = NULL; // The Cicero mutex
28BOOL g_bOnWow64 = FALSE; // Is the app running on WoW64?
29BOOL g_fNoRunKey = FALSE; // Don't write registry key "Run"?
30BOOL g_fJustRunKey = FALSE; // Just write registry key "Run"?
31DWORD g_dwOsInfo = 0; // The OS version info. See cicGetOSInfo
32CLoaderWnd* g_pLoaderWnd = NULL; // Tipbar loader window
33
34static VOID
35ParseCommandLine(
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
72static VOID
73WriteRegRun(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
97static HRESULT
98GetGlobalCompartment(
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
117static HRESULT
118SetGlobalCompartmentDWORD(
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
138static BOOL
139CheckX64System(
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
187static BOOL
188InitApp(
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 // Create a mutex for Cicero
198 g_hCicMutex = TF_CreateCicLoadMutex(&g_fWinLogon);
199 if (!g_hCicMutex)
200 return FALSE;
201
202 // Write to "Run" registry key for starting up
203 WriteRegRun();
204
205 // Call SetProcessShutdownParameters if possible
206 if (g_dwOsInfo & CIC_OSINFO_NT)
207 {
208 g_hKernel32 = cicGetSystemModuleHandle(TEXT("kernel32.dll"), FALSE);
209 g_fnSetProcessShutdownParameters =
210 (FN_SetProcessShutdownParameters)
211 ::GetProcAddress(g_hKernel32, "SetProcessShutdownParameters");
212 if (g_fnSetProcessShutdownParameters)
213 g_fnSetProcessShutdownParameters(0xF0, SHUTDOWN_NORETRY);
214 }
215
216 // Start text framework
217 TF_InitSystem();
218
219 // Start watching registry if x86/x64 native
220 if (!g_bOnWow64)
221 CRegWatcher::Init();
222
223 // Create Tipbar loader window
224 g_pLoaderWnd = new(cicNoThrow) CLoaderWnd();
225 if (!g_pLoaderWnd || !g_pLoaderWnd->Init())
226 return FALSE;
227
228 if (g_pLoaderWnd->CreateWnd())
229 {
230 // Go to the bottom of the (s)hell
231 ::SetWindowPos(g_pLoaderWnd->m_hWnd, HWND_BOTTOM, 0, 0, 0, 0,
232 SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE);
233 }
234
235 // Display Tipbar Popup if x86/x64 native and necessary
236 if (!g_bOnWow64)
237 GetPopupTipbar(g_pLoaderWnd->m_hWnd, g_fWinLogon);
238
239 // Initialize x64-specific support
240 CheckX64System(lpCmdLine);
241
242 return TRUE;
243}
244
245VOID
246UninitApp(VOID)
247{
248 // Close Tipbar Popup
249 ClosePopupTipbar();
250
251 // Close the mutex
252 ::CloseHandle(g_hCicMutex);
253 g_hCicMutex = NULL;
254
255 // Quit watching registry if x86/x64 native
256 if (!g_bOnWow64)
257 CRegWatcher::Uninit();
258}
259
260static INT
261DoMainLoop(VOID)
262{
263 MSG msg;
264
265 if (g_bOnWow64) // Is the current process on WoW64?
266 {
267 // Simply run a message loop
268 while (::GetMessage(&msg, NULL, 0, 0))
269 {
270 ::TranslateMessage(&msg);
271 ::DispatchMessage(&msg);
272 }
273 return (INT)msg.wParam;
274 }
275
276 // We wait on the CRegWatcher and the WinSta0 desktop-switch events.
277 HANDLE ahEvents[WI_REGEVTS_MAX + 1];
278 DWORD dwEventCount;
279
280 // Get the CRegWatcher handles
281 CopyMemory(ahEvents, CRegWatcher::s_ahWatchEvents, WI_REGEVTS_MAX * sizeof(HANDLE));
282 dwEventCount = WI_REGEVTS_MAX;
283
284 // Open the WinSta0 desktop-switch event and add it
285 HANDLE hSwitchEvent = ::OpenEvent(SYNCHRONIZE, FALSE, TEXT("WinSta0_DesktopSwitch"));
286 if (hSwitchEvent)
287 {
288 ahEvents[WI_DESKTOP_SWITCH] = hSwitchEvent;
289 ++dwEventCount;
290 }
291
292 // Run the event loop
293 for (;;)
294 {
295 DWORD dwWait = ::MsgWaitForMultipleObjects(dwEventCount, ahEvents,
296 FALSE, INFINITE, QS_ALLINPUT);
297 if (dwWait == (WAIT_OBJECT_0 + dwEventCount)) // Is input available?
298 {
299 // Pump available messages
300 while (::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
301 {
302 if (msg.message == WM_QUIT)
303 goto Quit;
304
305 ::TranslateMessage(&msg);
306 ::DispatchMessage(&msg);
307 }
308 }
309 else if (dwWait == (WAIT_OBJECT_0 + WI_DESKTOP_SWITCH)) // Desktop switch?
310 {
311 SetGlobalCompartmentDWORD(GUID_COMPARTMENT_SPEECH_OPENCLOSE, 0);
312 ::ResetEvent(hSwitchEvent);
313 }
314 else if (dwWait < (WAIT_OBJECT_0 + WI_REGEVTS_MAX)) // Do the other events
315 {
316 CRegWatcher::OnEvent(dwWait - WAIT_OBJECT_0);
317 }
318 else if (dwWait == WAIT_TIMEOUT)
319 {
320 // Ignore
321#ifndef NDEBUG
322 OutputDebugStringA("DoMainLoop: WAIT_TIMEOUT\n");
323#endif
324 }
325 else if (dwWait == WAIT_FAILED)
326 {
327 // Something failed, bail out
328#ifndef NDEBUG
329 OutputDebugStringA("DoMainLoop: WAIT_FAILED, exiting\n");
330#endif
331 break;
332 }
333 }
334
335Quit:
336 if (hSwitchEvent)
337 ::CloseHandle(hSwitchEvent);
338
339 return (INT)msg.wParam;
340}
341
342// The main function for Unicode Win32
343EXTERN_C INT WINAPI
344_tWinMain(
345 HINSTANCE hInstance,
346 HINSTANCE hPrevInst,
347 LPTSTR lpCmdLine,
348 INT nCmdShow)
349{
350 UNREFERENCED_PARAMETER(hPrevInst);
351 UNREFERENCED_PARAMETER(nCmdShow);
352
353 // Parse command line
354 ParseCommandLine(lpCmdLine);
355
356 if (g_fJustRunKey) // If "/R" option is specified
357 {
358 // Just write registry and exit
359 WriteRegRun();
360 return 1;
361 }
362
363 // Initialize the application
364 if (!InitApp(hInstance, lpCmdLine))
365 return 0;
366
367 // The main loop
368 INT ret = DoMainLoop();
369
370 // Clean up the loader
371 if (g_pLoaderWnd)
372 {
373 delete g_pLoaderWnd;
374 g_pLoaderWnd = NULL;
375 }
376
377 // Un-initialize app and text framework
378 if (!CLoaderWnd::s_bUninitedSystem)
379 {
380 UninitApp();
381 TF_UninitSystem();
382 }
383
384 return ret;
385}