1 /*
2  * PROJECT:         ReactOS Utility Manager Resources DLL (UManDlg.dll)
3  * LICENSE:         GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
4  * PURPOSE:         Main DLL code file
5  * COPYRIGHT:       Copyright 2019-2020 Bișoc George (fraizeraust99 at gmail dot com)
6  *                  Copyright 2019 Hermes Belusca-Maito
7  */
8 
9 /* INCLUDES *******************************************************************/
10 
11 #include "umandlg.h"
12 
13 /* GLOBALS ********************************************************************/
14 
15 UTILMAN_GLOBALS Globals;
16 
17 /* DECLARATIONS ***************************************************************/
18 
19 UTILMAN_STATE EntriesList[] =
20 {
21     {L"magnify.exe", IDS_MAGNIFIER, L"", FALSE},
22     {L"osk.exe", IDS_OSK, L"", FALSE}
23 };
24 
25 /* FUNCTIONS ******************************************************************/
26 
27 /**
28  * @InitUtilsList
29  *
30  * Initializes the list of accessibility utilities.
31  *
32  * @param[in]   bInitGui
33  *     Whether we are initializing the UI list (TRUE) or the internal array (FALSE).
34  *
35  * @return
36  *     Nothing.
37  */
38 VOID InitUtilsList(BOOL bInitGui)
39 {
40     UINT i;
41 
42     if (!bInitGui)
43     {
44         // TODO: Load the list dynamically from the registry key
45         // hklm\software\microsoft\windows nt\currentversion\accessibility
46 
47         /* Initialize the resource utility strings only once */
48         for (i = 0; i < _countof(EntriesList); ++i)
49         {
50             LoadStringW(Globals.hInstance, EntriesList[i].uNameId,
51                         EntriesList[i].szResource, _countof(EntriesList[i].szResource));
52 
53             EntriesList[i].bState = FALSE;
54         }
55     }
56     else
57     {
58         INT iItem;
59         BOOL bIsRunning;
60         WCHAR szFormat[MAX_BUFFER];
61 
62         /* Reset the listbox */
63         SendMessageW(Globals.hListDlg, LB_RESETCONTENT, 0, 0);
64 
65         /* Add the utilities in the listbox */
66         for (i = 0; i < _countof(EntriesList); ++i)
67         {
68             bIsRunning = IsProcessRunning(EntriesList[i].lpszProgram);
69             EntriesList[i].bState = bIsRunning;
70 
71             /* Load the string and append the utility's name to the format */
72             StringCchPrintfW(szFormat, _countof(szFormat),
73                              (bIsRunning ? Globals.szRunning : Globals.szNotRunning),
74                              EntriesList[i].szResource);
75 
76             /* Add the item in the listbox */
77             iItem = (INT)SendMessageW(Globals.hListDlg, LB_ADDSTRING, 0, (LPARAM)szFormat);
78             if (iItem != LB_ERR)
79                 SendMessageW(Globals.hListDlg, LB_SETITEMDATA, iItem, (LPARAM)&EntriesList[i]);
80         }
81     }
82 }
83 
84 /**
85  * @DlgInitHandler
86  *
87  * Function which processes several operations for WM_INITDIALOG.
88  *
89  * @param[in]   hDlg
90  *     The handle object of the dialog.
91  *
92  * @return
93  *     TRUE to inform the system that WM_INITDIALOG has been processed and
94  *     that it should set the keyboard focus to the control.
95  *
96  */
97 BOOL DlgInitHandler(IN HWND hDlg)
98 {
99     INT PosX, PosY;
100     RECT rc;
101     WCHAR szAboutDlg[MAX_BUFFER];
102     WCHAR szAppPath[MAX_BUFFER];
103     HMENU hSysMenu;
104 
105     /* Save the dialog handle */
106     Globals.hMainDlg = hDlg;
107 
108     /* Center the dialog on the screen */
109     GetWindowRect(hDlg, &rc);
110     PosX = (GetSystemMetrics(SM_CXSCREEN) - rc.right) / 2;
111     PosY = (GetSystemMetrics(SM_CYSCREEN) - rc.bottom) / 2;
112     SetWindowPos(hDlg, 0, PosX, PosY, 0, 0, SWP_NOZORDER | SWP_NOSIZE);
113 
114     /* Extract the icon resource from the executable process */
115     GetModuleFileNameW(NULL, szAppPath, _countof(szAppPath));
116     Globals.hIcon = ExtractIconW(Globals.hInstance, szAppPath, 0);
117 
118     /* Set the icon within the dialog's title bar */
119     if (Globals.hIcon)
120     {
121         SendMessageW(hDlg, WM_SETICON, ICON_SMALL, (LPARAM)Globals.hIcon);
122     }
123 
124     /* Retrieve the system menu and append the "About" menu item onto it */
125     hSysMenu = GetSystemMenu(hDlg, FALSE);
126     if (hSysMenu != NULL)
127     {
128         if (LoadStringW(Globals.hInstance, IDM_ABOUT, szAboutDlg, _countof(szAboutDlg)))
129         {
130             AppendMenuW(hSysMenu, MF_SEPARATOR, 0, NULL);
131             AppendMenuW(hSysMenu, MF_STRING, IDM_ABOUT, szAboutDlg);
132         }
133     }
134 
135     /* Get the dialog items, specifically the dialog list box, the Start and Stop buttons */
136     Globals.hListDlg = GetDlgItem(hDlg, IDC_LISTBOX);
137     Globals.hDlgCtlStart = GetDlgItem(hDlg, IDC_START);
138     Globals.hDlgCtlStop = GetDlgItem(hDlg, IDC_STOP);
139 
140     /* Initialize the GUI listbox */
141     InitUtilsList(TRUE);
142 
143     /* Set the selection to the first item */
144     Globals.iSelectedIndex = 0;
145 
146     /* Refresh the list */
147     ListBoxRefreshContents();
148 
149     /* Create a timer, we'll use it to control the state of our items in the listbox */
150     Globals.iTimer = SetTimer(hDlg, 0, 400, NULL);
151 
152     return TRUE;
153 }
154 
155 /**
156  * @GroupBoxUpdateTitle
157  *
158  * Updates the title of the groupbox.
159  *
160  * @return
161  *     Nothing.
162  *
163  */
164 VOID GroupBoxUpdateTitle(VOID)
165 {
166     WCHAR szFormat[MAX_BUFFER];
167 
168     /* Format the string with the utility's name and set it to the listbox's title */
169     StringCchPrintfW(szFormat, _countof(szFormat), Globals.szGrpBoxTitle, EntriesList[Globals.iSelectedIndex].szResource);
170     SetWindowTextW(GetDlgItem(Globals.hMainDlg, IDC_GROUPBOX), szFormat);
171 }
172 
173 /**
174  * @UpdateUtilityState
175  *
176  * Checks the state of the given accessibility tool.
177  *
178  * @param[in]   bUtilState
179  *     State condition (boolean TRUE: started / FALSE: stopped).
180  *
181  * @return
182  *     Nothing.
183  *
184  */
185 VOID UpdateUtilityState(IN BOOL bUtilState)
186 {
187     Button_Enable(Globals.hDlgCtlStart, !bUtilState);
188     Button_Enable(Globals.hDlgCtlStop,   bUtilState);
189 
190     /* Update the groupbox's title based on the selected utility item */
191     GroupBoxUpdateTitle();
192 }
193 
194 /**
195  * @ListBoxRefreshContents
196  *
197  * Handle the tasks on a periodic cycle. This function handles WM_TIMER message.
198  *
199  * @return
200  *     Returns 0 to inform the system that WM_TIMER has been processed.
201  *
202  */
203 INT ListBoxRefreshContents(VOID)
204 {
205     UINT i;
206     INT iItem;
207     BOOL bIsRunning;
208     WCHAR szFormat[MAX_BUFFER];
209 
210     /* Disable listbox redraw */
211     SendMessageW(Globals.hListDlg, WM_SETREDRAW, FALSE, 0);
212 
213     for (i = 0; i < _countof(EntriesList); ++i)
214     {
215         /* Check the utility's state */
216         bIsRunning = IsProcessRunning(EntriesList[i].lpszProgram);
217         if (bIsRunning != EntriesList[i].bState)
218         {
219             /* The utility's state has changed, save it */
220             EntriesList[i].bState = bIsRunning;
221 
222             /* Update the corresponding item in the listbox */
223             StringCchPrintfW(szFormat, _countof(szFormat),
224                              (bIsRunning ? Globals.szRunning : Globals.szNotRunning),
225                              EntriesList[i].szResource);
226 
227             SendMessageW(Globals.hListDlg, LB_DELETESTRING, (LPARAM)i, 0);
228             iItem = SendMessageW(Globals.hListDlg, LB_INSERTSTRING, (LPARAM)i, (LPARAM)szFormat);
229             if (iItem != LB_ERR)
230                 SendMessageW(Globals.hListDlg, LB_SETITEMDATA, iItem, (LPARAM)&EntriesList[i]);
231         }
232     }
233 
234     /* Re-enable listbox redraw */
235     SendMessageW(Globals.hListDlg, WM_SETREDRAW, TRUE, 0);
236 
237     /*
238      * Check the previously selected item. This will help us determine what
239      * item has been selected and set its focus selection back. Furthermore, check
240      * the state of each accessibility tool and enable/disable the buttons.
241      */
242     SendMessageW(Globals.hListDlg, LB_SETCURSEL, (WPARAM)Globals.iSelectedIndex, 0);
243     UpdateUtilityState(EntriesList[Globals.iSelectedIndex].bState);
244 
245     return 0;
246 }
247 
248 /**
249  * @DlgProc
250  *
251  * Main dialog application procedure function.
252  *
253  * @param[in]   hDlg
254  *     The handle object of the dialog.
255  *
256  * @param[in]   Msg
257  *     Message events (in unsigned int).
258  *
259  * @param[in]   wParam
260  *     Message parameter (in UINT_PTR).
261  *
262  * @param[in]   lParam
263  *     Message paramater (in LONG_PTR).
264  *
265  * @return
266  *     Returns 0 to inform the system that the procedure has been handled.
267  *
268  */
269 INT_PTR APIENTRY DlgProc(
270     IN HWND hDlg,
271     IN UINT Msg,
272     IN WPARAM wParam,
273     IN LPARAM lParam)
274 {
275     switch (Msg)
276     {
277         case WM_INITDIALOG:
278             DlgInitHandler(hDlg);
279             return TRUE;
280 
281         case WM_CLOSE:
282             KillTimer(hDlg, Globals.iTimer);
283             DestroyIcon(Globals.hIcon);
284             EndDialog(hDlg, FALSE);
285             break;
286 
287         case WM_COMMAND:
288         {
289             switch (LOWORD(wParam))
290             {
291                 case IDC_OK:
292                 case IDC_CANCEL:
293                     EndDialog(hDlg, FALSE);
294                     break;
295 
296                 case IDC_LISTBOX:
297                 {
298                     switch (HIWORD(wParam))
299                     {
300                         case LBN_SELCHANGE:
301                         {
302                             /* Retrieve the index of the current selected item */
303                             INT iIndex = SendMessageW(Globals.hListDlg, LB_GETCURSEL, 0, 0);
304                             if ((iIndex == LB_ERR) || (iIndex >= _countof(EntriesList)))
305                                 break;
306 
307                             /* Assign the selected index and check the utility's state */
308                             Globals.iSelectedIndex = iIndex;
309                             UpdateUtilityState(EntriesList[Globals.iSelectedIndex].bState);
310                             break;
311                         }
312                         break;
313                     }
314                     break;
315                 }
316 
317                 case IDC_START:
318                     LaunchProcess(EntriesList[Globals.iSelectedIndex].lpszProgram);
319                     break;
320 
321                 case IDC_STOP:
322                     CloseProcess(EntriesList[Globals.iSelectedIndex].lpszProgram);
323                     break;
324 
325                 default:
326                     break;
327             }
328             break;
329         }
330 
331         case WM_TIMER:
332             ListBoxRefreshContents();
333             return 0;
334 
335         case WM_SYSCOMMAND:
336         {
337             switch (LOWORD(wParam))
338             {
339                 case IDM_ABOUT:
340                     ShowAboutDlg(hDlg);
341                     break;
342             }
343             break;
344         }
345     }
346 
347     return 0;
348 }
349 
350 /**
351  * @UManStartDlg
352  *
353  * Executes the dialog initialization mechanism and starts Utility Manager.
354  * The function is exported for use by the main process.
355  *
356  * @return
357  *     Returns TRUE when the operation has succeeded, FALSE otherwise.
358  *
359  */
360 BOOL WINAPI UManStartDlg(VOID)
361 {
362     HANDLE hMutex;
363     DWORD dwError;
364     INITCOMMONCONTROLSEX iccex;
365 
366     /* Create a mutant object for the program. */
367     hMutex = CreateMutexW(NULL, FALSE, L"Utilman");
368     if (hMutex)
369     {
370         /* Check if there's already a mutex for the program */
371         dwError = GetLastError();
372         if (dwError == ERROR_ALREADY_EXISTS)
373         {
374             /*
375                 The program's instance is already here. That means
376                 the program is running and we should not set a new instance
377                 and mutex object.
378             */
379             CloseHandle(hMutex);
380             return FALSE;
381         }
382     }
383 
384     /* Load the common controls for the program */
385     iccex.dwSize = sizeof(INITCOMMONCONTROLSEX);
386     iccex.dwICC = ICC_STANDARD_CLASSES | ICC_WIN95_CLASSES;
387     InitCommonControlsEx(&iccex);
388 
389     LoadStringW(Globals.hInstance, IDS_RUNNING,
390                 Globals.szRunning, _countof(Globals.szRunning));
391     LoadStringW(Globals.hInstance, IDS_NOTRUNNING,
392                 Globals.szNotRunning, _countof(Globals.szNotRunning));
393     LoadStringW(Globals.hInstance, IDS_GROUPBOX_OPTIONS_TITLE,
394                 Globals.szGrpBoxTitle, _countof(Globals.szGrpBoxTitle));
395 
396     /* Initialize the list of accessibility utilities */
397     InitUtilsList(FALSE);
398 
399     /* Create the dialog box of the program */
400     DialogBoxW(Globals.hInstance,
401                MAKEINTRESOURCEW(IDD_MAIN_DIALOG),
402                GetDesktopWindow(),
403                DlgProc);
404 
405     /* Delete the mutex */
406     if (hMutex)
407     {
408         CloseHandle(hMutex);
409     }
410 
411     return TRUE;
412 }
413 
414 /**
415  * @DllMain
416  *
417  * Core routine of the Utility Manager's library.
418  *
419  * @param[in]   hDllInstance
420  *      The entry point instance of the library.
421  *
422  * @param[in]   fdwReason
423  *      The reason argument to indicate the motive DllMain
424  *      is being called.
425  *
426  * @param[in]   lpvReserved
427  *      Reserved.
428  *
429  * @return
430  *     Returns TRUE when main call initialization has succeeded, FALSE
431  *     otherwise.
432  *
433  */
434 BOOL WINAPI DllMain(IN HINSTANCE hDllInstance,
435                     IN DWORD fdwReason,
436                     IN LPVOID lpvReserved)
437 {
438     switch (fdwReason)
439     {
440         case DLL_PROCESS_ATTACH:
441         {
442             /* We don't care for DLL_THREAD_ATTACH and DLL_THREAD_DETACH notifications */
443             DisableThreadLibraryCalls(hDllInstance);
444 
445             /* Initialize the globals */
446             ZeroMemory(&Globals, sizeof(Globals));
447             Globals.hInstance = hDllInstance;
448             break;
449         }
450 
451         case DLL_PROCESS_DETACH:
452             break;
453     }
454 
455     return TRUE;
456 }
457