xref: /reactos/base/applications/ctfmon/ctfmon.cpp (revision 6bc40d36)
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
ParseCommandLine(_In_ LPCTSTR pszCmdLine)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
WriteRegRun(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
GetGlobalCompartment(_In_ REFGUID guid,_Inout_ ITfCompartment ** ppComp)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
SetGlobalCompartmentDWORD(_In_ REFGUID guid,_In_ DWORD dwValue)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
CheckX64System(_In_ LPTSTR lpCmdLine)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
InitApp(_In_ HINSTANCE hInstance,_In_ LPTSTR lpCmdLine)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     // 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 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     // Do x64 stuffs
240     CheckX64System(lpCmdLine);
241 
242     return TRUE;
243 }
244 
245 VOID
UninitApp(VOID)246 UninitApp(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 
260 static INT
DoMainLoop(VOID)261 DoMainLoop(VOID)
262 {
263     MSG msg;
264 
265     if (g_bOnWow64) // Is the current process on WoW64?
266     {
267         // Just a simple 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     // Open the existing event by the name
277     HANDLE hSwitchEvent = ::OpenEvent(SYNCHRONIZE, FALSE, TEXT("WinSta0_DesktopSwitch"));
278 
279     // The target events to watch
280     HANDLE ahEvents[WATCHENTRY_MAX + 1];
281 
282     // Borrow some handles from CRegWatcher
283     CopyMemory(ahEvents, CRegWatcher::s_ahWatchEvents, WATCHENTRY_MAX * sizeof(HANDLE));
284 
285     ahEvents[WI_DESKTOP_SWITCH] = hSwitchEvent; // Add it
286 
287     // Another message loop
288     for (;;)
289     {
290         // Wait for target signal
291         DWORD dwWait = ::MsgWaitForMultipleObjects(_countof(ahEvents), ahEvents, 0, INFINITE,
292                                                    QS_ALLINPUT);
293         if (dwWait == (WAIT_OBJECT_0 + _countof(ahEvents))) // Is input available?
294         {
295             // Do the events
296             while (::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
297             {
298                 if (msg.message == WM_QUIT)
299                     goto Quit;
300 
301                 ::TranslateMessage(&msg);
302                 ::DispatchMessage(&msg);
303             }
304         }
305         else if (dwWait == (WAIT_OBJECT_0 + WI_DESKTOP_SWITCH)) // Desktop switch?
306         {
307             SetGlobalCompartmentDWORD(GUID_COMPARTMENT_SPEECH_OPENCLOSE, 0);
308             ::ResetEvent(hSwitchEvent);
309         }
310         else // Do the other events
311         {
312             CRegWatcher::OnEvent(dwWait - WAIT_OBJECT_0);
313         }
314     }
315 
316 Quit:
317     ::CloseHandle(hSwitchEvent);
318 
319     return (INT)msg.wParam;
320 }
321 
322 // The main function for Unicode Win32
323 EXTERN_C INT WINAPI
_tWinMain(HINSTANCE hInstance,HINSTANCE hPrevInst,LPTSTR lpCmdLine,INT nCmdShow)324 _tWinMain(
325     HINSTANCE hInstance,
326     HINSTANCE hPrevInst,
327     LPTSTR lpCmdLine,
328     INT nCmdShow)
329 {
330     UNREFERENCED_PARAMETER(hPrevInst);
331     UNREFERENCED_PARAMETER(nCmdShow);
332 
333     // Parse command line
334     ParseCommandLine(lpCmdLine);
335 
336     if (g_fJustRunKey) // If "/R" option is specified
337     {
338         // Just write registry and exit
339         WriteRegRun();
340         return 1;
341     }
342 
343     // Initialize the application
344     if (!InitApp(hInstance, lpCmdLine))
345         return 0;
346 
347     // The main loop
348     INT ret = DoMainLoop();
349 
350     // Clean up the loader
351     if (g_pLoaderWnd)
352     {
353         delete g_pLoaderWnd;
354         g_pLoaderWnd = NULL;
355     }
356 
357     // Un-initialize app and text framework
358     if (!CLoaderWnd::s_bUninitedSystem)
359     {
360         UninitApp();
361         TF_UninitSystem();
362     }
363 
364     return ret;
365 }
366