1 /*
2  *  ReactOS Win32 Applications
3  *  Copyright (C) 2007 ReactOS Team
4  *
5  *  This program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2 of the License, or
8  *  (at your option) any later version.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License along
16  *  with this program; if not, write to the Free Software Foundation, Inc.,
17  *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18  */
19 /*
20  * PROJECT:         ReactOS Event Log Viewer
21  * LICENSE:         GPL - See COPYING in the top level directory
22  * FILE:            base/applications/mscutils/eventvwr/eventvwr.c
23  * PURPOSE:         Event Log Viewer main file
24  * PROGRAMMERS:     Marc Piulachs (marc.piulachs at codexchange [dot] net)
25  *                  Eric Kohl
26  *                  Hermes Belusca-Maito
27  */
28 
29 #include "eventvwr.h"
30 #include "evtdetctl.h"
31 
32 #include <sddl.h> // For ConvertSidToStringSidW
33 #include <shellapi.h>
34 #include <shlwapi.h>
35 
36 #include <pseh/pseh2.h>
37 
38 // #include "resource.h"
39 
40 #define LVM_PROGRESS    (WM_APP + 1)    // Used by the subclassed ListView
41 
42 static const LPCWSTR EVENTVWR_WNDCLASS = L"EVENTVWR"; /* The main window class name */
43 static const LPCWSTR EVENTLOG_BASE_KEY = L"SYSTEM\\CurrentControlSet\\Services\\EventLog\\";
44 static const LPCWSTR EVNTVWR_PARAM_KEY = L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Event Viewer";
45 
46 /* The 3 system logs that should always exist in the user's system */
47 static const LPCWSTR SystemLogs[] =
48 {
49     L"Application",
50     L"Security",
51     L"System"
52 };
53 
54 /* MessageFile message buffer size */
55 #define EVENT_MESSAGE_EVENTTEXT_BUFFER  1024*10                             // NOTE: Used by evtdetctl.c
56 #define EVENT_MESSAGE_FILE_BUFFER       1024*10
57 #define EVENT_DLL_SEPARATOR             L";"
58 #define EVENT_CATEGORY_MESSAGE_FILE     L"CategoryMessageFile"
59 #define EVENT_MESSAGE_FILE              L"EventMessageFile"
60 #define EVENT_PARAMETER_MESSAGE_FILE    L"ParameterMessageFile"
61 
62 #define MAX_LOADSTRING 255
63 
64 #define SPLIT_WIDTH 4
65 
66 /* Globals */
67 HINSTANCE hInst;                            /* Current instance */
68 WCHAR szTitle[MAX_LOADSTRING];              /* The title bar text */
69 WCHAR szTitleTemplate[MAX_LOADSTRING];      /* The logged-on title bar text */
70 WCHAR szStatusBarTemplate[MAX_LOADSTRING];  /* The status bar text */
71 WCHAR szLoadingWait[MAX_LOADSTRING];        /* The "Loading, please wait..." text */
72 WCHAR szEmptyList[MAX_LOADSTRING];          /* The "There are no items to show in this view" text */
73 WCHAR szSaveFilter[MAX_LOADSTRING];         /* Filter Mask for the save Dialog */
74 
75 INT  nVSplitPos;                            /* Vertical splitter (1) position */
76 INT  nHSplitPos;                            /* Horizontal splitter (2) position */
77 BYTE bSplit = 0;                            /* Splitter state:
78                                              * 0: No splitting;
79                                              * 1: Vertical splitting;
80                                              * 2: Horizontal splitting.
81                                              */
82 
83 HWND hwndMainWindow = NULL;                 /* Main window */
84 HWND hwndTreeView;                          /* TreeView control */
85 HWND hwndListView;                          /* ListView control */          // NOTE: Used by evtdetctl.c
86 HWND hwndEventDetails;                      /* Event details pane */
87 HWND hwndStatus;                            /* Status bar */
88 HWND hwndStatusProgress;                    /* Progress bar in the status bar */
89 HMENU hMainMenu;                            /* The application's main menu */
90 
91 HTREEITEM htiSystemLogs = NULL, htiAppLogs = NULL, htiUserLogs = NULL;
92 
93 LPWSTR lpComputerName = NULL;       /* NULL: local user computer (default) */
94 LPWSTR lpszzUserLogsToLoad = NULL;  /* The list of user logs to load at startup (multi-string) */
95 SIZE_T cbUserLogsSize = 0;
96 
97 HKEY hkMachine = NULL; // Registry handle to the HKEY_LOCAL_MACHINE key of the remote computer registry.
98 
99 /* Global event records cache for the current active event log filter */
100 DWORD g_TotalRecords = 0;
101 PEVENTLOGRECORD* g_RecordPtrs = NULL;
102 
103 /* Lists of event logs and event log filters */
104 LIST_ENTRY EventLogList;
105 LIST_ENTRY EventLogFilterList;
106 PEVENTLOGFILTER ActiveFilter = NULL;
107 
108 HANDLE hEnumEventsThread = NULL;
109 HANDLE hStopEnumEvent    = NULL;
110 
111 /*
112  * Setting EnumFilter to a valid pointer and raising the hStartEnumEvent event
113  * triggers the event-enumerator thread to perform a new enumeration.
114  */
115 PEVENTLOGFILTER EnumFilter = NULL;
116 HANDLE hStartStopEnumEvent = NULL;  // End-of-application event
117 HANDLE hStartEnumEvent     = NULL;  // Command event
118 
119 /* Default Open/Save-As dialog box */
120 OPENFILENAMEW sfn;
121 
122 
123 /* Event Viewer Application Settings */
124 typedef struct _SETTINGS
125 {
126     BOOL bShowDetailsPane;      /* Show (TRUE) or Hide (FALSE) the events details pane */
127     BOOL bShowGrid;             /* Show (TRUE) or Hide (FALSE) the events view grid */
128     BOOL bSaveSettings;         /* Save (TRUE) or do not save (FALSE) current settings on exit */
129     BOOL bNewestEventsFirst;    /* Sort the displayed events the newest ones first (TRUE) or last (FALSE) */
130     INT  nVSplitPos;            /* Vertical splitter position */
131     INT  nHSplitPos;            /* Horizontal splitter position */
132     WINDOWPLACEMENT wpPos;
133 } SETTINGS, *PSETTINGS;
134 
135 SETTINGS Settings;
136 
137 
138 /* Forward declarations of functions included in this code module */
139 
140 static DWORD WINAPI
141 StartStopEnumEventsThread(IN LPVOID lpParameter);
142 
143 VOID OpenUserEventLogFile(IN LPCWSTR lpszFileName);
144 
145 VOID BuildLogListAndFilterList(IN LPCWSTR lpComputerName);
146 VOID FreeLogList(VOID);
147 VOID FreeLogFilterList(VOID);
148 
149 ATOM MyRegisterClass(HINSTANCE);
150 BOOL InitInstance(HINSTANCE);
151 LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
152 INT_PTR EventLogProperties(HINSTANCE, HWND, PEVENTLOGFILTER);
153 INT_PTR CALLBACK EventDetails(HWND, UINT, WPARAM, LPARAM);
154 
155 
156 /* MAIN FUNCTIONS *************************************************************/
157 
158 VOID
159 ShowWin32Error(IN DWORD dwError)
160 {
161     LPWSTR lpMessageBuffer;
162 
163     if (dwError == ERROR_SUCCESS)
164         return;
165 
166     if (!FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER |
167                         FORMAT_MESSAGE_FROM_SYSTEM |
168                         FORMAT_MESSAGE_IGNORE_INSERTS,
169                         NULL,
170                         dwError,
171                         LANG_USER_DEFAULT,
172                         (LPWSTR)&lpMessageBuffer,
173                         0, NULL))
174     {
175         return;
176     }
177 
178     MessageBoxW(hwndMainWindow, lpMessageBuffer, szTitle, MB_OK | MB_ICONERROR);
179     LocalFree(lpMessageBuffer);
180 }
181 
182 VOID
183 DisplayUsage(VOID)
184 {
185     LPWSTR lpBuffer;
186     LPCWSTR lpUsage;
187     INT iUsageLen = LoadStringW(hInst, IDS_USAGE, (LPWSTR)&lpUsage, 0);
188 
189     if (iUsageLen == 0)
190         return;
191 
192     lpBuffer = HeapAlloc(GetProcessHeap(), 0, (iUsageLen + 1) * sizeof(WCHAR));
193     if (!lpBuffer)
194         return;
195 
196     StringCchCopyNW(lpBuffer, iUsageLen + 1, lpUsage, iUsageLen);
197     MessageBoxW(NULL, lpBuffer, szTitle, MB_OK | MB_ICONINFORMATION);
198 
199     HeapFree(GetProcessHeap(), 0, lpBuffer);
200 }
201 
202 BOOL
203 ProcessCmdLine(IN LPWSTR lpCmdLine)
204 {
205     BOOL Success = FALSE;
206     INT i, argc;
207     LPWSTR* argv;
208 
209     /* Skip any leading whitespace */
210     if (lpCmdLine)
211     {
212         while (iswspace(*lpCmdLine))
213             ++lpCmdLine;
214     }
215 
216     /* No command line means no processing needed */
217     if (!lpCmdLine || !*lpCmdLine)
218         return TRUE;
219 
220     /* Build the arguments vector */
221     argv = CommandLineToArgvW(lpCmdLine, &argc);
222     if (!argv)
223         return FALSE;
224 
225     /* Parse the command line for options (skip the program name) */
226     for (i = 1; i < argc; ++i)
227     {
228         /* Check for new options */
229         if (argv[i][0] == L'-' || argv[i][0] == L'/')
230         {
231             if (argv[i][1] == L'?' && argv[i][2] == 0)
232             {
233                 /* Display help */
234                 DisplayUsage();
235                 goto Quit;
236             }
237             else
238             if (argv[i][2] == L':')
239             {
240                 switch (towupper(argv[i][1]))
241                 {
242                     case L'L':
243                     {
244                         LPWSTR lpNewBuffer;
245                         LPWSTR lpFileName = argv[i] + 3;
246                         SIZE_T cbFileName;
247 
248                         /* Check for a quoted file name */
249                         if (*lpFileName == L'\"')
250                         {
251                             /* Skip this quote, and the last one too if any */
252                             ++lpFileName;
253                             cbFileName = wcslen(lpFileName);
254                             if (cbFileName > 0 && lpFileName[cbFileName - 1] == L'\"')
255                                 lpFileName[cbFileName - 1] = UNICODE_NULL;
256                         }
257 
258                         /* Skip this one if we do not actually have a file name */
259                         if (!*lpFileName)
260                             continue;
261 
262                         cbFileName = (wcslen(lpFileName) + 1) * sizeof(WCHAR);
263 
264                         /* Reallocate the list of user logs to load */
265                         if (lpszzUserLogsToLoad)
266                         {
267                             lpNewBuffer = HeapReAlloc(GetProcessHeap(),
268                                                       HEAP_ZERO_MEMORY,
269                                                       lpszzUserLogsToLoad,
270                                                       /* Count the multi-string NULL-terminator */
271                                                       cbUserLogsSize + cbFileName + sizeof(WCHAR));
272                         }
273                         else
274                         {
275                             cbUserLogsSize = 0;
276                             lpNewBuffer = HeapAlloc(GetProcessHeap(),
277                                                     HEAP_ZERO_MEMORY,
278                                                     /* Count the multi-string NULL-terminator */
279                                                     cbUserLogsSize + cbFileName + sizeof(WCHAR));
280                         }
281 
282                         if (!lpNewBuffer)
283                         {
284                             ShowWin32Error(ERROR_NOT_ENOUGH_MEMORY);
285                             goto Quit;
286                         }
287 
288                         lpszzUserLogsToLoad = lpNewBuffer;
289                         lpNewBuffer = (LPWSTR)((ULONG_PTR)lpNewBuffer + cbUserLogsSize);
290                         cbUserLogsSize += cbFileName;
291 
292                         /* Save the file name */
293                         StringCbCopyW(lpNewBuffer, cbFileName, lpFileName);
294 
295                         continue;
296                     }
297 
298                     default:
299                         break;
300                 }
301             }
302 
303             /* Unknown argument: display help and bail out */
304             DisplayUsage();
305             goto Quit;
306         }
307         else
308         {
309             /*
310              * An argument that does not start with the switch character.
311              * If this is the first argument then this corresponds to the
312              * optional computer name. Otherwise this is a wrong argument.
313              */
314             if (i == 1)
315             {
316                 /* Store the computer name */
317                 LPWSTR lpTemp = argv[i];
318                 SIZE_T cbLength;
319 
320                 /* Strip any leading backslashes */
321                 while (*lpTemp == L'\\')
322                     ++lpTemp;
323 
324                 cbLength = (wcslen(lpTemp) + 1) * sizeof(WCHAR);
325                 lpComputerName = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, cbLength);
326                 if (lpComputerName)
327                 {
328                     StringCbCopyW(lpComputerName, cbLength, lpTemp);
329                 }
330                 /* else, fall back to local computer */
331             }
332             else
333             {
334                 /* Invalid syntax: display help and bail out */
335                 DisplayUsage();
336                 goto Quit;
337             }
338         }
339     }
340 
341     Success = TRUE;
342 
343 Quit:
344     /* In case of failure, free anything we have allocated */
345     if (!Success)
346     {
347         if (lpszzUserLogsToLoad)
348         {
349             cbUserLogsSize = 0;
350             HeapFree(GetProcessHeap(), 0, lpszzUserLogsToLoad);
351             lpszzUserLogsToLoad = NULL;
352         }
353         if (lpComputerName)
354         {
355             HeapFree(GetProcessHeap(), 0, lpComputerName);
356             lpComputerName = NULL;
357         }
358     }
359 
360     /* Free the arguments vector and exit */
361     LocalFree(argv);
362     return Success;
363 }
364 
365 BOOL
366 LoadSettings(int nDefCmdShow)
367 {
368     HKEY hKeyEventVwr;
369     LONG Result;
370     DWORD dwSize;
371     DWORD dwType;
372     DWORD Value;
373     UNICODE_STRING ValueU;
374     WCHAR buffer[100];
375 
376     /* Load the default values */
377     Settings.bShowDetailsPane = TRUE;
378     Settings.bShowGrid = FALSE;
379     Settings.bSaveSettings = TRUE;
380     Settings.bNewestEventsFirst = TRUE;
381     Settings.nVSplitPos = 250; /* Splitter default positions */
382     Settings.nHSplitPos = 250;
383     ZeroMemory(&Settings.wpPos, sizeof(Settings.wpPos));
384     Settings.wpPos.length = sizeof(Settings.wpPos);
385     Settings.wpPos.rcNormalPosition.left = CW_USEDEFAULT;
386     Settings.wpPos.rcNormalPosition.top  = CW_USEDEFAULT;
387     Settings.wpPos.rcNormalPosition.right  = CW_USEDEFAULT;
388     Settings.wpPos.rcNormalPosition.bottom = CW_USEDEFAULT;
389 
390     /* Try to create/open the Event Viewer user key */
391     if (RegCreateKeyExW(HKEY_CURRENT_USER,
392                         EVNTVWR_PARAM_KEY,
393                         0,
394                         NULL,
395                         REG_OPTION_NON_VOLATILE,
396                         KEY_READ | KEY_WRITE,
397                         NULL,
398                         &hKeyEventVwr,
399                         NULL) != ERROR_SUCCESS)
400     {
401         return FALSE;
402     }
403 
404     // Result = RegQueryValueExW(hKeyEventVwr, L"Filter", NULL, &dwType, (LPBYTE)&szFilter, &dwSize); // REG_SZ
405     // Result = RegQueryValueExW(hKeyEventVwr, L"Find", NULL, &dwType, (LPBYTE)&szFind, &dwSize); // REG_SZ
406     // Result = RegQueryValueExW(hKeyEventVwr, L"Module", NULL, &dwType, (LPBYTE)&szModule, &dwSize); // REG_SZ
407 
408     dwSize = sizeof(Value);
409     Result = RegQueryValueExW(hKeyEventVwr, L"DetailsPane", NULL, &dwType, (LPBYTE)&Value, &dwSize);
410     if ((Result == ERROR_SUCCESS) && (dwType == REG_SZ || dwType == REG_DWORD))
411     {
412         if (dwType == REG_SZ)
413         {
414             ValueU.Buffer = (PWSTR)&Value;
415             ValueU.Length = ValueU.MaximumLength = dwSize;
416             RtlUnicodeStringToInteger(&ValueU, 10, &Value);
417         }
418         Settings.bShowDetailsPane = !!Value;
419     }
420 
421     dwSize = sizeof(Value);
422     Result = RegQueryValueExW(hKeyEventVwr, L"ShowGrid", NULL, &dwType, (LPBYTE)&Value, &dwSize);
423     if ((Result == ERROR_SUCCESS) && (dwType == REG_SZ || dwType == REG_DWORD))
424     {
425         if (dwType == REG_SZ)
426         {
427             ValueU.Buffer = (PWSTR)&Value;
428             ValueU.Length = ValueU.MaximumLength = dwSize;
429             RtlUnicodeStringToInteger(&ValueU, 10, &Value);
430         }
431         Settings.bShowGrid = !!Value;
432     }
433 
434     dwSize = sizeof(Value);
435     Result = RegQueryValueExW(hKeyEventVwr, L"SortOrder", NULL, &dwType, (LPBYTE)&Value, &dwSize);
436     if ((Result == ERROR_SUCCESS) && (dwType == REG_SZ || dwType == REG_DWORD))
437     {
438         if (dwType == REG_SZ)
439         {
440             ValueU.Buffer = (PWSTR)&Value;
441             ValueU.Length = ValueU.MaximumLength = dwSize;
442             RtlUnicodeStringToInteger(&ValueU, 10, &Value);
443         }
444         Settings.bNewestEventsFirst = !!Value;
445     }
446 
447     /* Retrieve the splitter positions */
448     dwSize = sizeof(Value);
449     Result = RegQueryValueExW(hKeyEventVwr, L"VSplitPos", NULL, &dwType, (LPBYTE)&Value, &dwSize);
450     if ((Result == ERROR_SUCCESS) && (dwType == REG_SZ || dwType == REG_DWORD))
451     {
452         if (dwType == REG_SZ)
453         {
454             ValueU.Buffer = (PWSTR)&Value;
455             ValueU.Length = ValueU.MaximumLength = dwSize;
456             RtlUnicodeStringToInteger(&ValueU, 10, &Value);
457         }
458         Settings.nVSplitPos = Value;
459     }
460 
461     dwSize = sizeof(Value);
462     Result = RegQueryValueExW(hKeyEventVwr, L"HSplitPos", NULL, &dwType, (LPBYTE)&Value, &dwSize);
463     if ((Result == ERROR_SUCCESS) && (dwType == REG_SZ || dwType == REG_DWORD))
464     {
465         if (dwType == REG_SZ)
466         {
467             ValueU.Buffer = (PWSTR)&Value;
468             ValueU.Length = ValueU.MaximumLength = dwSize;
469             RtlUnicodeStringToInteger(&ValueU, 10, &Value);
470         }
471         Settings.nHSplitPos = Value;
472     }
473 
474     /* Retrieve the geometry of the main window */
475     dwSize = sizeof(buffer);
476     Result = RegQueryValueExW(hKeyEventVwr, L"Window", NULL, &dwType, (LPBYTE)buffer, &dwSize);
477     if ((Result != ERROR_SUCCESS) || (dwType != REG_SZ))
478         buffer[0] = UNICODE_NULL;
479 
480     if (swscanf(buffer, L"%d %d %d %d %d",
481                 &Settings.wpPos.rcNormalPosition.left,
482                 &Settings.wpPos.rcNormalPosition.top,
483                 &Settings.wpPos.rcNormalPosition.right,
484                 &Settings.wpPos.rcNormalPosition.bottom,
485                 &Settings.wpPos.showCmd) != 5)
486     {
487         /* Parsing failed, use defaults */
488         Settings.wpPos.rcNormalPosition.left = CW_USEDEFAULT;
489         Settings.wpPos.rcNormalPosition.top  = CW_USEDEFAULT;
490         Settings.wpPos.rcNormalPosition.right  = CW_USEDEFAULT;
491         Settings.wpPos.rcNormalPosition.bottom = CW_USEDEFAULT;
492         Settings.wpPos.showCmd = nDefCmdShow; // SW_SHOWNORMAL;
493     }
494 
495     dwSize = sizeof(Value);
496     Result = RegQueryValueExW(hKeyEventVwr, L"SaveSettings", NULL, &dwType, (LPBYTE)&Value, &dwSize);
497     if ((Result == ERROR_SUCCESS) && (dwType == REG_SZ || dwType == REG_DWORD))
498     {
499         if (dwType == REG_SZ)
500         {
501             ValueU.Buffer = (PWSTR)&Value;
502             ValueU.Length = ValueU.MaximumLength = dwSize;
503             RtlUnicodeStringToInteger(&ValueU, 10, &Value);
504         }
505         Settings.bSaveSettings = !!Value;
506     }
507 
508     RegCloseKey(hKeyEventVwr);
509     return TRUE;
510 }
511 
512 BOOL
513 SaveSettings(VOID)
514 {
515     HKEY hKeyEventVwr;
516     DWORD dwSize;
517     WCHAR buffer[100];
518 
519     /* Try to create/open the Event Viewer user key */
520     if (RegCreateKeyExW(HKEY_CURRENT_USER,
521                         EVNTVWR_PARAM_KEY,
522                         0,
523                         NULL,
524                         REG_OPTION_NON_VOLATILE,
525                         KEY_READ | KEY_WRITE,
526                         NULL,
527                         &hKeyEventVwr,
528                         NULL) != ERROR_SUCCESS)
529     {
530         return FALSE;
531     }
532 
533     dwSize = sizeof(Settings.bSaveSettings);
534     RegSetValueExW(hKeyEventVwr, L"SaveSettings", 0, REG_DWORD, (LPBYTE)&Settings.bSaveSettings, dwSize);
535 
536     /* Do not save more settings if we are not asked to do so */
537     if (!Settings.bSaveSettings)
538         goto Quit;
539 
540     dwSize = sizeof(Settings.bShowDetailsPane);
541     RegSetValueExW(hKeyEventVwr, L"DetailsPane", 0, REG_DWORD, (LPBYTE)&Settings.bShowDetailsPane, dwSize);
542 
543     dwSize = sizeof(Settings.bShowGrid);
544     RegSetValueExW(hKeyEventVwr, L"ShowGrid", 0, REG_DWORD, (LPBYTE)&Settings.bShowGrid, dwSize);
545 
546     dwSize = sizeof(Settings.bNewestEventsFirst);
547     RegSetValueExW(hKeyEventVwr, L"SortOrder", 0, REG_DWORD, (LPBYTE)&Settings.bNewestEventsFirst, dwSize);
548 
549     Settings.nVSplitPos = nVSplitPos;
550     dwSize = sizeof(Settings.nVSplitPos);
551     RegSetValueExW(hKeyEventVwr, L"VSplitPos", 0, REG_DWORD, (LPBYTE)&Settings.nVSplitPos, dwSize);
552 
553     Settings.nHSplitPos = nHSplitPos;
554     dwSize = sizeof(Settings.nHSplitPos);
555     RegSetValueExW(hKeyEventVwr, L"HSplitPos", 0, REG_DWORD, (LPBYTE)&Settings.nHSplitPos, dwSize);
556 
557     StringCbPrintfW(buffer, sizeof(buffer),
558                     L"%d %d %d %d %d",
559                     Settings.wpPos.rcNormalPosition.left,
560                     Settings.wpPos.rcNormalPosition.top,
561                     Settings.wpPos.rcNormalPosition.right,
562                     Settings.wpPos.rcNormalPosition.bottom,
563                     Settings.wpPos.showCmd);
564 
565     dwSize = (DWORD)(wcslen(buffer) * sizeof(WCHAR));
566     RegSetValueExW(hKeyEventVwr, L"Window", 0, REG_SZ, (LPBYTE)buffer, dwSize);
567 
568 Quit:
569     RegCloseKey(hKeyEventVwr);
570     return TRUE;
571 }
572 
573 int APIENTRY
574 wWinMain(HINSTANCE hInstance,
575          HINSTANCE hPrevInstance,
576          LPWSTR    lpCmdLine,
577          int       nCmdShow)
578 {
579     HANDLE hThread;
580     INITCOMMONCONTROLSEX iccx;
581     HMODULE hRichEdit;
582     HACCEL hAccelTable;
583     MSG msg;
584 
585     UNREFERENCED_PARAMETER(hPrevInstance);
586     UNREFERENCED_PARAMETER(lpCmdLine);
587 
588     /* Whenever any of the common controls are used in your app,
589      * you must call InitCommonControlsEx() to register the classes
590      * for those controls. */
591     iccx.dwSize = sizeof(iccx);
592     iccx.dwICC = ICC_LISTVIEW_CLASSES;
593     InitCommonControlsEx(&iccx);
594 
595     /* Load the RichEdit DLL to add support for RichEdit controls */
596     hRichEdit = LoadLibraryW(L"riched20.dll");
597     if (!hRichEdit)
598         return -1;
599 
600     msg.wParam = (WPARAM)-1;
601 
602     /* Store the instance handle in the global variable */
603     hInst = hInstance;
604 
605     /* Initialize global strings */
606     LoadStringW(hInstance, IDS_APP_TITLE, szTitle, ARRAYSIZE(szTitle));
607     LoadStringW(hInstance, IDS_APP_TITLE_EX, szTitleTemplate, ARRAYSIZE(szTitleTemplate));
608     LoadStringW(hInstance, IDS_STATUS_MSG, szStatusBarTemplate, ARRAYSIZE(szStatusBarTemplate));
609     LoadStringW(hInstance, IDS_LOADING_WAIT, szLoadingWait, ARRAYSIZE(szLoadingWait));
610     LoadStringW(hInstance, IDS_NO_ITEMS, szEmptyList, ARRAYSIZE(szEmptyList));
611 
612     /*
613      * Process the command-line arguments. Note that we need the full
614      * command-line, with the program file included, and not just what
615      * WinMain() provides in its lpCmdLine parameter.
616      */
617     if (!ProcessCmdLine(GetCommandLineW()))
618         goto Quit;
619 
620     if (!MyRegisterClass(hInstance))
621         goto Quit;
622 
623     /* Load the settings */
624     LoadSettings(nCmdShow);
625 
626     /* Perform application initialization */
627     if (!InitInstance(hInstance))
628         goto Quit;
629 
630     hAccelTable = LoadAcceleratorsW(hInstance, MAKEINTRESOURCEW(IDA_EVENTVWR));
631 
632     /* Create the Start/Stop enumerator thread */
633     // Manual-reset event
634     hStartStopEnumEvent = CreateEventW(NULL, TRUE, FALSE, NULL);
635     if (!hStartStopEnumEvent)
636         goto Cleanup;
637 
638     // Auto-reset event
639     hStartEnumEvent = CreateEventW(NULL, FALSE, FALSE, NULL);
640     if (!hStartEnumEvent)
641         goto Cleanup;
642 
643     hThread = CreateThread(NULL, 0,
644                            StartStopEnumEventsThread,
645                            NULL, 0, NULL);
646     if (!hThread)
647         goto Cleanup;
648 
649     /* Retrieve the available event logs on this computer and create filters for them */
650     InitializeListHead(&EventLogList);
651     InitializeListHead(&EventLogFilterList);
652     BuildLogListAndFilterList(lpComputerName);
653 
654     /* Open the user-specified logs if any are present on the command-line */
655     if (lpszzUserLogsToLoad)
656     {
657         LPWSTR lpUserLog;
658         for (lpUserLog = lpszzUserLogsToLoad; *lpUserLog; lpUserLog += wcslen(lpUserLog) + 1)
659         {
660             OpenUserEventLogFile(lpUserLog);
661         }
662 
663         /* Now cleanup the list of user logs */
664         cbUserLogsSize = 0;
665         HeapFree(GetProcessHeap(), 0, lpszzUserLogsToLoad);
666         lpszzUserLogsToLoad = NULL;
667     }
668 
669     /* Main message loop */
670     while (GetMessageW(&msg, NULL, 0, 0))
671     {
672         if (!TranslateAcceleratorW(hwndMainWindow, hAccelTable, &msg))
673         {
674             TranslateMessage(&msg);
675             DispatchMessageW(&msg);
676         }
677     }
678 
679     /* Save the settings */
680     SaveSettings();
681 
682     /* Disconnect from computer */
683     if (hkMachine && hkMachine != HKEY_LOCAL_MACHINE)
684     {
685         /* We are connected to some other computer, close the old connection */
686         RegCloseKey(hkMachine);
687         hkMachine = NULL;
688     }
689 
690     /* Stop the enumerator thread */
691     SetEvent(hStartStopEnumEvent);
692     WaitForSingleObject(hThread, INFINITE);
693     CloseHandle(hThread);
694 
695     /* Free the filters list and the event logs list */
696     FreeLogFilterList();
697     FreeLogList();
698 
699 Cleanup:
700     /* Handle cleanup */
701     if (hStartEnumEvent)
702         CloseHandle(hStartEnumEvent);
703     if (hStartStopEnumEvent)
704         CloseHandle(hStartStopEnumEvent);
705 
706 Quit:
707     /* Final cleanup */
708     if (lpszzUserLogsToLoad)
709     {
710         cbUserLogsSize = 0;
711         HeapFree(GetProcessHeap(), 0, lpszzUserLogsToLoad);
712         lpszzUserLogsToLoad = NULL;
713     }
714     if (lpComputerName)
715     {
716         HeapFree(GetProcessHeap(), 0, lpComputerName);
717         lpComputerName = NULL;
718     }
719     FreeLibrary(hRichEdit);
720 
721     return (int)msg.wParam;
722 }
723 
724 
725 /* GENERIC HELPER FUNCTIONS ***************************************************/
726 
727 VOID
728 EventTimeToSystemTime(IN DWORD EventTime,
729                       OUT PSYSTEMTIME pSystemTime)
730 {
731     SYSTEMTIME st1970 = { 1970, 1, 0, 1, 0, 0, 0, 0 };
732     FILETIME ftLocal;
733     union
734     {
735         FILETIME ft;
736         ULONGLONG ll;
737     } u1970, uUCT;
738 
739     uUCT.ft.dwHighDateTime = 0;
740     uUCT.ft.dwLowDateTime = EventTime;
741     SystemTimeToFileTime(&st1970, &u1970.ft);
742     uUCT.ll = uUCT.ll * 10000000 + u1970.ll;
743     FileTimeToLocalFileTime(&uUCT.ft, &ftLocal);
744     FileTimeToSystemTime(&ftLocal, pSystemTime);
745 }
746 
747 /*
748  * This function takes in entry a path to a single DLL, in which
749  * the message string of ID dwMessageId has to be searched.
750  * The other parameters are similar to those of the FormatMessageW API.
751  */
752 LPWSTR
753 GetMessageStringFromDll(
754     IN LPCWSTR  lpMessageDll,
755     IN DWORD    dwFlags, // If we always use the same flags, just remove this param...
756     IN DWORD    dwMessageId,
757     IN DWORD    nSize,
758     IN va_list* Arguments OPTIONAL)
759 {
760     HMODULE hLibrary;
761     DWORD dwLength;
762     LPWSTR lpMsgBuf = NULL;
763 
764     hLibrary = LoadLibraryExW(lpMessageDll, NULL,
765                               /* LOAD_LIBRARY_AS_IMAGE_RESOURCE | */ LOAD_LIBRARY_AS_DATAFILE);
766     if (hLibrary == NULL)
767         return NULL;
768 
769     /* Sanitize dwFlags */
770     dwFlags &= ~FORMAT_MESSAGE_FROM_STRING;
771     dwFlags |= FORMAT_MESSAGE_FROM_HMODULE;
772 
773     _SEH2_TRY
774     {
775         /*
776          * Retrieve the message string without appending extra newlines.
777          * Wrap in SEH to protect from invalid string parameters.
778          */
779         _SEH2_TRY
780         {
781             dwLength = FormatMessageW(dwFlags,
782                                    /* FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_HMODULE |
783                                       FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_MAX_WIDTH_MASK, */
784                                       hLibrary,
785                                       dwMessageId,
786                                       LANG_USER_DEFAULT,
787                                       (LPWSTR)&lpMsgBuf,
788                                       nSize,
789                                       Arguments);
790         }
791         _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
792         {
793             dwLength = 0;
794 
795             /*
796              * An exception occurred while calling FormatMessage, this is usually
797              * the sign that a parameter was invalid, either 'lpMsgBuf' was NULL
798              * but we did not pass the flag FORMAT_MESSAGE_ALLOCATE_BUFFER, or the
799              * array pointer 'Arguments' was NULL or did not contain enough elements,
800              * and we did not pass the flag FORMAT_MESSAGE_IGNORE_INSERTS, and the
801              * message string expected too many inserts.
802              * In this last case only, we can call again FormatMessage but ignore
803              * explicitly the inserts. The string that we will return to the user
804              * will not be pre-formatted.
805              */
806             if (((dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER) || lpMsgBuf) &&
807                 !(dwFlags & FORMAT_MESSAGE_IGNORE_INSERTS))
808             {
809                 /* Remove any possible harmful flags and always ignore inserts */
810                 dwFlags &= ~FORMAT_MESSAGE_ARGUMENT_ARRAY;
811                 dwFlags |=  FORMAT_MESSAGE_IGNORE_INSERTS;
812 
813                 /* If this call also throws an exception, we are really dead */
814                 dwLength = FormatMessageW(dwFlags,
815                                           hLibrary,
816                                           dwMessageId,
817                                           LANG_USER_DEFAULT,
818                                           (LPWSTR)&lpMsgBuf,
819                                           nSize,
820                                           NULL /* Arguments */);
821             }
822         }
823         _SEH2_END;
824     }
825     _SEH2_FINALLY
826     {
827         FreeLibrary(hLibrary);
828     }
829     _SEH2_END;
830 
831     if (dwLength == 0)
832     {
833         ASSERT(lpMsgBuf == NULL);
834         lpMsgBuf = NULL;
835     }
836     else
837     {
838         LPWSTR ptr;
839 
840         ASSERT(lpMsgBuf);
841 
842         /* Trim any trailing whitespace */
843         ptr = lpMsgBuf + dwLength - 1;
844         while (iswspace(*ptr))
845             *ptr-- = UNICODE_NULL;
846     }
847 
848     return lpMsgBuf;
849 }
850 
851 /*
852  * This function takes in entry a comma-separated list of DLLs, in which
853  * the message string of ID dwMessageId has to be searched.
854  * The other parameters are similar to those of the FormatMessageW API.
855  */
856 LPWSTR
857 GetMessageStringFromDllList(
858     IN LPCWSTR  lpMessageDllList,
859     IN DWORD    dwFlags, // If we always use the same flags, just remove this param...
860     IN DWORD    dwMessageId,
861     IN DWORD    nSize,
862     IN va_list* Arguments OPTIONAL)
863 {
864     BOOL Success = FALSE;
865     SIZE_T cbLength;
866     LPWSTR szMessageDllList;
867     LPWSTR szDll;
868     LPWSTR lpMsgBuf = NULL;
869 
870     /* Allocate a local buffer for the DLL list that can be tokenized */
871     // TODO: Optimize that!! Maybe we can cleverly use lpMessageDllList in read/write mode
872     // and cleverly temporarily replace the ';' by UNICODE_NULL, do our job, then reverse the change.
873     cbLength = (wcslen(lpMessageDllList) + 1) * sizeof(WCHAR);
874     szMessageDllList = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, cbLength);
875     if (!szMessageDllList)
876         return NULL;
877     RtlCopyMemory(szMessageDllList, lpMessageDllList, cbLength);
878 
879     /* Loop through the list of message DLLs */
880     szDll = wcstok(szMessageDllList, EVENT_DLL_SEPARATOR);
881     while ((szDll != NULL) && !Success)
882     {
883         // Uses LANG_USER_DEFAULT
884         lpMsgBuf = GetMessageStringFromDll(szDll,
885                                            dwFlags,
886                                            dwMessageId,
887                                            nSize,
888                                            Arguments);
889         if (lpMsgBuf)
890         {
891             /* The ID was found and the message was formatted */
892             Success = TRUE;
893             break;
894         }
895 
896         /*
897          * The DLL could not be loaded, or the message could not be found,
898          * try the next DLL, if any.
899          */
900         szDll = wcstok(NULL, EVENT_DLL_SEPARATOR);
901     }
902 
903     HeapFree(GetProcessHeap(), 0, szMessageDllList);
904 
905     return lpMsgBuf;
906 }
907 
908 
909 typedef struct
910 {
911     LPWSTR pStartingAddress;    // Pointer to the beginning of a parameter string in pMessage
912     LPWSTR pEndingAddress;      // Pointer to the end of a parameter string in pMessage
913     DWORD  pParameterID;        // Parameter identifier found in pMessage
914     LPWSTR pParameter;          // Actual parameter string
915 } param_strings_format_data;
916 
917 DWORD
918 ApplyParameterStringsToMessage(
919     IN LPCWSTR  lpMessageDllList,
920     IN BOOL     bMessagePreFormatted,
921     IN CONST LPCWSTR pMessage,
922     OUT LPWSTR* pFinalMessage)
923 {
924     /*
925      * This code is heavily adapted from the MSDN example:
926      * https://msdn.microsoft.com/en-us/library/windows/desktop/bb427356.aspx
927      * with bugs removed.
928      */
929 
930     DWORD Status = ERROR_SUCCESS;
931     DWORD dwParamCount = 0; // Number of insertion strings found in pMessage
932     size_t cchBuffer = 0;   // Size of the buffer in characters
933     size_t cchParams = 0;   // Number of characters in all the parameter strings
934     size_t cch = 0;
935     DWORD i = 0;
936     param_strings_format_data* pParamData = NULL; // Array of pointers holding information about each parameter string in pMessage
937     LPWSTR pTempMessage = (LPWSTR)pMessage;
938     LPWSTR pTempFinalMessage = NULL;
939 
940     *pFinalMessage = NULL;
941 
942     /* Determine the number of parameter insertion strings in pMessage */
943     if (bMessagePreFormatted)
944     {
945         while ((pTempMessage = wcschr(pTempMessage, L'%')))
946         {
947             pTempMessage++;
948             if (iswdigit(*pTempMessage))
949             {
950                 dwParamCount++;
951                 while (iswdigit(*++pTempMessage)) ;
952             }
953         }
954     }
955     else
956     {
957         while ((pTempMessage = wcsstr(pTempMessage, L"%%")))
958         {
959             pTempMessage += 2;
960             if (iswdigit(*pTempMessage))
961             {
962                 dwParamCount++;
963                 while (iswdigit(*++pTempMessage)) ;
964             }
965         }
966     }
967 
968     /* If there are no parameter insertion strings in pMessage, just return */
969     if (dwParamCount == 0)
970     {
971         // *pFinalMessage = NULL;
972         goto Cleanup;
973     }
974 
975     /* Allocate the array of parameter string format data */
976     pParamData = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwParamCount * sizeof(param_strings_format_data));
977     if (!pParamData)
978     {
979         Status = ERROR_OUTOFMEMORY;
980         goto Cleanup;
981     }
982 
983     /*
984      * Retrieve each parameter in pMessage and the beginning and end of the
985      * insertion string, as well as the message identifier of the parameter.
986      */
987     pTempMessage = (LPWSTR)pMessage;
988     if (bMessagePreFormatted)
989     {
990         while ((pTempMessage = wcschr(pTempMessage, L'%')) && (i < dwParamCount))
991         {
992             pTempMessage++;
993             if (iswdigit(*pTempMessage))
994             {
995                 pParamData[i].pStartingAddress = pTempMessage-1;
996                 pParamData[i].pParameterID = (DWORD)_wtol(pTempMessage);
997 
998                 while (iswdigit(*++pTempMessage)) ;
999 
1000                 pParamData[i].pEndingAddress = pTempMessage;
1001                 i++;
1002             }
1003         }
1004     }
1005     else
1006     {
1007         while ((pTempMessage = wcsstr(pTempMessage, L"%%")) && (i < dwParamCount))
1008         {
1009             pTempMessage += 2;
1010             if (iswdigit(*pTempMessage))
1011             {
1012                 pParamData[i].pStartingAddress = pTempMessage-2;
1013                 pParamData[i].pParameterID = (DWORD)_wtol(pTempMessage);
1014 
1015                 while (iswdigit(*++pTempMessage)) ;
1016 
1017                 pParamData[i].pEndingAddress = pTempMessage;
1018                 i++;
1019             }
1020         }
1021     }
1022 
1023     /* Retrieve each parameter string */
1024     for (i = 0; i < dwParamCount; i++)
1025     {
1026         // pParamData[i].pParameter = GetMessageString(pParamData[i].pParameterID, 0, NULL);
1027         pParamData[i].pParameter =
1028             GetMessageStringFromDllList(lpMessageDllList,
1029                                         FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_HMODULE |
1030                                         FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_MAX_WIDTH_MASK,
1031                                         pParamData[i].pParameterID,
1032                                         0, NULL);
1033         if (!pParamData[i].pParameter)
1034         {
1035             /* Skip the insertion string */
1036             continue;
1037         }
1038 
1039         cchParams += wcslen(pParamData[i].pParameter);
1040     }
1041 
1042     /*
1043      * Allocate the final message buffer, the size of which is based on the
1044      * length of the original message and the length of each parameter string.
1045      */
1046     cchBuffer = wcslen(pMessage) + cchParams + 1;
1047     *pFinalMessage = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, cchBuffer * sizeof(WCHAR));
1048     if (!*pFinalMessage)
1049     {
1050         Status = ERROR_OUTOFMEMORY;
1051         goto Cleanup;
1052     }
1053 
1054     pTempFinalMessage = *pFinalMessage;
1055 
1056     /* Build the final message string */
1057     pTempMessage = (LPWSTR)pMessage;
1058     for (i = 0; i < dwParamCount; i++)
1059     {
1060         /* Append the segment from pMessage */
1061         cch = pParamData[i].pStartingAddress - pTempMessage;
1062         StringCchCopyNW(pTempFinalMessage, cchBuffer, pTempMessage, cch);
1063         pTempMessage = pParamData[i].pEndingAddress;
1064         cchBuffer -= cch;
1065         pTempFinalMessage += cch;
1066 
1067         /* Append the parameter string */
1068         if (pParamData[i].pParameter)
1069         {
1070             StringCchCopyW(pTempFinalMessage, cchBuffer, pParamData[i].pParameter);
1071             cch = wcslen(pParamData[i].pParameter); // pTempFinalMessage
1072         }
1073         else
1074         {
1075             /*
1076              * We failed to retrieve the parameter string before, so just
1077              * place back the original string placeholder.
1078              */
1079             cch = pParamData[i].pEndingAddress /* == pTempMessage */ - pParamData[i].pStartingAddress;
1080             StringCchCopyNW(pTempFinalMessage, cchBuffer, pParamData[i].pStartingAddress, cch);
1081             // cch = wcslen(pTempFinalMessage);
1082         }
1083         cchBuffer -= cch;
1084         pTempFinalMessage += cch;
1085     }
1086 
1087     /* Append the last segment from pMessage */
1088     StringCchCopyW(pTempFinalMessage, cchBuffer, pTempMessage);
1089 
1090 Cleanup:
1091 
1092     // if (Status != ERROR_SUCCESS)
1093         // *pFinalMessage = NULL;
1094 
1095     if (pParamData)
1096     {
1097         for (i = 0; i < dwParamCount; i++)
1098         {
1099             if (pParamData[i].pParameter)
1100                 LocalFree(pParamData[i].pParameter);
1101         }
1102 
1103         HeapFree(GetProcessHeap(), 0, pParamData);
1104     }
1105 
1106     return Status;
1107 }
1108 
1109 
1110 /*
1111  * The following functions were adapted from
1112  * shell32!dialogs/filedefext.cpp:``SH_...'' functions.
1113  */
1114 
1115 UINT
1116 FormatInteger(LONGLONG Num, LPWSTR pwszResult, UINT cchResultMax)
1117 {
1118     WCHAR wszNumber[24];
1119     WCHAR wszDecimalSep[8], wszThousandSep[8];
1120     NUMBERFMTW nf;
1121     WCHAR wszGrouping[12];
1122     INT cchGrouping;
1123     INT cchResult;
1124     INT i;
1125 
1126     // Print the number in uniform mode
1127     swprintf(wszNumber, L"%I64u", Num);
1128 
1129     // Get system strings for decimal and thousand separators.
1130     GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SDECIMAL, wszDecimalSep, _countof(wszDecimalSep));
1131     GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_STHOUSAND, wszThousandSep, _countof(wszThousandSep));
1132 
1133     // Initialize format for printing the number in bytes
1134     ZeroMemory(&nf, sizeof(nf));
1135     nf.lpDecimalSep = wszDecimalSep;
1136     nf.lpThousandSep = wszThousandSep;
1137 
1138     // Get system string for groups separator
1139     cchGrouping = GetLocaleInfoW(LOCALE_USER_DEFAULT,
1140                                  LOCALE_SGROUPING,
1141                                  wszGrouping,
1142                                  _countof(wszGrouping));
1143 
1144     // Convert grouping specs from string to integer
1145     for (i = 0; i < cchGrouping; i++)
1146     {
1147         WCHAR wch = wszGrouping[i];
1148 
1149         if (wch >= L'0' && wch <= L'9')
1150             nf.Grouping = nf.Grouping * 10 + (wch - L'0');
1151         else if (wch != L';')
1152             break;
1153     }
1154 
1155     if ((nf.Grouping % 10) == 0)
1156         nf.Grouping /= 10;
1157     else
1158         nf.Grouping *= 10;
1159 
1160     // Format the number
1161     cchResult = GetNumberFormatW(LOCALE_USER_DEFAULT,
1162                                  0,
1163                                  wszNumber,
1164                                  &nf,
1165                                  pwszResult,
1166                                  cchResultMax);
1167 
1168     if (!cchResult)
1169         return 0;
1170 
1171     // GetNumberFormatW returns number of characters including UNICODE_NULL
1172     return cchResult - 1;
1173 }
1174 
1175 UINT
1176 FormatByteSize(LONGLONG cbSize, LPWSTR pwszResult, UINT cchResultMax)
1177 {
1178     UINT cchWritten, cchRemaining;
1179     LPWSTR pwszEnd;
1180     size_t cchStringRemaining;
1181 
1182     /* Write formated bytes count */
1183     cchWritten = FormatInteger(cbSize, pwszResult, cchResultMax);
1184     if (!cchWritten)
1185         return 0;
1186 
1187     /* Copy " bytes" to buffer */
1188     pwszEnd = pwszResult + cchWritten;
1189     cchRemaining = cchResultMax - cchWritten;
1190     StringCchCopyExW(pwszEnd, cchRemaining, L" ", &pwszEnd, &cchStringRemaining, 0);
1191     cchRemaining = (UINT)cchStringRemaining;
1192     cchWritten = LoadStringW(hInst, IDS_BYTES_FORMAT, pwszEnd, cchRemaining);
1193     cchRemaining -= cchWritten;
1194 
1195     return cchResultMax - cchRemaining;
1196 }
1197 
1198 LPWSTR
1199 FormatFileSizeWithBytes(const PULARGE_INTEGER lpQwSize, LPWSTR pwszResult, UINT cchResultMax)
1200 {
1201     UINT cchWritten, cchRemaining;
1202     LPWSTR pwszEnd;
1203     size_t cchCopyRemaining;
1204 
1205     /* Format bytes in KBs, MBs etc */
1206     if (StrFormatByteSizeW(lpQwSize->QuadPart, pwszResult, cchResultMax) == NULL)
1207         return NULL;
1208 
1209     /* If there is less bytes than 1KB, we have nothing to do */
1210     if (lpQwSize->QuadPart < 1024)
1211         return pwszResult;
1212 
1213     /* Concatenate " (" */
1214     cchWritten = (UINT)wcslen(pwszResult);
1215     pwszEnd = pwszResult + cchWritten;
1216     cchRemaining = cchResultMax - cchWritten;
1217     StringCchCopyExW(pwszEnd, cchRemaining, L" (", &pwszEnd, &cchCopyRemaining, 0);
1218     cchRemaining = (UINT)cchCopyRemaining;
1219 
1220     /* Write formated bytes count */
1221     cchWritten = FormatByteSize(lpQwSize->QuadPart, pwszEnd, cchRemaining);
1222     pwszEnd += cchWritten;
1223     cchRemaining -= cchWritten;
1224 
1225     /* Copy ")" to the buffer */
1226     StringCchCopyW(pwszEnd, cchRemaining, L")");
1227 
1228     return pwszResult;
1229 }
1230 
1231 /* Adapted from shell32!dialogs/filedefext.cpp:``CFileDefExt::GetFileTimeString'' */
1232 BOOL
1233 GetFileTimeString(LPFILETIME lpFileTime, LPWSTR pwszResult, UINT cchResult)
1234 {
1235     FILETIME ft;
1236     SYSTEMTIME st;
1237     int cchWritten;
1238     UINT cchRemaining = cchResult;
1239     size_t cchCopyRemaining;
1240     LPWSTR pwszEnd = pwszResult;
1241 
1242     if (!FileTimeToLocalFileTime(lpFileTime, &ft) || !FileTimeToSystemTime(&ft, &st))
1243         return FALSE;
1244 
1245     cchWritten = GetDateFormatW(LOCALE_USER_DEFAULT, DATE_LONGDATE, &st, NULL, pwszEnd, cchRemaining);
1246     if (cchWritten)
1247         --cchWritten; // GetDateFormatW returns count with terminating zero
1248     // else
1249         // ERR("GetDateFormatW failed\n");
1250 
1251     cchRemaining -= cchWritten;
1252     pwszEnd += cchWritten;
1253 
1254     StringCchCopyExW(pwszEnd, cchRemaining, L", ", &pwszEnd, &cchCopyRemaining, 0);
1255     cchRemaining = (UINT)cchCopyRemaining;
1256 
1257     cchWritten = GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL, pwszEnd, cchRemaining);
1258     if (cchWritten)
1259         --cchWritten; // GetTimeFormatW returns count with terminating zero
1260     // else
1261         // ERR("GetTimeFormatW failed\n");
1262 
1263     return TRUE;
1264 }
1265 
1266 
1267 HTREEITEM
1268 TreeViewAddItem(IN HWND hTreeView,
1269                 IN HTREEITEM hParent,
1270                 IN LPWSTR lpText,
1271                 IN INT Image,
1272                 IN INT SelectedImage,
1273                 IN LPARAM lParam)
1274 {
1275     TV_INSERTSTRUCTW Insert;
1276 
1277     ZeroMemory(&Insert, sizeof(Insert));
1278 
1279     Insert.item.mask = TVIF_TEXT | TVIF_PARAM | TVIF_IMAGE | TVIF_SELECTEDIMAGE;
1280     Insert.hInsertAfter = TVI_LAST;
1281     Insert.hParent = hParent;
1282     Insert.item.pszText = lpText;
1283     Insert.item.iImage = Image;
1284     Insert.item.iSelectedImage = SelectedImage;
1285     Insert.item.lParam = lParam;
1286 
1287     Insert.item.mask |= TVIF_STATE;
1288     Insert.item.stateMask = TVIS_OVERLAYMASK;
1289     Insert.item.state = INDEXTOOVERLAYMASK(1);
1290 
1291     return TreeView_InsertItem(hTreeView, &Insert);
1292 }
1293 
1294 
1295 /* LOG HELPER FUNCTIONS *******************************************************/
1296 
1297 PEVENTLOG
1298 AllocEventLog(IN PCWSTR ComputerName OPTIONAL,
1299               IN PCWSTR LogName,
1300               IN BOOL Permanent)
1301 {
1302     PEVENTLOG EventLog;
1303     SIZE_T cchName;
1304 
1305     /* Allocate a new event log entry */
1306     EventLog = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*EventLog));
1307     if (!EventLog)
1308         return NULL;
1309 
1310     /* Allocate the computer name string (optional) and copy it */
1311     if (ComputerName)
1312     {
1313         cchName = wcslen(ComputerName) + 1;
1314         EventLog->ComputerName = HeapAlloc(GetProcessHeap(), 0, cchName * sizeof(WCHAR));
1315         if (EventLog->ComputerName)
1316             StringCchCopyW(EventLog->ComputerName, cchName, ComputerName);
1317     }
1318 
1319     /* Allocate the event log name string and copy it */
1320     cchName = wcslen(LogName) + 1;
1321     EventLog->LogName = HeapAlloc(GetProcessHeap(), 0, cchName * sizeof(WCHAR));
1322     if (!EventLog->LogName)
1323     {
1324         if (EventLog->ComputerName)
1325             HeapFree(GetProcessHeap(), 0, EventLog->ComputerName);
1326         HeapFree(GetProcessHeap(), 0, EventLog);
1327         return NULL;
1328     }
1329     StringCchCopyW(EventLog->LogName, cchName, LogName);
1330 
1331     EventLog->Permanent = Permanent;
1332 
1333     return EventLog;
1334 }
1335 
1336 VOID
1337 EventLog_Free(IN PEVENTLOG EventLog)
1338 {
1339     if (EventLog->LogName)
1340         HeapFree(GetProcessHeap(), 0, EventLog->LogName);
1341 
1342     if (EventLog->ComputerName)
1343         HeapFree(GetProcessHeap(), 0, EventLog->ComputerName);
1344 
1345     if (EventLog->FileName)
1346         HeapFree(GetProcessHeap(), 0, EventLog->FileName);
1347 
1348     HeapFree(GetProcessHeap(), 0, EventLog);
1349 }
1350 
1351 
1352 PWSTR
1353 AllocAndCopyMultiStr(IN PCWSTR MultiStr OPTIONAL)
1354 {
1355     PWSTR pStr;
1356     ULONG Length;
1357 
1358     if (!MultiStr)
1359         return NULL;
1360 
1361     pStr = (PWSTR)MultiStr;
1362     while (*pStr) pStr += (wcslen(pStr) + 1);
1363     Length = MultiStr - pStr + 2;
1364 
1365     pStr = HeapAlloc(GetProcessHeap(), 0, Length * sizeof(WCHAR));
1366     // NOTE: If we failed allocating the string, then fall back into no filter!
1367     if (pStr)
1368         RtlCopyMemory(pStr, MultiStr, Length * sizeof(WCHAR));
1369 
1370     return pStr;
1371 }
1372 
1373 PEVENTLOGFILTER
1374 AllocEventLogFilter(// IN PCWSTR FilterName,
1375                     IN BOOL Information,
1376                     IN BOOL Warning,
1377                     IN BOOL Error,
1378                     IN BOOL AuditSuccess,
1379                     IN BOOL AuditFailure,
1380                     IN PCWSTR Sources OPTIONAL,
1381                     IN PCWSTR Users OPTIONAL,
1382                     IN PCWSTR ComputerNames OPTIONAL,
1383                     IN ULONG NumOfEventLogs,
1384                     IN PEVENTLOG* EventLogs)
1385 {
1386     PEVENTLOGFILTER EventLogFilter;
1387 
1388     /* Allocate a new event log filter entry, big enough to accommodate the list of logs */
1389     EventLogFilter = HeapAlloc(GetProcessHeap(),
1390                                HEAP_ZERO_MEMORY,
1391                                FIELD_OFFSET(EVENTLOGFILTER, EventLogs[NumOfEventLogs]));
1392     if (!EventLogFilter)
1393         return NULL;
1394 
1395     EventLogFilter->Information  = Information;
1396     EventLogFilter->Warning      = Warning;
1397     EventLogFilter->Error        = Error;
1398     EventLogFilter->AuditSuccess = AuditSuccess;
1399     EventLogFilter->AuditFailure = AuditFailure;
1400 
1401     /* Allocate and copy the sources, users, and computers multi-strings */
1402     EventLogFilter->Sources = AllocAndCopyMultiStr(Sources);
1403     EventLogFilter->Users   = AllocAndCopyMultiStr(Users);
1404     EventLogFilter->ComputerNames = AllocAndCopyMultiStr(ComputerNames);
1405 
1406     /* Copy the list of event logs */
1407     EventLogFilter->NumOfEventLogs = NumOfEventLogs;
1408     RtlCopyMemory(EventLogFilter->EventLogs, EventLogs, NumOfEventLogs * sizeof(PEVENTLOG));
1409 
1410     /* Initialize the filter reference count */
1411     EventLogFilter->ReferenceCount = 1;
1412 
1413     return EventLogFilter;
1414 }
1415 
1416 VOID
1417 EventLogFilter_Free(IN PEVENTLOGFILTER EventLogFilter)
1418 {
1419     if (EventLogFilter->Sources)
1420         HeapFree(GetProcessHeap(), 0, EventLogFilter->Sources);
1421 
1422     if (EventLogFilter->Users)
1423         HeapFree(GetProcessHeap(), 0, EventLogFilter->Users);
1424 
1425     if (EventLogFilter->ComputerNames)
1426         HeapFree(GetProcessHeap(), 0, EventLogFilter->ComputerNames);
1427 
1428     HeapFree(GetProcessHeap(), 0, EventLogFilter);
1429 }
1430 
1431 LONG EventLogFilter_AddRef(IN PEVENTLOGFILTER EventLogFilter)
1432 {
1433     ASSERT(EventLogFilter);
1434     return InterlockedIncrement(&EventLogFilter->ReferenceCount);
1435 }
1436 
1437 LONG EventLogFilter_Release(IN PEVENTLOGFILTER EventLogFilter)
1438 {
1439     LONG RefCount;
1440 
1441     ASSERT(EventLogFilter);
1442 
1443     /* When the reference count reaches zero, delete the filter */
1444     RefCount = InterlockedDecrement(&EventLogFilter->ReferenceCount);
1445     if (RefCount <= 0)
1446     {
1447         /* Remove the filter from the list */
1448         /** RemoveEntryList(&EventLogFilter->ListEntry); **/
1449         EventLogFilter_Free(EventLogFilter);
1450     }
1451 
1452     return RefCount;
1453 }
1454 
1455 void
1456 TrimNulls(LPWSTR s)
1457 {
1458     WCHAR *c;
1459 
1460     if (s != NULL)
1461     {
1462         c = s + wcslen(s) - 1;
1463         while (c >= s && iswspace(*c))
1464             --c;
1465         *++c = L'\0';
1466     }
1467 }
1468 
1469 DWORD
1470 GetExpandedFilePathName(
1471     IN LPCWSTR ComputerName OPTIONAL,
1472     IN LPCWSTR lpFileName,
1473     OUT LPWSTR lpFullFileName OPTIONAL,
1474     IN DWORD nSize)
1475 {
1476     SIZE_T dwLength;
1477 
1478     /* Determine the needed size after expansion of any environment strings */
1479     dwLength = ExpandEnvironmentStringsW(lpFileName, NULL, 0);
1480     if (dwLength == 0)
1481     {
1482         /* We failed, bail out */
1483         return 0;
1484     }
1485 
1486     /* If the file path is on a remote computer, estimate its length */
1487     // FIXME: Use WNetGetUniversalName instead?
1488     if (ComputerName && *ComputerName)
1489     {
1490         /* Skip any leading backslashes */
1491         while (*ComputerName == L'\\')
1492             ++ComputerName;
1493 
1494         if (*ComputerName)
1495         {
1496             /* Count 2 backslashes plus the computer name and one backslash separator */
1497             dwLength += 2 + wcslen(ComputerName) + 1;
1498         }
1499     }
1500 
1501     /* Check whether we have enough space */
1502     if (dwLength > nSize)
1503     {
1504         /* No, return the needed size in characters (includes NULL-terminator) */
1505         return dwLength;
1506     }
1507 
1508 
1509     /* Now expand the file path */
1510     ASSERT(dwLength <= nSize);
1511 
1512     /* Expand any existing environment strings */
1513     if (ExpandEnvironmentStringsW(lpFileName, lpFullFileName, dwLength) == 0)
1514     {
1515         /* We failed, bail out */
1516         return 0;
1517     }
1518 
1519     /* If the file path is on a remote computer, retrieve the network share form of the file name */
1520     // FIXME: Use WNetGetUniversalName instead?
1521     if (ComputerName && *ComputerName)
1522     {
1523         /* Note that we previously skipped any potential leading backslashes */
1524 
1525         /* Replace ':' by '$' in the drive letter */
1526         if (*lpFullFileName && lpFullFileName[1] == L':')
1527             lpFullFileName[1] = L'$';
1528 
1529         /* Prepend the computer name */
1530         RtlMoveMemory(lpFullFileName + 2 + wcslen(ComputerName) + 1,
1531                       lpFullFileName, dwLength * sizeof(WCHAR) - (2 + wcslen(ComputerName) + 1) * sizeof(WCHAR));
1532         lpFullFileName[0] = L'\\';
1533         lpFullFileName[1] = L'\\';
1534         wcsncpy(lpFullFileName + 2, ComputerName, wcslen(ComputerName));
1535         lpFullFileName[2 + wcslen(ComputerName)] = L'\\';
1536     }
1537 
1538     /* Return the number of stored characters (includes NULL-terminator) */
1539     return dwLength;
1540 }
1541 
1542 BOOL
1543 GetEventMessageFileDLL(IN LPCWSTR lpLogName,
1544                        IN LPCWSTR SourceName,
1545                        IN LPCWSTR EntryName,
1546                        OUT PWCHAR lpModuleName) // TODO: Add IN DWORD BufLen
1547 {
1548     BOOL Success = FALSE;
1549     LONG Result;
1550     DWORD dwType, dwSize;
1551     WCHAR szModuleName[MAX_PATH];
1552     WCHAR szKeyName[MAX_PATH];
1553     HKEY hLogKey = NULL;
1554     HKEY hSourceKey = NULL;
1555 
1556     StringCbCopyW(szKeyName, sizeof(szKeyName), EVENTLOG_BASE_KEY);
1557     StringCbCatW(szKeyName, sizeof(szKeyName), lpLogName);
1558 
1559     Result = RegOpenKeyExW(hkMachine,
1560                            szKeyName,
1561                            0,
1562                            KEY_READ,
1563                            &hLogKey);
1564     if (Result != ERROR_SUCCESS)
1565         return FALSE;
1566 
1567     Result = RegOpenKeyExW(hLogKey,
1568                            SourceName,
1569                            0,
1570                            KEY_QUERY_VALUE,
1571                            &hSourceKey);
1572     if (Result != ERROR_SUCCESS)
1573     {
1574         RegCloseKey(hLogKey);
1575         return FALSE;
1576     }
1577 
1578     dwSize = sizeof(szModuleName);
1579     Result = RegQueryValueExW(hSourceKey,
1580                               EntryName,
1581                               NULL,
1582                               &dwType,
1583                               (LPBYTE)szModuleName,
1584                               &dwSize);
1585     if ((Result != ERROR_SUCCESS) || (dwType != REG_EXPAND_SZ && dwType != REG_SZ))
1586     {
1587         szModuleName[0] = UNICODE_NULL;
1588     }
1589     else
1590     {
1591         /* NULL-terminate the string and expand it */
1592         szModuleName[dwSize / sizeof(WCHAR) - 1] = UNICODE_NULL;
1593         GetExpandedFilePathName(lpComputerName, szModuleName, lpModuleName, ARRAYSIZE(szModuleName));
1594         Success = TRUE;
1595     }
1596 
1597     RegCloseKey(hSourceKey);
1598     RegCloseKey(hLogKey);
1599 
1600     return Success;
1601 }
1602 
1603 BOOL
1604 GetEventCategory(IN LPCWSTR KeyName,
1605                  IN LPCWSTR SourceName,
1606                  IN PEVENTLOGRECORD pevlr,
1607                  OUT PWCHAR CategoryName) // TODO: Add IN DWORD BufLen
1608 {
1609     BOOL Success = FALSE;
1610     WCHAR szMessageDLL[MAX_PATH];
1611     LPWSTR lpMsgBuf = NULL;
1612 
1613     if (!GetEventMessageFileDLL(KeyName, SourceName, EVENT_CATEGORY_MESSAGE_FILE, szMessageDLL))
1614         goto Quit;
1615 
1616     /* Retrieve the message string without appending extra newlines */
1617     lpMsgBuf =
1618     GetMessageStringFromDllList(szMessageDLL,
1619                                 FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_HMODULE |
1620                                 FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_MAX_WIDTH_MASK,
1621                                 pevlr->EventCategory,
1622                                 EVENT_MESSAGE_FILE_BUFFER,
1623                                 NULL);
1624     if (lpMsgBuf)
1625     {
1626         /* Trim the string */
1627         TrimNulls(lpMsgBuf);
1628 
1629         /* Copy the category name */
1630         StringCchCopyW(CategoryName, MAX_PATH, lpMsgBuf);
1631 
1632         /* Free the buffer allocated by FormatMessage */
1633         LocalFree(lpMsgBuf);
1634 
1635         /* The ID was found and the message was formatted */
1636         Success = TRUE;
1637     }
1638 
1639 Quit:
1640     if (!Success)
1641     {
1642         if (pevlr->EventCategory != 0)
1643         {
1644             StringCchPrintfW(CategoryName, MAX_PATH, L"(%lu)", pevlr->EventCategory);
1645             Success = TRUE;
1646         }
1647     }
1648 
1649     return Success;
1650 }
1651 
1652 
1653 BOOL                                                                        // NOTE: Used by evtdetctl.c
1654 GetEventMessage(IN LPCWSTR KeyName,
1655                 IN LPCWSTR SourceName,
1656                 IN PEVENTLOGRECORD pevlr,
1657                 OUT PWCHAR EventText) // TODO: Add IN DWORD BufLen
1658 {
1659     BOOL Success = FALSE;
1660     DWORD i;
1661     size_t cch;
1662     WCHAR SourceModuleName[1024];
1663     WCHAR ParameterModuleName[1024];
1664     BOOL IsParamModNameCached = FALSE;
1665     LPWSTR lpMsgBuf = NULL;
1666     LPWSTR szStringArray, szMessage;
1667     LPWSTR *szArguments;
1668 
1669     /* Get the event string array */
1670     szStringArray = (LPWSTR)((LPBYTE)pevlr + pevlr->StringOffset);
1671 
1672     /* NOTE: GetEventMessageFileDLL can return a comma-separated list of DLLs */
1673     if (!GetEventMessageFileDLL(KeyName, SourceName, EVENT_MESSAGE_FILE, SourceModuleName))
1674         goto Quit;
1675 
1676     /* Allocate space for insertion strings */
1677     szArguments = HeapAlloc(GetProcessHeap(), 0, pevlr->NumStrings * sizeof(LPVOID));
1678     if (!szArguments)
1679         goto Quit;
1680 
1681     if (!IsParamModNameCached)
1682     {
1683         /* Now that the parameter file list is loaded, no need to reload it at the next run! */
1684         IsParamModNameCached = GetEventMessageFileDLL(KeyName, SourceName, EVENT_PARAMETER_MESSAGE_FILE, ParameterModuleName);
1685         // FIXME: If the string loading failed the first time, no need to retry it just after???
1686     }
1687 
1688     if (IsParamModNameCached)
1689     {
1690         /* Not yet support for reading messages from parameter message DLL */
1691     }
1692 
1693     szMessage = szStringArray;
1694     /*
1695      * HACK:
1696      * We do some hackish preformatting of the cached event strings...
1697      * That's because after we pass the string to FormatMessage
1698      * (via GetMessageStringFromDllList) with the FORMAT_MESSAGE_ARGUMENT_ARRAY
1699      * flag, instead of ignoring the insertion parameters and do the formatting
1700      * by ourselves. Therefore, the resulting string should have the parameter
1701      * string placeholders starting with a single '%' instead of a mix of one
1702      * and two '%'.
1703      */
1704     /* HACK part 1: Compute the full length of the string array */
1705     cch = 0;
1706     for (i = 0; i < pevlr->NumStrings; i++)
1707     {
1708         szMessage += wcslen(szMessage) + 1;
1709     }
1710     cch = szMessage - szStringArray;
1711 
1712     /* HACK part 2: Now do the HACK proper! */
1713     szMessage = szStringArray;
1714     for (i = 0; i < pevlr->NumStrings; i++)
1715     {
1716         lpMsgBuf = szMessage;
1717         while ((lpMsgBuf = wcsstr(lpMsgBuf, L"%%")))
1718         {
1719             if (iswdigit(lpMsgBuf[2]))
1720             {
1721                 RtlMoveMemory(lpMsgBuf, lpMsgBuf+1, ((szStringArray + cch) - lpMsgBuf - 1) * sizeof(WCHAR));
1722             }
1723         }
1724 
1725         szArguments[i] = szMessage;
1726         szMessage += wcslen(szMessage) + 1;
1727     }
1728 
1729     /* Retrieve the message string without appending extra newlines */
1730     lpMsgBuf =
1731     GetMessageStringFromDllList(SourceModuleName,
1732                                 FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_HMODULE |
1733                                 FORMAT_MESSAGE_ARGUMENT_ARRAY | FORMAT_MESSAGE_MAX_WIDTH_MASK,
1734                                 pevlr->EventID,
1735                                 0,
1736                                 (va_list*)szArguments);
1737     if (lpMsgBuf)
1738     {
1739         /* Trim the string */
1740         TrimNulls(lpMsgBuf);
1741 
1742         szMessage = NULL;
1743         Success = (ApplyParameterStringsToMessage(ParameterModuleName,
1744                                                   TRUE,
1745                                                   lpMsgBuf,
1746                                                   &szMessage) == ERROR_SUCCESS);
1747         if (Success && szMessage)
1748         {
1749             /* Free the buffer allocated by FormatMessage */
1750             LocalFree(lpMsgBuf);
1751             lpMsgBuf = szMessage;
1752         }
1753 
1754         /* Copy the event text */
1755         StringCchCopyW(EventText, EVENT_MESSAGE_EVENTTEXT_BUFFER, lpMsgBuf);
1756 
1757         /* Free the buffer allocated by FormatMessage */
1758         LocalFree(lpMsgBuf);
1759     }
1760 
1761     HeapFree(GetProcessHeap(), 0, szArguments);
1762 
1763 Quit:
1764     if (!Success)
1765     {
1766         /* Get a read-only pointer to the "event-not-found" string */
1767         lpMsgBuf = HeapAlloc(GetProcessHeap(), 0, EVENT_MESSAGE_EVENTTEXT_BUFFER * sizeof(WCHAR));
1768         LoadStringW(hInst, IDS_EVENTSTRINGIDNOTFOUND, lpMsgBuf, EVENT_MESSAGE_EVENTTEXT_BUFFER);
1769         StringCchPrintfW(EventText, EVENT_MESSAGE_EVENTTEXT_BUFFER, lpMsgBuf, (pevlr->EventID & 0xFFFF), SourceName);
1770 
1771         /* Append the strings */
1772         szMessage = szStringArray;
1773         for (i = 0; i < pevlr->NumStrings; i++)
1774         {
1775             StringCchCatW(EventText, EVENT_MESSAGE_EVENTTEXT_BUFFER, szMessage);
1776             StringCchCatW(EventText, EVENT_MESSAGE_EVENTTEXT_BUFFER, L"\n");
1777             szMessage += wcslen(szMessage) + 1;
1778         }
1779     }
1780 
1781     return Success;
1782 }
1783 
1784 VOID
1785 GetEventType(IN WORD dwEventType,
1786              OUT PWCHAR eventTypeText) // TODO: Add IN DWORD BufLen
1787 {
1788     switch (dwEventType)
1789     {
1790         case EVENTLOG_ERROR_TYPE:
1791             LoadStringW(hInst, IDS_EVENTLOG_ERROR_TYPE, eventTypeText, MAX_LOADSTRING);
1792             break;
1793         case EVENTLOG_WARNING_TYPE:
1794             LoadStringW(hInst, IDS_EVENTLOG_WARNING_TYPE, eventTypeText, MAX_LOADSTRING);
1795             break;
1796         case EVENTLOG_INFORMATION_TYPE:
1797             LoadStringW(hInst, IDS_EVENTLOG_INFORMATION_TYPE, eventTypeText, MAX_LOADSTRING);
1798             break;
1799         case EVENTLOG_SUCCESS:
1800             LoadStringW(hInst, IDS_EVENTLOG_SUCCESS, eventTypeText, MAX_LOADSTRING);
1801             break;
1802         case EVENTLOG_AUDIT_SUCCESS:
1803             LoadStringW(hInst, IDS_EVENTLOG_AUDIT_SUCCESS, eventTypeText, MAX_LOADSTRING);
1804             break;
1805         case EVENTLOG_AUDIT_FAILURE:
1806             LoadStringW(hInst, IDS_EVENTLOG_AUDIT_FAILURE, eventTypeText, MAX_LOADSTRING);
1807             break;
1808         default:
1809             LoadStringW(hInst, IDS_EVENTLOG_UNKNOWN_TYPE, eventTypeText, MAX_LOADSTRING);
1810             break;
1811     }
1812 }
1813 
1814 BOOL
1815 GetEventUserName(IN PEVENTLOGRECORD pelr,
1816                  IN OUT PSID *pLastSid,
1817                  OUT PWCHAR pszUser) // TODO: Add IN DWORD BufLen
1818 {
1819     PSID pCurrentSid;
1820     PWSTR StringSid;
1821     WCHAR szName[1024];
1822     WCHAR szDomain[1024];
1823     SID_NAME_USE peUse;
1824     DWORD cchName = ARRAYSIZE(szName);
1825     DWORD cchDomain = ARRAYSIZE(szDomain);
1826     BOOL Success = FALSE;
1827 
1828     /* Point to the SID */
1829     pCurrentSid = (PSID)((LPBYTE)pelr + pelr->UserSidOffset);
1830 
1831     if (!IsValidSid(pCurrentSid))
1832     {
1833         *pLastSid = NULL;
1834         return FALSE;
1835     }
1836     else if (*pLastSid && EqualSid(*pLastSid, pCurrentSid))
1837     {
1838         return TRUE;
1839     }
1840 
1841     /* User SID */
1842     if (pelr->UserSidLength > 0)
1843     {
1844         /*
1845          * Try to retrieve the user account name and domain name corresponding
1846          * to the SID. If it cannot be retrieved, try to convert the SID to a
1847          * string-form. It should not be bigger than the user-provided buffer
1848          * 'pszUser', otherwise we return an error.
1849          */
1850         if (LookupAccountSidW(lpComputerName,
1851                               pCurrentSid,
1852                               szName,
1853                               &cchName,
1854                               szDomain,
1855                               &cchDomain,
1856                               &peUse))
1857         {
1858             StringCchCopyW(pszUser, MAX_PATH, szName);
1859             Success = TRUE;
1860         }
1861         else if (ConvertSidToStringSidW(pCurrentSid, &StringSid))
1862         {
1863             /* Copy the string only if the user-provided buffer is big enough */
1864             if (wcslen(StringSid) + 1 <= MAX_PATH) // + 1 for NULL-terminator
1865             {
1866                 StringCchCopyW(pszUser, MAX_PATH, StringSid);
1867                 Success = TRUE;
1868             }
1869             else
1870             {
1871                 SetLastError(ERROR_NOT_ENOUGH_MEMORY);
1872                 Success = FALSE;
1873             }
1874 
1875             /* Free the allocated buffer */
1876             LocalFree(StringSid);
1877         }
1878     }
1879 
1880     *pLastSid = Success ? pCurrentSid : NULL;
1881 
1882     return Success;
1883 }
1884 
1885 
1886 static VOID FreeRecords(VOID)
1887 {
1888     DWORD iIndex;
1889 
1890     if (!g_RecordPtrs)
1891         return;
1892 
1893     for (iIndex = 0; iIndex < g_TotalRecords; iIndex++)
1894     {
1895         if (g_RecordPtrs[iIndex])
1896             HeapFree(GetProcessHeap(), 0, g_RecordPtrs[iIndex]);
1897     }
1898     HeapFree(GetProcessHeap(), 0, g_RecordPtrs);
1899     g_RecordPtrs = NULL;
1900     g_TotalRecords = 0;
1901 }
1902 
1903 BOOL
1904 FilterByType(IN PEVENTLOGFILTER EventLogFilter,
1905              IN PEVENTLOGRECORD pevlr)
1906 {
1907     if ((pevlr->EventType == EVENTLOG_SUCCESS          && !EventLogFilter->Information ) ||
1908         (pevlr->EventType == EVENTLOG_INFORMATION_TYPE && !EventLogFilter->Information ) ||
1909         (pevlr->EventType == EVENTLOG_WARNING_TYPE     && !EventLogFilter->Warning     ) ||
1910         (pevlr->EventType == EVENTLOG_ERROR_TYPE       && !EventLogFilter->Error       ) ||
1911         (pevlr->EventType == EVENTLOG_AUDIT_SUCCESS    && !EventLogFilter->AuditSuccess) ||
1912         (pevlr->EventType == EVENTLOG_AUDIT_FAILURE    && !EventLogFilter->AuditFailure))
1913     {
1914         return FALSE;
1915     }
1916     return TRUE;
1917 }
1918 
1919 BOOL
1920 FilterByString(IN PCWSTR FilterString, // This is a multi-string
1921                IN PWSTR  String)
1922 {
1923     PCWSTR pStr;
1924 
1925     /* The filter string is NULL so it does not filter anything */
1926     if (!FilterString)
1927         return TRUE;
1928 
1929     /*
1930      * If the filter string filters for an empty string AND the source string
1931      * is an empty string, we have a match (particular case of the last one).
1932      */
1933     if (!*FilterString && !*String)
1934         return TRUE;
1935 
1936     // if (*FilterString || *String)
1937 
1938     /*
1939      * If the filter string is empty BUT the source string is not empty,
1940      * OR vice-versa, we cannot have a match.
1941      */
1942     if ( (!*FilterString && *String) || (*FilterString && !*String) )
1943         return FALSE;
1944 
1945     /*
1946      * If the filter string filters for at least a non-empty string,
1947      * browse it and search for a string that matches the source string.
1948      */
1949     // else if (*FilterString && *String)
1950     {
1951         pStr = FilterString;
1952         while (*pStr)
1953         {
1954             if (wcsicmp(pStr, String) == 0)
1955             {
1956                 /* We have a match, break the loop */
1957                 break;
1958             }
1959 
1960             pStr += (wcslen(pStr) + 1);
1961         }
1962         if (!*pStr) // && *String
1963         {
1964             /* We do not have a match */
1965             return FALSE;
1966         }
1967     }
1968 
1969     /* We have a match */
1970     return TRUE;
1971 }
1972 
1973 /*
1974  * The events enumerator thread.
1975  */
1976 static DWORD WINAPI
1977 EnumEventsThread(IN LPVOID lpParameter)
1978 {
1979     PEVENTLOGFILTER EventLogFilter = (PEVENTLOGFILTER)lpParameter;
1980     PEVENTLOG EventLog;
1981 
1982     ULONG LogIndex;
1983     HANDLE hEventLog;
1984     PEVENTLOGRECORD pEvlr = NULL;
1985     PBYTE pEvlrEnd;
1986     PBYTE pEvlrBuffer;
1987     DWORD dwWanted, dwRead, dwNeeded, dwStatus = ERROR_SUCCESS;
1988     DWORD dwTotalRecords = 0, dwCurrentRecord = 0;
1989     DWORD dwFlags, dwMaxLength;
1990     size_t cchRemaining;
1991     LPWSTR lpszSourceName;
1992     LPWSTR lpszComputerName;
1993     BOOL bResult = TRUE; /* Read succeeded */
1994     HANDLE hProcessHeap = GetProcessHeap();
1995     PSID pLastSid = NULL;
1996 
1997     UINT uStep = 0, uStepAt = 0, uPos = 0;
1998 
1999     WCHAR szWindowTitle[MAX_PATH];
2000     WCHAR szStatusText[MAX_PATH];
2001     WCHAR szLocalDate[MAX_PATH];
2002     WCHAR szLocalTime[MAX_PATH];
2003     WCHAR szEventID[MAX_PATH];
2004     WCHAR szEventTypeText[MAX_LOADSTRING];
2005     WCHAR szCategoryID[MAX_PATH];
2006     WCHAR szUsername[MAX_PATH];
2007     WCHAR szNoUsername[MAX_PATH];
2008     WCHAR szCategory[MAX_PATH];
2009     WCHAR szNoCategory[MAX_PATH];
2010     PWCHAR lpTitleTemplateEnd;
2011 
2012     SYSTEMTIME time;
2013     LVITEMW lviEventItem;
2014 
2015     /* Save the current event log filter globally */
2016     EventLogFilter_AddRef(EventLogFilter);
2017     ActiveFilter = EventLogFilter;
2018 
2019 
2020     /** HACK!! **/
2021     EventLog = EventLogFilter->EventLogs[0];
2022 
2023     // FIXME: Use something else instead of EventLog->LogName !!
2024 
2025     /*
2026      * Use a different formatting, whether the event log filter holds
2027      * only one log, or many logs (the latter case is WIP TODO!)
2028      */
2029     if (EventLogFilter->NumOfEventLogs <= 1)
2030     {
2031         StringCchPrintfExW(szWindowTitle,
2032                            ARRAYSIZE(szWindowTitle),
2033                            &lpTitleTemplateEnd,
2034                            &cchRemaining,
2035                            0,
2036                            szTitleTemplate, szTitle, EventLog->LogName); /* i = number of characters written */
2037         dwMaxLength = (DWORD)cchRemaining;
2038         if (!EventLog->ComputerName)
2039             GetComputerNameW(lpTitleTemplateEnd, &dwMaxLength);
2040         else
2041             StringCchCopyW(lpTitleTemplateEnd, dwMaxLength, EventLog->ComputerName);
2042 
2043         StringCbPrintfW(szStatusText,
2044                         sizeof(szStatusText),
2045                         szStatusBarTemplate,
2046                         EventLog->LogName,
2047                         0,
2048                         0);
2049     }
2050     else
2051     {
2052         // TODO: Use a different title & implement filtering for multi-log filters !!
2053         // (EventLogFilter->NumOfEventLogs > 1)
2054         MessageBoxW(hwndMainWindow,
2055                     L"Many-logs filtering is not implemented yet!!",
2056                     L"Event Log",
2057                     MB_OK | MB_ICONINFORMATION);
2058     }
2059 
2060     /* Set the window title */
2061     SetWindowTextW(hwndMainWindow, szWindowTitle);
2062 
2063     /* Update the status bar */
2064     StatusBar_SetText(hwndStatus, 0, szStatusText);
2065 
2066 
2067     /* Disable list view redraw */
2068     SendMessageW(hwndListView, WM_SETREDRAW, FALSE, 0);
2069 
2070     /* Clear the list view and free the cached records */
2071     ListView_DeleteAllItems(hwndListView);
2072     FreeRecords();
2073 
2074     SendMessageW(hwndListView, LVM_PROGRESS, 0, TRUE);
2075     ProgressBar_SetRange(hwndStatusProgress, 0);
2076     StatusBar_SetText(hwndStatus, 0, NULL);
2077     ShowWindow(hwndStatusProgress, SW_SHOW);
2078 
2079     /* Do a loop over the logs enumerated in the filter */
2080     // FIXME: For now we only support 1 event log per filter!
2081     LogIndex = 0;
2082     // for (LogIndex = 0; LogIndex < EventLogFilter->NumOfEventLogs; ++LogIndex)
2083     {
2084 
2085     EventLog = EventLogFilter->EventLogs[LogIndex];
2086 
2087     /* Open the event log */
2088     if (EventLog->Permanent)
2089         hEventLog = OpenEventLogW(EventLog->ComputerName, EventLog->LogName);
2090     else
2091         hEventLog = OpenBackupEventLogW(EventLog->ComputerName, EventLog->LogName); // FileName
2092 
2093     if (hEventLog == NULL)
2094     {
2095         ShowWin32Error(GetLastError());
2096         goto Cleanup;
2097     }
2098 
2099     // GetOldestEventLogRecord(hEventLog, &dwThisRecord);
2100 
2101     /* Get the total number of event log records */
2102     GetNumberOfEventLogRecords(hEventLog, &dwTotalRecords);
2103 
2104     if (dwTotalRecords > 0)
2105     {
2106         EnableMenuItem(hMainMenu, IDM_CLEAR_EVENTS, MF_BYCOMMAND | MF_ENABLED);
2107         EnableMenuItem(hMainMenu, IDM_SAVE_EVENTLOG, MF_BYCOMMAND | MF_ENABLED);
2108     }
2109     else
2110     {
2111         EnableMenuItem(hMainMenu, IDM_CLEAR_EVENTS, MF_BYCOMMAND | MF_GRAYED);
2112         EnableMenuItem(hMainMenu, IDM_SAVE_EVENTLOG, MF_BYCOMMAND | MF_GRAYED);
2113     }
2114 
2115     /* Set up the event records cache */
2116     g_RecordPtrs = HeapAlloc(hProcessHeap, HEAP_ZERO_MEMORY, dwTotalRecords * sizeof(*g_RecordPtrs));
2117     if (!g_RecordPtrs)
2118     {
2119         // ShowWin32Error(GetLastError());
2120         goto Quit;
2121     }
2122     g_TotalRecords = dwTotalRecords;
2123 
2124     if (WaitForSingleObject(hStopEnumEvent, 0) == WAIT_OBJECT_0)
2125         goto Quit;
2126 
2127     LoadStringW(hInst, IDS_NOT_AVAILABLE, szNoUsername, ARRAYSIZE(szNoUsername));
2128     LoadStringW(hInst, IDS_NONE, szNoCategory, ARRAYSIZE(szNoCategory));
2129 
2130     ProgressBar_SetRange(hwndStatusProgress, MAKELPARAM(0, 100));
2131     uStepAt = (dwTotalRecords / 100) + 1;
2132 
2133     dwFlags = EVENTLOG_SEQUENTIAL_READ | (Settings.bNewestEventsFirst ? EVENTLOG_FORWARDS_READ : EVENTLOG_BACKWARDS_READ);
2134 
2135     /* 0x7ffff is the maximum buffer size ReadEventLog will accept */
2136     dwWanted = 0x7ffff;
2137     pEvlr = HeapAlloc(hProcessHeap, 0, dwWanted);
2138 
2139     if (!pEvlr)
2140         goto Quit;
2141 
2142     while (dwStatus == ERROR_SUCCESS)
2143     {
2144         bResult =  ReadEventLogW(hEventLog, dwFlags, 0, pEvlr, dwWanted, &dwRead, &dwNeeded);
2145         dwStatus = GetLastError();
2146 
2147         if (!bResult && dwStatus == ERROR_INSUFFICIENT_BUFFER)
2148         {
2149             pEvlr = HeapReAlloc(hProcessHeap, 0, pEvlr, dwNeeded);
2150             dwWanted = dwNeeded;
2151 
2152             if (!pEvlr)
2153                 break;
2154 
2155             bResult =  ReadEventLogW(hEventLog, dwFlags, 0, pEvlr, dwNeeded, &dwRead, &dwNeeded);
2156 
2157             if (!bResult)
2158                 break;
2159         }
2160         else if (!bResult)
2161         {
2162             /* Exit on other errors (ERROR_HANDLE_EOF) */
2163             break;
2164         }
2165 
2166         pEvlrBuffer = (LPBYTE)pEvlr;
2167         pEvlrEnd = pEvlrBuffer + dwRead;
2168 
2169         while (pEvlrBuffer < pEvlrEnd)
2170         {
2171             PEVENTLOGRECORD pEvlrTmp = (PEVENTLOGRECORD)pEvlrBuffer;
2172             PWSTR lpszUsername, lpszCategoryName;
2173             g_RecordPtrs[dwCurrentRecord] = NULL;
2174 
2175             // ProgressBar_StepIt(hwndStatusProgress);
2176             uStep++;
2177             if (uStep % uStepAt == 0)
2178             {
2179                 ++uPos;
2180                 ProgressBar_SetPos(hwndStatusProgress, uPos);
2181             }
2182 
2183            if (WaitForSingleObject(hStopEnumEvent, 0) == WAIT_OBJECT_0)
2184                 goto Quit;
2185 
2186             /* Filter by event type */
2187             if (!FilterByType(EventLogFilter, pEvlrTmp))
2188                 goto SkipEvent;
2189 
2190             /* Get the event source name and filter it */
2191             lpszSourceName = (LPWSTR)(pEvlrBuffer + sizeof(EVENTLOGRECORD));
2192             if (!FilterByString(EventLogFilter->Sources, lpszSourceName))
2193                 goto SkipEvent;
2194 
2195             /* Get the computer name and filter it */
2196             lpszComputerName = (LPWSTR)(pEvlrBuffer + sizeof(EVENTLOGRECORD) + (wcslen(lpszSourceName) + 1) * sizeof(WCHAR));
2197             if (!FilterByString(EventLogFilter->ComputerNames, lpszComputerName))
2198                 goto SkipEvent;
2199 
2200             /* Compute the event time */
2201             EventTimeToSystemTime(pEvlrTmp->TimeWritten, &time);
2202             GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &time, NULL, szLocalDate, ARRAYSIZE(szLocalDate));
2203             GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &time, NULL, szLocalTime, ARRAYSIZE(szLocalTime));
2204 
2205             /* Get the username that generated the event, and filter it */
2206             lpszUsername = GetEventUserName(pEvlrTmp, &pLastSid, szUsername) ? szUsername : szNoUsername;
2207 
2208             if (!FilterByString(EventLogFilter->Users, lpszUsername))
2209                 goto SkipEvent;
2210 
2211             // TODO: Filter by event ID and category
2212             GetEventType(pEvlrTmp->EventType, szEventTypeText);
2213 
2214             lpszCategoryName = GetEventCategory(EventLog->LogName, lpszSourceName, pEvlrTmp, szCategory) ? szCategory : szNoCategory;
2215 
2216             StringCbPrintfW(szEventID, sizeof(szEventID), L"%u", (pEvlrTmp->EventID & 0xFFFF));
2217             StringCbPrintfW(szCategoryID, sizeof(szCategoryID), L"%u", pEvlrTmp->EventCategory);
2218 
2219             g_RecordPtrs[dwCurrentRecord] = HeapAlloc(hProcessHeap, 0, pEvlrTmp->Length);
2220             RtlCopyMemory(g_RecordPtrs[dwCurrentRecord], pEvlrTmp, pEvlrTmp->Length);
2221 
2222             lviEventItem.mask = LVIF_IMAGE | LVIF_TEXT | LVIF_PARAM;
2223             lviEventItem.iItem = 0;
2224             lviEventItem.iSubItem = 0;
2225             lviEventItem.lParam = (LPARAM)g_RecordPtrs[dwCurrentRecord];
2226             lviEventItem.pszText = szEventTypeText;
2227 
2228             switch (pEvlrTmp->EventType)
2229             {
2230                 case EVENTLOG_SUCCESS:
2231                 case EVENTLOG_INFORMATION_TYPE:
2232                     lviEventItem.iImage = 0;
2233                     break;
2234 
2235                 case EVENTLOG_WARNING_TYPE:
2236                     lviEventItem.iImage = 1;
2237                     break;
2238 
2239                 case EVENTLOG_ERROR_TYPE:
2240                     lviEventItem.iImage = 2;
2241                     break;
2242 
2243                 case EVENTLOG_AUDIT_SUCCESS:
2244                     lviEventItem.iImage = 3;
2245                     break;
2246 
2247                 case EVENTLOG_AUDIT_FAILURE:
2248                     lviEventItem.iImage = 4;
2249                     break;
2250             }
2251 
2252             lviEventItem.iItem = ListView_InsertItem(hwndListView, &lviEventItem);
2253 
2254             ListView_SetItemText(hwndListView, lviEventItem.iItem, 1, szLocalDate);
2255             ListView_SetItemText(hwndListView, lviEventItem.iItem, 2, szLocalTime);
2256             ListView_SetItemText(hwndListView, lviEventItem.iItem, 3, lpszSourceName);
2257             ListView_SetItemText(hwndListView, lviEventItem.iItem, 4, lpszCategoryName);
2258             ListView_SetItemText(hwndListView, lviEventItem.iItem, 5, szEventID);
2259             ListView_SetItemText(hwndListView, lviEventItem.iItem, 6, lpszUsername);
2260             ListView_SetItemText(hwndListView, lviEventItem.iItem, 7, lpszComputerName);
2261 
2262 SkipEvent:
2263             pEvlrBuffer += pEvlrTmp->Length;
2264             dwCurrentRecord++;
2265         }
2266     }
2267 
2268 Quit:
2269 
2270     if (pEvlr)
2271         HeapFree(hProcessHeap, 0, pEvlr);
2272 
2273     /* Close the event log */
2274     CloseEventLog(hEventLog);
2275 
2276     } // end-for (LogIndex)
2277 
2278     /* All events loaded */
2279 
2280 Cleanup:
2281 
2282     ShowWindow(hwndStatusProgress, SW_HIDE);
2283     SendMessageW(hwndListView, LVM_PROGRESS, 0, FALSE);
2284 
2285     // FIXME: Use something else instead of EventLog->LogName !!
2286 
2287     /*
2288      * Use a different formatting, whether the event log filter holds
2289      * only one log, or many logs (the latter case is WIP TODO!)
2290      */
2291     if (EventLogFilter->NumOfEventLogs <= 1)
2292     {
2293         StringCbPrintfW(szStatusText,
2294                         sizeof(szStatusText),
2295                         szStatusBarTemplate,
2296                         EventLog->LogName,
2297                         dwTotalRecords,
2298                         ListView_GetItemCount(hwndListView));
2299     }
2300     else
2301     {
2302         // TODO: Use a different title & implement filtering for multi-log filters !!
2303         // (EventLogFilter->NumOfEventLogs > 1)
2304     }
2305 
2306     /* Update the status bar */
2307     StatusBar_SetText(hwndStatus, 0, szStatusText);
2308 
2309     /* Resume list view redraw */
2310     SendMessageW(hwndListView, WM_SETREDRAW, TRUE, 0);
2311 
2312     EventLogFilter_Release(EventLogFilter);
2313 
2314     CloseHandle(hStopEnumEvent);
2315     InterlockedExchangePointer((PVOID*)&hStopEnumEvent, NULL);
2316 
2317     return 0;
2318 }
2319 
2320 /*
2321  * The purpose of this thread is to serialize the creation of the events
2322  * enumeration thread, since the Event Log Viewer currently only supports
2323  * one view, one event list, one enumeration.
2324  */
2325 static DWORD WINAPI
2326 StartStopEnumEventsThread(IN LPVOID lpParameter)
2327 {
2328     HANDLE WaitHandles[2];
2329     DWORD  WaitResult;
2330 
2331     WaitHandles[0] = hStartStopEnumEvent; // End-of-application event
2332     WaitHandles[1] = hStartEnumEvent;     // Command event
2333 
2334     while (TRUE)
2335     {
2336         WaitResult = WaitForMultipleObjects(ARRAYSIZE(WaitHandles),
2337                                             WaitHandles,
2338                                             FALSE, // WaitAny
2339                                             INFINITE);
2340         switch (WaitResult)
2341         {
2342             case WAIT_OBJECT_0 + 0:
2343             {
2344                 /* End-of-application event signaled, quit this thread */
2345 
2346                 /* Stop the previous enumeration */
2347                 if (hEnumEventsThread)
2348                 {
2349                     if (hStopEnumEvent)
2350                     {
2351                         SetEvent(hStopEnumEvent);
2352                         WaitForSingleObject(hEnumEventsThread, INFINITE);
2353                         // NOTE: The following is done by the enumeration thread just before terminating.
2354                         // hStopEnumEvent = NULL;
2355                     }
2356 
2357                     CloseHandle(hEnumEventsThread);
2358                     hEnumEventsThread = NULL;
2359                 }
2360 
2361                 /* Clear the list view and free the cached records */
2362                 ListView_DeleteAllItems(hwndListView);
2363                 FreeRecords();
2364 
2365                 /* Reset the active filter */
2366                 ActiveFilter = NULL;
2367 
2368                 return 0;
2369             }
2370 
2371             case WAIT_OBJECT_0 + 1:
2372             {
2373                 /* Restart a new enumeration if needed */
2374                 PEVENTLOGFILTER EventLogFilter;
2375 
2376                 /* Stop the previous enumeration */
2377                 if (hEnumEventsThread)
2378                 {
2379                     if (hStopEnumEvent)
2380                     {
2381                         SetEvent(hStopEnumEvent);
2382                         WaitForSingleObject(hEnumEventsThread, INFINITE);
2383                         // NOTE: The following is done by the enumeration thread just before terminating.
2384                         // hStopEnumEvent = NULL;
2385                     }
2386 
2387                     CloseHandle(hEnumEventsThread);
2388                     hEnumEventsThread = NULL;
2389                 }
2390 
2391                 /* Clear the list view and free the cached records */
2392                 ListView_DeleteAllItems(hwndListView);
2393                 FreeRecords();
2394 
2395                 /* Reset the active filter */
2396                 ActiveFilter = NULL;
2397 
2398                 EventLogFilter = InterlockedExchangePointer((PVOID*)&EnumFilter, NULL);
2399                 if (!EventLogFilter)
2400                     break;
2401 
2402                 // Manual-reset event
2403                 hStopEnumEvent = CreateEventW(NULL, TRUE, FALSE, NULL);
2404                 if (!hStopEnumEvent)
2405                     break;
2406 
2407                 hEnumEventsThread = CreateThread(NULL,
2408                                                  0,
2409                                                  EnumEventsThread,
2410                                                  (LPVOID)EventLogFilter,
2411                                                  CREATE_SUSPENDED,
2412                                                  NULL);
2413                 if (!hEnumEventsThread)
2414                 {
2415                     CloseHandle(hStopEnumEvent);
2416                     hStopEnumEvent = NULL;
2417                     break;
2418                 }
2419                 // CloseHandle(hEnumEventsThread);
2420                 ResumeThread(hEnumEventsThread);
2421 
2422                 break;
2423             }
2424 
2425             default:
2426             {
2427                 /* Unknown command, must never go there! */
2428                 return GetLastError();
2429             }
2430         }
2431     }
2432 
2433     return 0;
2434 }
2435 
2436 VOID
2437 EnumEvents(IN PEVENTLOGFILTER EventLogFilter)
2438 {
2439     /* Signal the enumerator thread we want to enumerate events */
2440     InterlockedExchangePointer((PVOID*)&EnumFilter, EventLogFilter);
2441     SetEvent(hStartEnumEvent);
2442     return;
2443 }
2444 
2445 
2446 PEVENTLOGFILTER
2447 GetSelectedFilter(OUT HTREEITEM* phti OPTIONAL)
2448 {
2449     TVITEMEXW tvItemEx;
2450     HTREEITEM hti;
2451 
2452     if (phti)
2453         *phti = NULL;
2454 
2455     /* Get index of selected item */
2456     hti = TreeView_GetSelection(hwndTreeView);
2457     if (hti == NULL)
2458         return NULL; // No filter
2459 
2460     tvItemEx.mask = TVIF_PARAM;
2461     tvItemEx.hItem = hti;
2462 
2463     TreeView_GetItem(hwndTreeView, &tvItemEx);
2464 
2465     if (phti)
2466         *phti = tvItemEx.hItem;
2467 
2468     return (PEVENTLOGFILTER)tvItemEx.lParam;
2469 }
2470 
2471 
2472 VOID
2473 OpenUserEventLogFile(IN LPCWSTR lpszFileName)
2474 {
2475     WIN32_FIND_DATAW FindData;
2476     HANDLE hFind;
2477     PEVENTLOG EventLog;
2478     PEVENTLOGFILTER EventLogFilter;
2479     SIZE_T cchFileName;
2480     HTREEITEM hItem = NULL;
2481 
2482     /* Check whether the file actually exists */
2483     hFind = FindFirstFileW(lpszFileName, &FindData);
2484     if (hFind == INVALID_HANDLE_VALUE)
2485     {
2486         ShowWin32Error(GetLastError());
2487         return;
2488     }
2489     FindClose(hFind);
2490 
2491     /* Allocate a new event log entry */
2492     EventLog = AllocEventLog(NULL, lpszFileName, FALSE);
2493     if (EventLog == NULL)
2494     {
2495         ShowWin32Error(ERROR_NOT_ENOUGH_MEMORY);
2496         return;
2497     }
2498 
2499     /* Allocate a new event log filter entry for this event log */
2500     EventLogFilter = AllocEventLogFilter(// LogName,
2501                                          TRUE, TRUE, TRUE, TRUE, TRUE,
2502                                          NULL, NULL, NULL,
2503                                          1, &EventLog);
2504     if (EventLogFilter == NULL)
2505     {
2506         ShowWin32Error(ERROR_NOT_ENOUGH_MEMORY);
2507         EventLog_Free(EventLog);
2508         return;
2509     }
2510 
2511     /* Add the event log and the filter into their lists */
2512     InsertTailList(&EventLogList, &EventLog->ListEntry);
2513     InsertTailList(&EventLogFilterList, &EventLogFilter->ListEntry);
2514 
2515     /* Retrieve and cache the event log file */
2516     cchFileName = wcslen(lpszFileName) + 1;
2517     EventLog->FileName = HeapAlloc(GetProcessHeap(), 0, cchFileName * sizeof(WCHAR));
2518     if (EventLog->FileName)
2519         StringCchCopyW(EventLog->FileName, cchFileName, lpszFileName);
2520 
2521     hItem = TreeViewAddItem(hwndTreeView, htiUserLogs,
2522                             (LPWSTR)lpszFileName,
2523                             2, 3, (LPARAM)EventLogFilter);
2524 
2525     /* Select the event log */
2526     if (hItem)
2527     {
2528         // TreeView_Expand(hwndTreeView, htiUserLogs, TVE_EXPAND);
2529         TreeView_SelectItem(hwndTreeView, hItem);
2530         TreeView_EnsureVisible(hwndTreeView, hItem);
2531     }
2532     InvalidateRect(hwndTreeView, NULL, FALSE);
2533     SetFocus(hwndTreeView);
2534 }
2535 
2536 VOID
2537 OpenUserEventLog(VOID)
2538 {
2539     WCHAR szFileName[MAX_PATH];
2540 
2541     ZeroMemory(szFileName, sizeof(szFileName));
2542 
2543     sfn.lpstrFile = szFileName;
2544     sfn.nMaxFile  = ARRAYSIZE(szFileName);
2545 
2546     if (!GetOpenFileNameW(&sfn))
2547         return;
2548     sfn.lpstrFile[sfn.nMaxFile-1] = UNICODE_NULL;
2549 
2550     OpenUserEventLogFile(sfn.lpstrFile);
2551 }
2552 
2553 VOID
2554 SaveEventLog(IN PEVENTLOGFILTER EventLogFilter)
2555 {
2556     PEVENTLOG EventLog;
2557     HANDLE hEventLog;
2558     WCHAR szFileName[MAX_PATH];
2559 
2560     /* Bail out if there is no available filter */
2561     if (!EventLogFilter)
2562         return;
2563 
2564     ZeroMemory(szFileName, sizeof(szFileName));
2565 
2566     sfn.lpstrFile = szFileName;
2567     sfn.nMaxFile  = ARRAYSIZE(szFileName);
2568 
2569     if (!GetSaveFileNameW(&sfn))
2570         return;
2571 
2572     EventLogFilter_AddRef(EventLogFilter);
2573 
2574     EventLog = EventLogFilter->EventLogs[0];
2575     hEventLog = OpenEventLogW(EventLog->ComputerName, EventLog->LogName);
2576 
2577     EventLogFilter_Release(EventLogFilter);
2578 
2579     if (!hEventLog)
2580     {
2581         ShowWin32Error(GetLastError());
2582         return;
2583     }
2584 
2585     if (!BackupEventLogW(hEventLog, szFileName))
2586         ShowWin32Error(GetLastError());
2587 
2588     CloseEventLog(hEventLog);
2589 }
2590 
2591 VOID
2592 CloseUserEventLog(IN PEVENTLOGFILTER EventLogFilter, IN HTREEITEM hti)
2593 {
2594     /* Bail out if there is no available filter */
2595     if (!EventLogFilter)
2596         return;
2597 
2598     if (InterlockedCompareExchangePointer((PVOID*)&ActiveFilter, NULL, NULL) == EventLogFilter)
2599     {
2600         /* Signal the enumerator thread we want to stop enumerating events */
2601         // EnumEvents(NULL);
2602         InterlockedExchangePointer((PVOID*)&EnumFilter, NULL);
2603         SetEvent(hStartEnumEvent);
2604     }
2605 
2606     /*
2607      * The deletion of the item automatically triggers a TVN_SELCHANGED
2608      * notification, that will reset the ActiveFilter (in case the item
2609      * selected is a filter). Otherwise we reset it there.
2610      */
2611     TreeView_DeleteItem(hwndTreeView, hti);
2612 
2613     /* Remove the filter from the list */
2614     RemoveEntryList(&EventLogFilter->ListEntry);
2615     EventLogFilter_Release(EventLogFilter);
2616 
2617     // /* Select the default event log */
2618     // // TreeView_Expand(hwndTreeView, htiUserLogs, TVE_EXPAND);
2619     // TreeView_SelectItem(hwndTreeView, hItem);
2620     // TreeView_EnsureVisible(hwndTreeView, hItem);
2621     InvalidateRect(hwndTreeView, NULL, FALSE);
2622     SetFocus(hwndTreeView);
2623 }
2624 
2625 
2626 BOOL
2627 ClearEvents(IN PEVENTLOGFILTER EventLogFilter)
2628 {
2629     BOOL Success;
2630     PEVENTLOG EventLog;
2631     HANDLE hEventLog;
2632     WCHAR szFileName[MAX_PATH];
2633     WCHAR szMessage[MAX_LOADSTRING];
2634 
2635     /* Bail out if there is no available filter */
2636     if (!EventLogFilter)
2637         return FALSE;
2638 
2639     ZeroMemory(szFileName, sizeof(szFileName));
2640     ZeroMemory(szMessage, sizeof(szMessage));
2641 
2642     LoadStringW(hInst, IDS_CLEAREVENTS_MSG, szMessage, ARRAYSIZE(szMessage));
2643 
2644     sfn.lpstrFile = szFileName;
2645     sfn.nMaxFile  = ARRAYSIZE(szFileName);
2646 
2647     switch (MessageBoxW(hwndMainWindow, szMessage, szTitle, MB_YESNOCANCEL | MB_ICONINFORMATION))
2648     {
2649         case IDCANCEL:
2650             return FALSE;
2651 
2652         case IDNO:
2653             sfn.lpstrFile = NULL;
2654             break;
2655 
2656         case IDYES:
2657             if (!GetSaveFileNameW(&sfn))
2658                 return FALSE;
2659             break;
2660     }
2661 
2662     EventLogFilter_AddRef(EventLogFilter);
2663 
2664     EventLog = EventLogFilter->EventLogs[0];
2665     hEventLog = OpenEventLogW(EventLog->ComputerName, EventLog->LogName);
2666 
2667     EventLogFilter_Release(EventLogFilter);
2668 
2669     if (!hEventLog)
2670     {
2671         ShowWin32Error(GetLastError());
2672         return FALSE;
2673     }
2674 
2675     Success = ClearEventLogW(hEventLog, sfn.lpstrFile);
2676     if (!Success)
2677         ShowWin32Error(GetLastError());
2678 
2679     CloseEventLog(hEventLog);
2680     return Success;
2681 }
2682 
2683 
2684 VOID
2685 Refresh(IN PEVENTLOGFILTER EventLogFilter)
2686 {
2687     /* Bail out if there is no available filter */
2688     if (!EventLogFilter)
2689         return;
2690 
2691     /* Reenumerate the events through the filter */
2692     EnumEvents(EventLogFilter);
2693 }
2694 
2695 
2696 ATOM
2697 MyRegisterClass(HINSTANCE hInstance)
2698 {
2699     WNDCLASSEXW wcex;
2700 
2701     wcex.cbSize = sizeof(wcex);
2702     wcex.style = 0;
2703     wcex.lpfnWndProc = WndProc;
2704     wcex.cbClsExtra = 0;
2705     wcex.cbWndExtra = 0;
2706     wcex.hInstance = hInstance;
2707     wcex.hIcon = LoadIconW(hInstance, MAKEINTRESOURCEW(IDI_EVENTVWR));
2708     wcex.hCursor = LoadCursorW(NULL, MAKEINTRESOURCEW(IDC_ARROW));
2709     wcex.hbrBackground = (HBRUSH)(COLOR_3DFACE + 1); // COLOR_WINDOW + 1
2710     wcex.lpszMenuName = MAKEINTRESOURCEW(IDM_EVENTVWR);
2711     wcex.lpszClassName = EVENTVWR_WNDCLASS;
2712     wcex.hIconSm = (HICON)LoadImageW(hInstance,
2713                                      MAKEINTRESOURCEW(IDI_EVENTVWR),
2714                                      IMAGE_ICON,
2715                                      16,
2716                                      16,
2717                                      LR_SHARED);
2718 
2719     return RegisterClassExW(&wcex);
2720 }
2721 
2722 
2723 BOOL
2724 GetDisplayNameFileAndID(IN LPCWSTR lpLogName,
2725                         OUT PWCHAR lpModuleName, // TODO: Add IN DWORD BufLen
2726                         OUT PDWORD pdwMessageID)
2727 {
2728     BOOL Success = FALSE;
2729     LONG Result;
2730     HKEY hLogKey;
2731     WCHAR *KeyPath;
2732     SIZE_T cbKeyPath;
2733     DWORD dwType, cbData;
2734     DWORD dwMessageID = 0;
2735     WCHAR szModuleName[MAX_PATH];
2736 
2737     /* Use a default value for the message ID */
2738     *pdwMessageID = 0;
2739 
2740     cbKeyPath = (wcslen(EVENTLOG_BASE_KEY) + wcslen(lpLogName) + 1) * sizeof(WCHAR);
2741     KeyPath = HeapAlloc(GetProcessHeap(), 0, cbKeyPath);
2742     if (!KeyPath)
2743     {
2744         ShowWin32Error(ERROR_NOT_ENOUGH_MEMORY);
2745         return FALSE;
2746     }
2747 
2748     StringCbCopyW(KeyPath, cbKeyPath, EVENTLOG_BASE_KEY);
2749     StringCbCatW(KeyPath, cbKeyPath, lpLogName);
2750 
2751     Result = RegOpenKeyExW(hkMachine, KeyPath, 0, KEY_QUERY_VALUE, &hLogKey);
2752     HeapFree(GetProcessHeap(), 0, KeyPath);
2753     if (Result != ERROR_SUCCESS)
2754     {
2755         ShowWin32Error(Result);
2756         return FALSE;
2757     }
2758 
2759     cbData = sizeof(szModuleName);
2760     Result = RegQueryValueExW(hLogKey,
2761                               L"DisplayNameFile",
2762                               NULL,
2763                               &dwType,
2764                               (LPBYTE)szModuleName,
2765                               &cbData);
2766     if ((Result != ERROR_SUCCESS) || (dwType != REG_EXPAND_SZ && dwType != REG_SZ))
2767     {
2768         szModuleName[0] = UNICODE_NULL;
2769     }
2770     else
2771     {
2772         /* NULL-terminate the string and expand it */
2773         szModuleName[cbData / sizeof(WCHAR) - 1] = UNICODE_NULL;
2774         GetExpandedFilePathName(lpComputerName, szModuleName, lpModuleName, ARRAYSIZE(szModuleName));
2775         Success = TRUE;
2776     }
2777 
2778     /*
2779      * If we have a 'DisplayNameFile', query for 'DisplayNameID';
2780      * otherwise it's not really useful. 'DisplayNameID' is optional.
2781      */
2782     if (Success)
2783     {
2784         cbData = sizeof(dwMessageID);
2785         Result = RegQueryValueExW(hLogKey,
2786                                   L"DisplayNameID",
2787                                   NULL,
2788                                   &dwType,
2789                                   (LPBYTE)&dwMessageID,
2790                                   &cbData);
2791         if ((Result != ERROR_SUCCESS) || (dwType != REG_DWORD))
2792             dwMessageID = 0;
2793 
2794         *pdwMessageID = dwMessageID;
2795     }
2796 
2797     RegCloseKey(hLogKey);
2798 
2799     return Success;
2800 }
2801 
2802 
2803 VOID
2804 BuildLogListAndFilterList(IN LPCWSTR lpComputerName)
2805 {
2806     LONG Result;
2807     HKEY hEventLogKey, hLogKey;
2808     DWORD dwNumLogs = 0;
2809     DWORD dwIndex, dwMaxKeyLength;
2810     DWORD dwType;
2811     PEVENTLOG EventLog;
2812     PEVENTLOGFILTER EventLogFilter;
2813     LPWSTR LogName = NULL;
2814     WCHAR szModuleName[MAX_PATH];
2815     DWORD lpcName;
2816     DWORD dwMessageID;
2817     LPWSTR lpDisplayName;
2818     HTREEITEM hRootNode = NULL, hItem = NULL, hItemDefault = NULL;
2819 
2820     if (hkMachine && hkMachine != HKEY_LOCAL_MACHINE)
2821     {
2822         /* We are connected to some other computer, close the old connection */
2823         RegCloseKey(hkMachine);
2824         hkMachine = NULL;
2825     }
2826     if (!lpComputerName || !*lpComputerName)
2827     {
2828         /* Use the local computer registry */
2829         hkMachine = HKEY_LOCAL_MACHINE;
2830     }
2831     else
2832     {
2833         /* Connect to the remote computer registry */
2834         Result = RegConnectRegistry(lpComputerName, HKEY_LOCAL_MACHINE, &hkMachine);
2835         if (Result != ERROR_SUCCESS)
2836         {
2837             /* Connection failed, display a message and bail out */
2838             hkMachine = NULL;
2839             ShowWin32Error(GetLastError());
2840             return;
2841         }
2842     }
2843 
2844     /* Open the EventLog key */
2845     Result = RegOpenKeyExW(hkMachine, EVENTLOG_BASE_KEY, 0, KEY_READ, &hEventLogKey);
2846     if (Result != ERROR_SUCCESS)
2847     {
2848         return;
2849     }
2850 
2851     /* Retrieve the number of event logs enumerated as registry keys */
2852     Result = RegQueryInfoKeyW(hEventLogKey, NULL, NULL, NULL, &dwNumLogs, &dwMaxKeyLength,
2853                               NULL, NULL, NULL, NULL, NULL, NULL);
2854     if (Result != ERROR_SUCCESS)
2855     {
2856         goto Quit;
2857     }
2858     if (!dwNumLogs)
2859         goto Quit;
2860 
2861     /* Take the NULL terminator into account */
2862     ++dwMaxKeyLength;
2863 
2864     /* Allocate the temporary buffer */
2865     LogName = HeapAlloc(GetProcessHeap(), 0, dwMaxKeyLength * sizeof(WCHAR));
2866     if (!LogName)
2867         goto Quit;
2868 
2869     /* Enumerate and retrieve each event log name */
2870     for (dwIndex = 0; dwIndex < dwNumLogs; dwIndex++)
2871     {
2872         lpcName = dwMaxKeyLength;
2873         Result = RegEnumKeyExW(hEventLogKey, dwIndex, LogName, &lpcName, NULL, NULL, NULL, NULL);
2874         if (Result != ERROR_SUCCESS)
2875             continue;
2876 
2877         /* Take the NULL terminator into account */
2878         ++lpcName;
2879 
2880         /* Allocate a new event log entry */
2881         EventLog = AllocEventLog(lpComputerName, LogName, TRUE);
2882         if (EventLog == NULL)
2883             continue;
2884 
2885         /* Allocate a new event log filter entry for this event log */
2886         EventLogFilter = AllocEventLogFilter(// LogName,
2887                                              TRUE, TRUE, TRUE, TRUE, TRUE,
2888                                              NULL, NULL, NULL,
2889                                              1, &EventLog);
2890         if (EventLogFilter == NULL)
2891         {
2892             EventLog_Free(EventLog);
2893             continue;
2894         }
2895 
2896         /* Add the event log and the filter into their lists */
2897         InsertTailList(&EventLogList, &EventLog->ListEntry);
2898         InsertTailList(&EventLogFilterList, &EventLogFilter->ListEntry);
2899 
2900         EventLog->FileName = NULL;
2901 
2902         /* Retrieve and cache the event log file */
2903         Result = RegOpenKeyExW(hEventLogKey,
2904                                LogName,
2905                                0,
2906                                KEY_QUERY_VALUE,
2907                                &hLogKey);
2908         if (Result == ERROR_SUCCESS)
2909         {
2910             lpcName = 0;
2911             Result = RegQueryValueExW(hLogKey,
2912                                       L"File",
2913                                       NULL,
2914                                       &dwType,
2915                                       NULL,
2916                                       &lpcName);
2917             if ((Result != ERROR_SUCCESS) || (dwType != REG_EXPAND_SZ && dwType != REG_SZ))
2918             {
2919                 // Windows' EventLog uses some kind of default value, we do not.
2920                 EventLog->FileName = NULL;
2921             }
2922             else
2923             {
2924                 lpcName = ROUND_DOWN(lpcName, sizeof(WCHAR));
2925                 EventLog->FileName = HeapAlloc(GetProcessHeap(), 0, lpcName);
2926                 if (EventLog->FileName)
2927                 {
2928                     Result = RegQueryValueExW(hLogKey,
2929                                               L"File",
2930                                               NULL,
2931                                               &dwType,
2932                                               (LPBYTE)EventLog->FileName,
2933                                               &lpcName);
2934                     if (Result != ERROR_SUCCESS)
2935                     {
2936                         HeapFree(GetProcessHeap(), 0, EventLog->FileName);
2937                         EventLog->FileName = NULL;
2938                     }
2939                     else
2940                     {
2941                         EventLog->FileName[lpcName / sizeof(WCHAR) - 1] = UNICODE_NULL;
2942                     }
2943                 }
2944             }
2945 
2946             RegCloseKey(hLogKey);
2947         }
2948 
2949         /* Get the display name for the event log */
2950         lpDisplayName = NULL;
2951 
2952         ZeroMemory(szModuleName, sizeof(szModuleName));
2953         if (GetDisplayNameFileAndID(LogName, szModuleName, &dwMessageID))
2954         {
2955             /* Retrieve the message string without appending extra newlines */
2956             lpDisplayName =
2957             GetMessageStringFromDll(szModuleName,
2958                                     FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_HMODULE |
2959                                     FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_MAX_WIDTH_MASK,
2960                                     dwMessageID,
2961                                     0,
2962                                     NULL);
2963         }
2964 
2965         /*
2966          * Select the correct tree root node, whether the log is a System
2967          * or an Application log. Default to Application log otherwise.
2968          */
2969         hRootNode = htiAppLogs;
2970         for (lpcName = 0; lpcName < ARRAYSIZE(SystemLogs); ++lpcName)
2971         {
2972             /* Check whether the log name is part of the system logs */
2973             if (wcsicmp(LogName, SystemLogs[lpcName]) == 0)
2974             {
2975                 hRootNode = htiSystemLogs;
2976                 break;
2977             }
2978         }
2979 
2980         hItem = TreeViewAddItem(hwndTreeView, hRootNode,
2981                                 (lpDisplayName ? lpDisplayName : LogName),
2982                                 2, 3, (LPARAM)EventLogFilter);
2983 
2984         /* Try to get the default event log: "Application" */
2985         if ((hItemDefault == NULL) && (wcsicmp(LogName, SystemLogs[0]) == 0))
2986         {
2987             hItemDefault = hItem;
2988         }
2989 
2990         /* Free the buffer allocated by FormatMessage */
2991         if (lpDisplayName)
2992             LocalFree(lpDisplayName);
2993     }
2994 
2995     HeapFree(GetProcessHeap(), 0, LogName);
2996 
2997 Quit:
2998     RegCloseKey(hEventLogKey);
2999 
3000     /* Select the default event log */
3001     if (hItemDefault)
3002     {
3003         // TreeView_Expand(hwndTreeView, hRootNode, TVE_EXPAND);
3004         TreeView_SelectItem(hwndTreeView, hItemDefault);
3005         TreeView_EnsureVisible(hwndTreeView, hItemDefault);
3006     }
3007     InvalidateRect(hwndTreeView, NULL, FALSE);
3008     SetFocus(hwndTreeView);
3009 
3010     return;
3011 }
3012 
3013 VOID
3014 FreeLogList(VOID)
3015 {
3016     PLIST_ENTRY Entry;
3017     PEVENTLOG EventLog;
3018 
3019     while (!IsListEmpty(&EventLogList))
3020     {
3021         Entry = RemoveHeadList(&EventLogList);
3022         EventLog = (PEVENTLOG)CONTAINING_RECORD(Entry, EVENTLOG, ListEntry);
3023         EventLog_Free(EventLog);
3024     }
3025 
3026     return;
3027 }
3028 
3029 VOID
3030 FreeLogFilterList(VOID)
3031 {
3032     PLIST_ENTRY Entry;
3033     PEVENTLOGFILTER EventLogFilter;
3034 
3035     while (!IsListEmpty(&EventLogFilterList))
3036     {
3037         Entry = RemoveHeadList(&EventLogFilterList);
3038         EventLogFilter = (PEVENTLOGFILTER)CONTAINING_RECORD(Entry, EVENTLOGFILTER, ListEntry);
3039         EventLogFilter_Free(EventLogFilter);
3040     }
3041 
3042     ActiveFilter = NULL;
3043 
3044     return;
3045 }
3046 
3047 BOOL
3048 InitInstance(HINSTANCE hInstance)
3049 {
3050     RECT rcClient, rs;
3051     LONG StatusHeight;
3052     HIMAGELIST hSmall;
3053     LVCOLUMNW lvc = {0};
3054     WCHAR szTemp[256];
3055 
3056     /* Create the main window */
3057     rs = Settings.wpPos.rcNormalPosition;
3058     hwndMainWindow = CreateWindowW(EVENTVWR_WNDCLASS,
3059                                    szTitle,
3060                                    WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
3061                                    rs.left, rs.top,
3062                                    (rs.right  != CW_USEDEFAULT && rs.left != CW_USEDEFAULT) ? rs.right - rs.left : CW_USEDEFAULT,
3063                                    (rs.bottom != CW_USEDEFAULT && rs.top  != CW_USEDEFAULT) ? rs.bottom - rs.top : CW_USEDEFAULT,
3064                                    NULL,
3065                                    NULL,
3066                                    hInstance,
3067                                    NULL);
3068     if (!hwndMainWindow)
3069         return FALSE;
3070 
3071     /* Create the status bar */
3072     hwndStatus = CreateWindowExW(0,                                  // no extended styles
3073                                  STATUSCLASSNAMEW,                   // status bar
3074                                  L"",                                // no text
3075                                  WS_CHILD | WS_VISIBLE | CCS_BOTTOM | SBARS_SIZEGRIP, // styles
3076                                  0, 0, 0, 0,                         // x, y, cx, cy
3077                                  hwndMainWindow,                     // parent window
3078                                  (HMENU)100,                         // window ID
3079                                  hInstance,                          // instance
3080                                  NULL);                              // window data
3081 
3082     GetClientRect(hwndMainWindow, &rcClient);
3083     GetWindowRect(hwndStatus, &rs);
3084     StatusHeight = rs.bottom - rs.top;
3085 
3086     /* Create a progress bar in the status bar (hidden by default) */
3087     StatusBar_GetItemRect(hwndStatus, 0, &rs);
3088     hwndStatusProgress = CreateWindowExW(0,                          // no extended styles
3089                                          PROGRESS_CLASSW,            // status bar
3090                                          NULL,                       // no text
3091                                          WS_CHILD | PBS_SMOOTH,      // styles
3092                                          rs.left, rs.top,            // x, y
3093                                          rs.right - rs.left, rs.bottom - rs.top, // cx, cy
3094                                          hwndStatus,                 // parent window
3095                                          NULL,                       // window ID
3096                                          hInstance,                  // instance
3097                                          NULL);                      // window data
3098     /* Remove its static edge */
3099     SetWindowLongPtrW(hwndStatusProgress, GWL_EXSTYLE,
3100                       GetWindowLongPtrW(hwndStatusProgress, GWL_EXSTYLE) & ~WS_EX_STATICEDGE);
3101     ProgressBar_SetStep(hwndStatusProgress, 1);
3102 
3103     /* Initialize the splitter default positions */
3104     nVSplitPos = Settings.nVSplitPos;
3105     nHSplitPos = Settings.nHSplitPos;
3106 
3107     /* Create the TreeView */
3108     hwndTreeView = CreateWindowExW(WS_EX_CLIENTEDGE,
3109                                    WC_TREEVIEWW,
3110                                    NULL,
3111                                    // WS_CHILD | WS_VISIBLE | TVS_HASLINES | TVS_SHOWSELALWAYS,
3112                                    WS_CHILD | WS_VISIBLE | /* WS_TABSTOP | */ TVS_HASLINES | TVS_HASBUTTONS | TVS_LINESATROOT | TVS_EDITLABELS | TVS_SHOWSELALWAYS,
3113                                    0, 0,
3114                                    nVSplitPos - SPLIT_WIDTH/2,
3115                                    (rcClient.bottom - rcClient.top) - StatusHeight,
3116                                    hwndMainWindow,
3117                                    NULL,
3118                                    hInstance,
3119                                    NULL);
3120 
3121     /* Create the ImageList */
3122     hSmall = ImageList_Create(GetSystemMetrics(SM_CXSMICON),
3123                               GetSystemMetrics(SM_CYSMICON),
3124                               ILC_COLOR32 | ILC_MASK, // ILC_COLOR24
3125                               1, 1);
3126 
3127     /* Add event type icons to the ImageList: closed/opened folder, event log (normal/viewed) */
3128     ImageList_AddIcon(hSmall, LoadIconW(hInstance, MAKEINTRESOURCEW(IDI_CLOSED_CATEGORY)));
3129     ImageList_AddIcon(hSmall, LoadIconW(hInstance, MAKEINTRESOURCEW(IDI_OPENED_CATEGORY)));
3130     ImageList_AddIcon(hSmall, LoadIconW(hInstance, MAKEINTRESOURCEW(IDI_EVENTLOG)));
3131     ImageList_AddIcon(hSmall, LoadIconW(hInstance, MAKEINTRESOURCEW(IDI_EVENTVWR)));
3132 
3133     /* Assign the ImageList to the Tree View */
3134     TreeView_SetImageList(hwndTreeView, hSmall, TVSIL_NORMAL);
3135 
3136     /* Add the event logs nodes */
3137     // "System Logs"
3138     LoadStringW(hInstance, IDS_EVENTLOG_SYSTEM, szTemp, ARRAYSIZE(szTemp));
3139     htiSystemLogs = TreeViewAddItem(hwndTreeView, NULL, szTemp, 0, 1, (LPARAM)NULL);
3140     // "Application Logs"
3141     LoadStringW(hInstance, IDS_EVENTLOG_APP, szTemp, ARRAYSIZE(szTemp));
3142     htiAppLogs = TreeViewAddItem(hwndTreeView, NULL, szTemp, 0, 1, (LPARAM)NULL);
3143     // "User Logs"
3144     LoadStringW(hInstance, IDS_EVENTLOG_USER, szTemp, ARRAYSIZE(szTemp));
3145     htiUserLogs = TreeViewAddItem(hwndTreeView, NULL, szTemp, 0, 1, (LPARAM)NULL);
3146 
3147     /* Create the Event details pane (optional) */
3148     hwndEventDetails = CreateEventDetailsCtrl(hInst, hwndMainWindow, (LPARAM)NULL);
3149     if (hwndEventDetails)
3150     {
3151     SetWindowLongPtrW(hwndEventDetails, GWL_EXSTYLE,
3152                       GetWindowLongPtrW(hwndEventDetails, GWL_EXSTYLE) | WS_EX_CLIENTEDGE);
3153     SetWindowPos(hwndEventDetails, NULL,
3154                  nVSplitPos + SPLIT_WIDTH/2,
3155                  nHSplitPos + SPLIT_WIDTH/2,
3156                  (rcClient.right - rcClient.left) - nVSplitPos - SPLIT_WIDTH/2,
3157                  (rcClient.bottom - rcClient.top) - nHSplitPos - SPLIT_WIDTH/2 - StatusHeight,
3158                  SWP_NOZORDER | SWP_NOACTIVATE | (Settings.bShowDetailsPane ? SWP_SHOWWINDOW : SWP_HIDEWINDOW));
3159     }
3160 
3161     /* Create the ListView */
3162     hwndListView = CreateWindowExW(WS_EX_CLIENTEDGE,
3163                                    WC_LISTVIEWW,
3164                                    NULL,
3165                                    WS_CHILD | WS_VISIBLE | LVS_SHOWSELALWAYS | LVS_REPORT,
3166                                    nVSplitPos + SPLIT_WIDTH/2,
3167                                    0,
3168                                    (rcClient.right - rcClient.left) - nVSplitPos - SPLIT_WIDTH/2,
3169                                    hwndEventDetails && Settings.bShowDetailsPane
3170                                        ? nHSplitPos - SPLIT_WIDTH/2
3171                                        : (rcClient.bottom - rcClient.top) - StatusHeight,
3172                                    hwndMainWindow,
3173                                    NULL,
3174                                    hInstance,
3175                                    NULL);
3176 
3177     /* Add the extended ListView styles */
3178     ListView_SetExtendedListViewStyle(hwndListView, LVS_EX_HEADERDRAGDROP | LVS_EX_FULLROWSELECT | LVS_EX_LABELTIP | (Settings.bShowGrid ? LVS_EX_GRIDLINES : 0));
3179 
3180     /* Create the ImageList */
3181     hSmall = ImageList_Create(GetSystemMetrics(SM_CXSMICON),
3182                               GetSystemMetrics(SM_CYSMICON),
3183                               ILC_COLOR32 | ILC_MASK, // ILC_COLOR24
3184                               1, 1);
3185 
3186     /* Add event type icons to the ImageList */
3187     ImageList_AddIcon(hSmall, LoadIconW(hInstance, MAKEINTRESOURCEW(IDI_INFORMATIONICON)));
3188     ImageList_AddIcon(hSmall, LoadIconW(hInstance, MAKEINTRESOURCEW(IDI_WARNINGICON)));
3189     ImageList_AddIcon(hSmall, LoadIconW(hInstance, MAKEINTRESOURCEW(IDI_ERRORICON)));
3190     ImageList_AddIcon(hSmall, LoadIconW(hInstance, MAKEINTRESOURCEW(IDI_AUDITSUCCESSICON)));
3191     ImageList_AddIcon(hSmall, LoadIconW(hInstance, MAKEINTRESOURCEW(IDI_AUDITFAILUREICON)));
3192 
3193     /* Assign the ImageList to the List View */
3194     ListView_SetImageList(hwndListView, hSmall, LVSIL_SMALL);
3195 
3196     /* Now set up the listview with its columns */
3197     lvc.mask = LVCF_TEXT | LVCF_WIDTH;
3198     lvc.cx = 90;
3199     LoadStringW(hInstance,
3200                 IDS_COLUMNTYPE,
3201                 szTemp,
3202                 ARRAYSIZE(szTemp));
3203     lvc.pszText = szTemp;
3204     ListView_InsertColumn(hwndListView, 0, &lvc);
3205 
3206     lvc.cx = 70;
3207     LoadStringW(hInstance,
3208                 IDS_COLUMNDATE,
3209                 szTemp,
3210                 ARRAYSIZE(szTemp));
3211     lvc.pszText = szTemp;
3212     ListView_InsertColumn(hwndListView, 1, &lvc);
3213 
3214     lvc.cx = 70;
3215     LoadStringW(hInstance,
3216                 IDS_COLUMNTIME,
3217                 szTemp,
3218                 ARRAYSIZE(szTemp));
3219     lvc.pszText = szTemp;
3220     ListView_InsertColumn(hwndListView, 2, &lvc);
3221 
3222     lvc.cx = 150;
3223     LoadStringW(hInstance,
3224                 IDS_COLUMNSOURCE,
3225                 szTemp,
3226                 ARRAYSIZE(szTemp));
3227     lvc.pszText = szTemp;
3228     ListView_InsertColumn(hwndListView, 3, &lvc);
3229 
3230     lvc.cx = 100;
3231     LoadStringW(hInstance,
3232                 IDS_COLUMNCATEGORY,
3233                 szTemp,
3234                 ARRAYSIZE(szTemp));
3235     lvc.pszText = szTemp;
3236     ListView_InsertColumn(hwndListView, 4, &lvc);
3237 
3238     lvc.cx = 60;
3239     LoadStringW(hInstance,
3240                 IDS_COLUMNEVENT,
3241                 szTemp,
3242                 ARRAYSIZE(szTemp));
3243     lvc.pszText = szTemp;
3244     ListView_InsertColumn(hwndListView, 5, &lvc);
3245 
3246     lvc.cx = 120;
3247     LoadStringW(hInstance,
3248                 IDS_COLUMNUSER,
3249                 szTemp,
3250                 ARRAYSIZE(szTemp));
3251     lvc.pszText = szTemp;
3252     ListView_InsertColumn(hwndListView, 6, &lvc);
3253 
3254     lvc.cx = 100;
3255     LoadStringW(hInstance,
3256                 IDS_COLUMNCOMPUTER,
3257                 szTemp,
3258                 ARRAYSIZE(szTemp));
3259     lvc.pszText = szTemp;
3260     ListView_InsertColumn(hwndListView, 7, &lvc);
3261 
3262     /* Initialize the save Dialog */
3263     ZeroMemory(&sfn, sizeof(sfn));
3264     ZeroMemory(szSaveFilter, sizeof(szSaveFilter));
3265 
3266     LoadStringW(hInst, IDS_SAVE_FILTER, szSaveFilter, ARRAYSIZE(szSaveFilter));
3267 
3268     sfn.lStructSize     = sizeof(sfn);
3269     sfn.hwndOwner       = hwndMainWindow;
3270     sfn.hInstance       = hInstance;
3271     sfn.lpstrFilter     = szSaveFilter;
3272     sfn.lpstrInitialDir = NULL;
3273     sfn.Flags           = OFN_EXPLORER | OFN_HIDEREADONLY | OFN_SHAREAWARE;
3274     sfn.lpstrDefExt     = L"evt";
3275 
3276     ShowWindow(hwndMainWindow, Settings.wpPos.showCmd);
3277     UpdateWindow(hwndMainWindow);
3278 
3279     return TRUE;
3280 }
3281 
3282 VOID ResizeWnd(INT cx, INT cy)
3283 {
3284     RECT rs;
3285     LONG StatusHeight;
3286     LONG_PTR dwExStyle;
3287     HDWP hdwp;
3288 
3289     /* Resize the status bar -- now done in WM_SIZE */
3290     // SendMessageW(hwndStatus, WM_SIZE, 0, 0);
3291     GetWindowRect(hwndStatus, &rs);
3292     StatusHeight = rs.bottom - rs.top;
3293 
3294     /*
3295      * Move the progress bar -- Take into account for extra size due to the static edge
3296      * (AdjustWindowRectEx() does not seem to work for the progress bar).
3297      */
3298     StatusBar_GetItemRect(hwndStatus, 0, &rs);
3299     dwExStyle = GetWindowLongPtrW(hwndStatusProgress, GWL_EXSTYLE);
3300     SetWindowLongPtrW(hwndStatusProgress, GWL_EXSTYLE, dwExStyle | WS_EX_STATICEDGE);
3301     MoveWindow(hwndStatusProgress,
3302                rs.left, rs.top, rs.right - rs.left, rs.bottom - rs.top,
3303                IsWindowVisible(hwndStatusProgress) ? TRUE : FALSE);
3304     SetWindowLongPtrW(hwndStatusProgress, GWL_EXSTYLE, dwExStyle);
3305 
3306     /*
3307      * TODO: Adjust the splitter positions:
3308      * - Vertical splitter (1)  : fixed position from the left window side.
3309      * - Horizontal splitter (2): fixed position from the bottom window side.
3310      */
3311     nVSplitPos = min(max(nVSplitPos, SPLIT_WIDTH/2), cx - SPLIT_WIDTH/2); // OK
3312     nHSplitPos = min(max(nHSplitPos, SPLIT_WIDTH/2), cy - SPLIT_WIDTH/2 - StatusHeight); // FIXME!
3313 
3314     hdwp = BeginDeferWindowPos(3);
3315 
3316     if (hdwp)
3317         hdwp = DeferWindowPos(hdwp,
3318                               hwndTreeView,
3319                               HWND_TOP,
3320                               0, 0,
3321                               nVSplitPos - SPLIT_WIDTH/2,
3322                               cy - StatusHeight,
3323                               SWP_NOZORDER | SWP_NOACTIVATE);
3324 
3325     if (hdwp)
3326         hdwp = DeferWindowPos(hdwp,
3327                               hwndListView,
3328                               HWND_TOP,
3329                               nVSplitPos + SPLIT_WIDTH/2, 0,
3330                               cx - nVSplitPos - SPLIT_WIDTH/2,
3331                               hwndEventDetails && Settings.bShowDetailsPane
3332                                   ? nHSplitPos - SPLIT_WIDTH/2
3333                                   : cy - StatusHeight,
3334                               SWP_NOZORDER | SWP_NOACTIVATE);
3335 
3336     if (hwndEventDetails && Settings.bShowDetailsPane && hdwp)
3337         hdwp = DeferWindowPos(hdwp,
3338                               hwndEventDetails,
3339                               HWND_TOP,
3340                               nVSplitPos + SPLIT_WIDTH/2,
3341                               nHSplitPos + SPLIT_WIDTH/2,
3342                               cx - nVSplitPos - SPLIT_WIDTH/2,
3343                               cy - nHSplitPos - SPLIT_WIDTH/2 - StatusHeight,
3344                               SWP_NOZORDER | SWP_NOACTIVATE);
3345 
3346     if (hdwp)
3347         EndDeferWindowPos(hdwp);
3348 }
3349 
3350 
3351 LRESULT CALLBACK
3352 WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
3353 {
3354     RECT rect;
3355 
3356     switch (uMsg)
3357     {
3358         case WM_CREATE:
3359             hMainMenu = GetMenu(hWnd);
3360             break;
3361 
3362         case WM_DESTROY:
3363         {
3364             GetWindowPlacement(hwndMainWindow, &Settings.wpPos);
3365             PostQuitMessage(0);
3366             break;
3367         }
3368 
3369         case WM_NOTIFY:
3370         {
3371             LPNMHDR hdr = (LPNMHDR)lParam;
3372 
3373             if (hdr->hwndFrom == hwndListView)
3374             {
3375                 switch (hdr->code)
3376                 {
3377                     case LVN_ITEMCHANGED:
3378                     {
3379                         LPNMLISTVIEW pnmv = (LPNMLISTVIEW)lParam;
3380 
3381                         if ( (pnmv->uChanged  & LVIF_STATE) && /* The state has changed */
3382                              (pnmv->uNewState & LVIS_SELECTED) /* The item has been (de)selected */ )
3383                         {
3384                             if (hwndEventDetails)
3385                                 SendMessageW(hwndEventDetails, EVT_DISPLAY, 0, 0);
3386                         }
3387                         break;
3388                     }
3389 
3390                     case NM_DBLCLK:
3391                     case NM_RETURN:
3392                         SendMessageW(hWnd, WM_COMMAND, IDM_EVENT_DETAILS, 0);
3393                         break;
3394                 }
3395             }
3396             else if (hdr->hwndFrom == hwndTreeView)
3397             {
3398                 switch (hdr->code)
3399                 {
3400                     case TVN_BEGINLABELEDIT:
3401                     {
3402                         HTREEITEM hItem = ((LPNMTVDISPINFO)lParam)->item.hItem;
3403 
3404                         /* Disable label editing for root nodes */
3405                         return ((hItem == htiSystemLogs) ||
3406                                 (hItem == htiAppLogs)    ||
3407                                 (hItem == htiUserLogs));
3408                     }
3409 
3410                     case TVN_ENDLABELEDIT:
3411                     {
3412                         TVITEMW item = ((LPNMTVDISPINFO)lParam)->item;
3413                         HTREEITEM hItem = item.hItem;
3414 
3415                         /* Disable label editing for root nodes */
3416                         if ((hItem == htiSystemLogs) ||
3417                             (hItem == htiAppLogs)    ||
3418                             (hItem == htiUserLogs))
3419                         {
3420                             return FALSE;
3421                         }
3422 
3423                         if (item.pszText)
3424                         {
3425                             LPWSTR pszText = item.pszText;
3426 
3427                             /* Trim leading whitespace */
3428                             while (*pszText && iswspace(*pszText))
3429                                 ++pszText;
3430 
3431                             if (!*pszText)
3432                                 return FALSE;
3433 
3434                             return TRUE;
3435                         }
3436                         else
3437                         {
3438                             return FALSE;
3439                         }
3440                     }
3441 
3442                     case TVN_SELCHANGED:
3443                     {
3444                         PEVENTLOGFILTER EventLogFilter =
3445                             (PEVENTLOGFILTER)((LPNMTREEVIEW)lParam)->itemNew.lParam;
3446 
3447                         // FIXME: It might be nice to reference here the filter,
3448                         // so that we don't have to reference/dereference it many times
3449                         // in the other functions???
3450 
3451                         // FIXME: This is a hack!!
3452                         if (hwndEventDetails && EventLogFilter)
3453                         {
3454                             SendMessageW(hwndEventDetails, EVT_SETFILTER, 0, (LPARAM)EventLogFilter);
3455                         }
3456 
3457                         if (EventLogFilter)
3458                         {
3459                             /*
3460                              * If we have selected a filter, enable the menu commands;
3461                              * they will possibly be updated after events enumeration.
3462                              */
3463                             EnableMenuItem(hMainMenu, IDM_SAVE_EVENTLOG, MF_BYCOMMAND | MF_ENABLED);
3464                             EnableMenuItem(hMainMenu, IDM_CLOSE_EVENTLOG, MF_BYCOMMAND | MF_ENABLED);
3465                             EnableMenuItem(hMainMenu, IDM_CLEAR_EVENTS, MF_BYCOMMAND | MF_ENABLED);
3466                             EnableMenuItem(hMainMenu, IDM_RENAME_EVENTLOG, MF_BYCOMMAND | MF_ENABLED);
3467                             EnableMenuItem(hMainMenu, IDM_EVENTLOG_SETTINGS, MF_BYCOMMAND | MF_ENABLED);
3468                         }
3469                         else
3470                         {
3471                             EnableMenuItem(hMainMenu, IDM_SAVE_EVENTLOG, MF_BYCOMMAND | MF_GRAYED);
3472                             EnableMenuItem(hMainMenu, IDM_CLOSE_EVENTLOG, MF_BYCOMMAND | MF_GRAYED);
3473                             EnableMenuItem(hMainMenu, IDM_CLEAR_EVENTS, MF_BYCOMMAND | MF_GRAYED);
3474                             EnableMenuItem(hMainMenu, IDM_RENAME_EVENTLOG, MF_BYCOMMAND | MF_GRAYED);
3475                             EnableMenuItem(hMainMenu, IDM_EVENTLOG_SETTINGS, MF_BYCOMMAND | MF_GRAYED);
3476                         }
3477 
3478                         /*
3479                          * The enumeration thread that is triggered by EnumEvents
3480                          * will set a new value for the 'ActiveFilter'.
3481                          */
3482                         if (EventLogFilter)
3483                             EnumEvents(EventLogFilter);
3484 
3485                         break;
3486                     }
3487                 }
3488             }
3489             break;
3490         }
3491 
3492         case WM_COMMAND:
3493         {
3494             /* Parse the menu selections */
3495             switch (LOWORD(wParam))
3496             {
3497                 case IDM_OPEN_EVENTLOG:
3498                     OpenUserEventLog();
3499                     break;
3500 
3501                 case IDM_SAVE_EVENTLOG:
3502                     SaveEventLog(GetSelectedFilter(NULL));
3503                     break;
3504 
3505                 case IDM_CLOSE_EVENTLOG:
3506                 {
3507                     HTREEITEM hti;
3508                     PEVENTLOGFILTER EventLogFilter = GetSelectedFilter(&hti);
3509                     CloseUserEventLog(EventLogFilter, hti);
3510                     break;
3511                 }
3512 
3513                 case IDM_CLEAR_EVENTS:
3514                 {
3515                     PEVENTLOGFILTER EventLogFilter = GetSelectedFilter(NULL);
3516                     if (EventLogFilter && ClearEvents(EventLogFilter))
3517                         Refresh(EventLogFilter);
3518                     break;
3519                 }
3520 
3521                 case IDM_RENAME_EVENTLOG:
3522                     if (GetFocus() == hwndTreeView)
3523                         TreeView_EditLabel(hwndTreeView, TreeView_GetSelection(hwndTreeView));
3524                     break;
3525 
3526                 case IDM_EVENTLOG_SETTINGS:
3527                 {
3528                     PEVENTLOGFILTER EventLogFilter = GetSelectedFilter(NULL);
3529                     // TODO: Check the returned value?
3530                     if (EventLogFilter)
3531                         EventLogProperties(hInst, hWnd, EventLogFilter);
3532                     break;
3533                 }
3534 
3535                 case IDM_LIST_NEWEST:
3536                 {
3537                     CheckMenuRadioItem(hMainMenu, IDM_LIST_NEWEST, IDM_LIST_OLDEST, IDM_LIST_NEWEST, MF_BYCOMMAND);
3538                     if (!Settings.bNewestEventsFirst)
3539                     {
3540                         Settings.bNewestEventsFirst = TRUE;
3541                         Refresh(GetSelectedFilter(NULL));
3542                     }
3543                     break;
3544                 }
3545 
3546                 case IDM_LIST_OLDEST:
3547                 {
3548                     CheckMenuRadioItem(hMainMenu, IDM_LIST_NEWEST, IDM_LIST_OLDEST, IDM_LIST_OLDEST, MF_BYCOMMAND);
3549                     if (Settings.bNewestEventsFirst)
3550                     {
3551                         Settings.bNewestEventsFirst = FALSE;
3552                         Refresh(GetSelectedFilter(NULL));
3553                     }
3554                     break;
3555                 }
3556 
3557                 case IDM_EVENT_DETAILS:
3558                 {
3559                     // LPNMITEMACTIVATE lpnmitem = (LPNMITEMACTIVATE)lParam;
3560                     PEVENTLOGFILTER EventLogFilter = GetSelectedFilter(NULL);
3561                     if (/*lpnmitem->iItem != -1 &&*/ EventLogFilter)
3562                     {
3563                         EventLogFilter_AddRef(EventLogFilter);
3564                         DialogBoxParamW(hInst,
3565                                         MAKEINTRESOURCEW(IDD_EVENTDETAILS_DLG),
3566                                         hWnd,
3567                                         EventDetails,
3568                                         (LPARAM)EventLogFilter);
3569                         EventLogFilter_Release(EventLogFilter);
3570                     }
3571                     break;
3572                 }
3573 
3574                 case IDM_REFRESH:
3575                     Refresh(GetSelectedFilter(NULL));
3576                     break;
3577 
3578                 case IDM_EVENT_DETAILS_VIEW:
3579                 {
3580                     Settings.bShowDetailsPane = !Settings.bShowDetailsPane;
3581                     CheckMenuItem(hMainMenu, IDM_EVENT_DETAILS_VIEW,
3582                                   MF_BYCOMMAND | (Settings.bShowDetailsPane ? MF_CHECKED : MF_UNCHECKED));
3583 
3584                     GetClientRect(hWnd, &rect);
3585                     if (Settings.bShowDetailsPane)
3586                     {
3587                         ResizeWnd(rect.right - rect.left, rect.bottom - rect.top);
3588                         ShowWindow(hwndEventDetails, SW_SHOW);
3589                     }
3590                     else
3591                     {
3592                         ShowWindow(hwndEventDetails, SW_HIDE);
3593                         ResizeWnd(rect.right - rect.left, rect.bottom - rect.top);
3594                     }
3595 
3596                     break;
3597                 }
3598 
3599                 case IDM_LIST_GRID_LINES:
3600                 {
3601                     Settings.bShowGrid = !Settings.bShowGrid;
3602                     CheckMenuItem(hMainMenu, IDM_LIST_GRID_LINES,
3603                                   MF_BYCOMMAND | (Settings.bShowGrid ? MF_CHECKED : MF_UNCHECKED));
3604 
3605                     ListView_SetExtendedListViewStyleEx(hwndListView, LVS_EX_GRIDLINES, (Settings.bShowGrid ? LVS_EX_GRIDLINES : 0));
3606                     break;
3607                 }
3608 
3609                 case IDM_SAVE_SETTINGS:
3610                 {
3611                     Settings.bSaveSettings = !Settings.bSaveSettings;
3612                     CheckMenuItem(hMainMenu, IDM_SAVE_SETTINGS,
3613                                   MF_BYCOMMAND | (Settings.bSaveSettings ? MF_CHECKED : MF_UNCHECKED));
3614                     break;
3615                 }
3616 
3617                 case IDM_ABOUT:
3618                 {
3619                     HICON hIcon;
3620                     WCHAR szCopyright[MAX_LOADSTRING];
3621 
3622                     hIcon = LoadIconW(hInst, MAKEINTRESOURCEW(IDI_EVENTVWR));
3623                     LoadStringW(hInst, IDS_COPYRIGHT, szCopyright, ARRAYSIZE(szCopyright));
3624                     ShellAboutW(hWnd, szTitle, szCopyright, hIcon);
3625                     DeleteObject(hIcon);
3626                     break;
3627                 }
3628 
3629                 case IDM_HELP:
3630                     MessageBoxW(hwndMainWindow,
3631                                 L"Help not implemented yet!",
3632                                 L"Event Log",
3633                                 MB_OK | MB_ICONINFORMATION);
3634                                 break;
3635 
3636                 case IDM_EXIT:
3637                     DestroyWindow(hWnd);
3638                     break;
3639 
3640                 default:
3641                     return DefWindowProcW(hWnd, uMsg, wParam, lParam);
3642             }
3643             break;
3644         }
3645 
3646         case WM_INITMENU:
3647         {
3648             if ((HMENU)wParam != hMainMenu)
3649                 break;
3650 
3651             CheckMenuRadioItem(hMainMenu, IDM_LIST_NEWEST, IDM_LIST_OLDEST,
3652                                Settings.bNewestEventsFirst ? IDM_LIST_NEWEST : IDM_LIST_OLDEST,
3653                                MF_BYCOMMAND);
3654 
3655             if (!hwndEventDetails)
3656             {
3657                 EnableMenuItem(hMainMenu, IDM_EVENT_DETAILS_VIEW,
3658                                MF_BYCOMMAND | MF_GRAYED);
3659             }
3660             CheckMenuItem(hMainMenu, IDM_EVENT_DETAILS_VIEW,
3661                           MF_BYCOMMAND | (Settings.bShowDetailsPane ? MF_CHECKED : MF_UNCHECKED));
3662 
3663             CheckMenuItem(hMainMenu, IDM_LIST_GRID_LINES,
3664                           MF_BYCOMMAND | (Settings.bShowGrid ? MF_CHECKED : MF_UNCHECKED));
3665 
3666             CheckMenuItem(hMainMenu, IDM_SAVE_SETTINGS,
3667                           MF_BYCOMMAND | (Settings.bSaveSettings ? MF_CHECKED : MF_UNCHECKED));
3668 
3669             break;
3670         }
3671 
3672 #if 0
3673         case WM_INITMENUPOPUP:
3674             lParam = lParam;
3675             break;
3676 #endif
3677 
3678         case WM_CONTEXTMENU:
3679         {
3680             RECT rc;
3681             HTREEITEM hItem;
3682             TVHITTESTINFO hInfo = {0};
3683 
3684             INT xPos = GET_X_LPARAM(lParam);
3685             INT yPos = GET_Y_LPARAM(lParam);
3686 
3687             GetWindowRect(hwndTreeView, &rc);
3688             hInfo.pt.x = xPos - rc.left;
3689             hInfo.pt.y = yPos - rc.top;
3690 
3691             hItem = TreeView_HitTest(hwndTreeView, &hInfo);
3692             if (hItem)
3693             {
3694                 TreeView_SelectItem(hwndTreeView, hItem);
3695 
3696                 if (TreeView_GetParent(hwndTreeView, hItem))
3697                 {
3698                     HMENU hCtxMenu = GetSubMenu(LoadMenuW(hInst, MAKEINTRESOURCEW(IDM_EVENTWR_CTX)), 0);
3699 
3700                     DWORD dwCmdID = TrackPopupMenuEx(hCtxMenu,
3701                                                      TPM_LEFTALIGN | TPM_TOPALIGN | TPM_NONOTIFY | TPM_RETURNCMD,
3702                                                      xPos, yPos, hWnd, NULL);
3703                     SendMessageW(hWnd, WM_COMMAND, (WPARAM)dwCmdID, (LPARAM)hwndTreeView);
3704                 }
3705             }
3706             break;
3707         }
3708 
3709         case WM_SETCURSOR:
3710         {
3711             POINT pt;
3712 
3713             if (LOWORD(lParam) != HTCLIENT)
3714                 goto Default;
3715 
3716             GetCursorPos(&pt);
3717             ScreenToClient(hWnd, &pt);
3718 
3719             /* Set the cursor for the vertical splitter */
3720             if (pt.x >= nVSplitPos - SPLIT_WIDTH/2 && pt.x < nVSplitPos + SPLIT_WIDTH/2 + 1)
3721             {
3722                 RECT rs;
3723                 GetClientRect(hWnd, &rect);
3724                 GetWindowRect(hwndStatus, &rs);
3725                 if (pt.y >= rect.top && pt.y < rect.bottom - (rs.bottom - rs.top))
3726                 {
3727                     SetCursor(LoadCursorW(NULL, IDC_SIZEWE));
3728                     return TRUE;
3729                 }
3730             }
3731             else
3732             /* Set the cursor for the horizontal splitter, if the Event details pane is displayed */
3733             if (hwndEventDetails && Settings.bShowDetailsPane &&
3734                 (pt.y >= nHSplitPos - SPLIT_WIDTH/2 && pt.y < nHSplitPos + SPLIT_WIDTH/2 + 1))
3735             {
3736                 // RECT rs;
3737                 GetClientRect(hWnd, &rect);
3738                 // GetWindowRect(hwndStatus, &rs);
3739                 if (pt.x >= nVSplitPos + SPLIT_WIDTH/2 + 1 /* rect.left + (rs.bottom - rs.top) */ &&
3740                     pt.x < rect.right)
3741                 {
3742                     SetCursor(LoadCursorW(NULL, IDC_SIZENS));
3743                     return TRUE;
3744                 }
3745             }
3746             goto Default;
3747         }
3748 
3749         case WM_LBUTTONDOWN:
3750         {
3751             INT x = GET_X_LPARAM(lParam);
3752             INT y = GET_Y_LPARAM(lParam);
3753 
3754             /* Reset the splitter state */
3755             bSplit = 0;
3756 
3757             /* Capture the cursor for the vertical splitter */
3758             if (x >= nVSplitPos - SPLIT_WIDTH/2 && x < nVSplitPos + SPLIT_WIDTH/2 + 1)
3759             {
3760                 bSplit = 1;
3761                 SetCapture(hWnd);
3762             }
3763             else
3764             /* Capture the cursor for the horizontal splitter, if the Event details pane is displayed */
3765             if (hwndEventDetails && Settings.bShowDetailsPane &&
3766                 (y >= nHSplitPos - SPLIT_WIDTH/2 && y < nHSplitPos + SPLIT_WIDTH/2 + 1))
3767             {
3768                 bSplit = 2;
3769                 SetCapture(hWnd);
3770             }
3771             break;
3772         }
3773 
3774         case WM_LBUTTONUP:
3775         case WM_RBUTTONDOWN:
3776         {
3777             if (GetCapture() != hWnd)
3778                 break;
3779 
3780             /* Adjust the correct splitter position */
3781             if (bSplit == 1)
3782                 nVSplitPos = GET_X_LPARAM(lParam);
3783             else if (bSplit == 2)
3784                 nHSplitPos = GET_Y_LPARAM(lParam);
3785 
3786             /* If we are splitting, resize the windows */
3787             if (bSplit != 0)
3788             {
3789                 GetClientRect(hWnd, &rect);
3790                 ResizeWnd(rect.right - rect.left, rect.bottom - rect.top);
3791             }
3792 
3793             /* Reset the splitter state */
3794             bSplit = 0;
3795 
3796             ReleaseCapture();
3797             break;
3798         }
3799 
3800         case WM_MOUSEMOVE:
3801         {
3802             if (GetCapture() != hWnd)
3803                 break;
3804 
3805             /* Move the correct splitter */
3806             if (bSplit == 1)
3807             {
3808                 INT x = GET_X_LPARAM(lParam);
3809 
3810                 GetClientRect(hWnd, &rect);
3811 
3812                 x = min(max(x, SPLIT_WIDTH/2), rect.right - rect.left - SPLIT_WIDTH/2);
3813                 if (nVSplitPos != x)
3814                 {
3815                     nVSplitPos = x;
3816                     ResizeWnd(rect.right - rect.left, rect.bottom - rect.top);
3817                 }
3818             }
3819             else if (bSplit == 2)
3820             {
3821                 RECT rs;
3822                 INT y = GET_Y_LPARAM(lParam);
3823 
3824                 GetClientRect(hWnd, &rect);
3825                 GetWindowRect(hwndStatus, &rs);
3826 
3827                 y = min(max(y, SPLIT_WIDTH/2), rect.bottom - rect.top - SPLIT_WIDTH/2 - (rs.bottom - rs.top));
3828                 if (nHSplitPos != y)
3829                 {
3830                     nHSplitPos = y;
3831                     ResizeWnd(rect.right - rect.left, rect.bottom - rect.top);
3832                 }
3833             }
3834             break;
3835         }
3836 
3837         case WM_SIZE:
3838         {
3839             if (wParam != SIZE_MINIMIZED)
3840             {
3841                 SendMessageW(hwndStatus, WM_SIZE, 0, 0);
3842                 ResizeWnd(LOWORD(lParam), HIWORD(lParam));
3843                 break;
3844             }
3845             /* Fall through the default case */
3846         }
3847 
3848         default: Default:
3849             return DefWindowProcW(hWnd, uMsg, wParam, lParam);
3850     }
3851 
3852     return 0;
3853 }
3854 
3855 
3856 static
3857 VOID
3858 InitPropertiesDlg(HWND hDlg, PEVENTLOG EventLog)
3859 {
3860     LPWSTR lpLogName = EventLog->LogName;
3861 
3862     DWORD Result, dwType;
3863     DWORD dwMaxSize = 0, dwRetention = 0;
3864     BOOL Success;
3865     WIN32_FIND_DATAW FileInfo; // WIN32_FILE_ATTRIBUTE_DATA
3866     ULARGE_INTEGER FileSize;
3867     WCHAR wszBuf[MAX_PATH];
3868     WCHAR szTemp[MAX_LOADSTRING];
3869     LPWSTR FileName;
3870 
3871     HKEY hLogKey;
3872     WCHAR *KeyPath;
3873     DWORD cbData;
3874     SIZE_T cbKeyPath;
3875 
3876     if (EventLog->Permanent)
3877     {
3878 
3879     cbKeyPath = (wcslen(EVENTLOG_BASE_KEY) + wcslen(lpLogName) + 1) * sizeof(WCHAR);
3880     KeyPath = HeapAlloc(GetProcessHeap(), 0, cbKeyPath);
3881     if (!KeyPath)
3882     {
3883         ShowWin32Error(ERROR_NOT_ENOUGH_MEMORY);
3884         goto Quit;
3885     }
3886 
3887     StringCbCopyW(KeyPath, cbKeyPath, EVENTLOG_BASE_KEY);
3888     StringCbCatW(KeyPath, cbKeyPath, lpLogName);
3889 
3890     Result = RegOpenKeyExW(hkMachine, KeyPath, 0, KEY_QUERY_VALUE, &hLogKey);
3891     HeapFree(GetProcessHeap(), 0, KeyPath);
3892     if (Result != ERROR_SUCCESS)
3893     {
3894         ShowWin32Error(Result);
3895         goto Quit;
3896     }
3897 
3898 
3899     cbData = sizeof(dwMaxSize);
3900     Result = RegQueryValueExW(hLogKey,
3901                               L"MaxSize",
3902                               NULL,
3903                               &dwType,
3904                               (LPBYTE)&dwMaxSize,
3905                               &cbData);
3906     if ((Result != ERROR_SUCCESS) || (dwType != REG_DWORD))
3907     {
3908         // dwMaxSize = 512 * 1024; /* 512 kBytes */
3909         /* Workstation: 512 KB; Server: 16384 KB */
3910         dwMaxSize = 16384 * 1024;
3911     }
3912     /* Convert in KB */
3913     dwMaxSize /= 1024;
3914 
3915     cbData = sizeof(dwRetention);
3916     Result = RegQueryValueExW(hLogKey,
3917                               L"Retention",
3918                               NULL,
3919                               &dwType,
3920                               (LPBYTE)&dwRetention,
3921                               &cbData);
3922     if ((Result != ERROR_SUCCESS) || (dwType != REG_DWORD))
3923     {
3924         /* By default, it is 604800 (secs) == 7 days. On Server, the retention is zeroed out. */
3925         dwRetention = 0;
3926     }
3927     /* Convert in days, rounded up */
3928     if (dwRetention != INFINITE)
3929         dwRetention = (dwRetention + 24*3600 - 1) / (24*3600);
3930 
3931     RegCloseKey(hLogKey);
3932 
3933     }
3934 
3935 Quit:
3936 
3937     SetDlgItemTextW(hDlg, IDC_DISPLAYNAME, lpLogName); // FIXME!
3938     SetDlgItemTextW(hDlg, IDC_LOGNAME, lpLogName);
3939 
3940     FileName = EventLog->FileName;
3941     if (FileName && *FileName)
3942     {
3943         /* Expand the file name. If the log file is on a remote computer, retrieve the network share form of the file name. */
3944         GetExpandedFilePathName(EventLog->ComputerName, FileName, wszBuf, ARRAYSIZE(wszBuf));
3945         FileName = wszBuf;
3946     }
3947     else
3948     {
3949         FileName = L"";
3950     }
3951     SetDlgItemTextW(hDlg, IDC_LOGFILE, FileName);
3952 
3953     if (FileName && *FileName)
3954     {
3955         /*
3956          * The general problem here (and in the shell as well) is that
3957          * GetFileAttributesEx fails for files that are opened without
3958          * shared access. To retrieve file information for those we need
3959          * to use something else: FindFirstFile, on the full file name.
3960          */
3961         Success = GetFileAttributesExW(FileName,
3962                                        GetFileExInfoStandard,
3963                                        (LPWIN32_FILE_ATTRIBUTE_DATA)&FileInfo);
3964         if (!Success)
3965         {
3966             HANDLE hFind = FindFirstFileW(FileName, &FileInfo);
3967             Success = (hFind != INVALID_HANDLE_VALUE);
3968             if (Success)
3969                 FindClose(hFind);
3970         }
3971     }
3972     else
3973     {
3974         Success = FALSE;
3975     }
3976 
3977     /* Starting there, FileName becomes invalid because we are reusing wszBuf */
3978 
3979     if (Success)
3980     {
3981         FileSize.u.LowPart = FileInfo.nFileSizeLow;
3982         FileSize.u.HighPart = FileInfo.nFileSizeHigh;
3983         if (FormatFileSizeWithBytes(&FileSize, wszBuf, ARRAYSIZE(wszBuf)))
3984             SetDlgItemTextW(hDlg, IDC_SIZE_LABEL, wszBuf);
3985 
3986         LoadStringW(hInst, IDS_NOT_AVAILABLE, szTemp, ARRAYSIZE(szTemp));
3987 
3988         if (GetFileTimeString(&FileInfo.ftCreationTime, wszBuf, ARRAYSIZE(wszBuf)))
3989             SetDlgItemTextW(hDlg, IDC_CREATED_LABEL, wszBuf);
3990         else
3991             SetDlgItemTextW(hDlg, IDC_CREATED_LABEL, szTemp);
3992 
3993         if (GetFileTimeString(&FileInfo.ftLastWriteTime, wszBuf, ARRAYSIZE(wszBuf)))
3994             SetDlgItemTextW(hDlg, IDC_MODIFIED_LABEL, wszBuf);
3995         else
3996             SetDlgItemTextW(hDlg, IDC_MODIFIED_LABEL, szTemp);
3997 
3998         if (GetFileTimeString(&FileInfo.ftLastAccessTime, wszBuf, ARRAYSIZE(wszBuf)))
3999             SetDlgItemTextW(hDlg, IDC_ACCESSED_LABEL, wszBuf);
4000         else
4001             SetDlgItemTextW(hDlg, IDC_MODIFIED_LABEL, szTemp);
4002     }
4003     else
4004     {
4005         LoadStringW(hInst, IDS_NOT_AVAILABLE, szTemp, ARRAYSIZE(szTemp));
4006 
4007         SetDlgItemTextW(hDlg, IDC_SIZE_LABEL, szTemp);
4008         SetDlgItemTextW(hDlg, IDC_CREATED_LABEL, szTemp);
4009         SetDlgItemTextW(hDlg, IDC_MODIFIED_LABEL, szTemp);
4010         SetDlgItemTextW(hDlg, IDC_ACCESSED_LABEL, szTemp);
4011     }
4012 
4013     if (EventLog->Permanent)
4014     {
4015         SendDlgItemMessageW(hDlg, IDC_UPDOWN_MAXLOGSIZE, UDM_SETRANGE32, (WPARAM)1, (LPARAM)0x3FFFC0);
4016         SendDlgItemMessageW(hDlg, IDC_UPDOWN_EVENTS_AGE, UDM_SETRANGE, 0, (LPARAM)MAKELONG(365, 1));
4017 
4018         SetDlgItemInt(hDlg, IDC_EDIT_MAXLOGSIZE, dwMaxSize, FALSE);
4019         SetDlgItemInt(hDlg, IDC_EDIT_EVENTS_AGE, (dwRetention == 0) ? 7 : dwRetention, FALSE);
4020 
4021         if (dwRetention == 0)
4022         {
4023             CheckRadioButton(hDlg, IDC_OVERWRITE_AS_NEEDED, IDC_NO_OVERWRITE, IDC_OVERWRITE_AS_NEEDED);
4024             EnableDlgItem(hDlg, IDC_EDIT_EVENTS_AGE, FALSE);
4025             EnableDlgItem(hDlg, IDC_UPDOWN_EVENTS_AGE, FALSE);
4026         }
4027         else if (dwRetention == INFINITE)
4028         {
4029             CheckRadioButton(hDlg, IDC_OVERWRITE_AS_NEEDED, IDC_NO_OVERWRITE, IDC_NO_OVERWRITE);
4030             EnableDlgItem(hDlg, IDC_EDIT_EVENTS_AGE, FALSE);
4031             EnableDlgItem(hDlg, IDC_UPDOWN_EVENTS_AGE, FALSE);
4032         }
4033         else
4034         {
4035             CheckRadioButton(hDlg, IDC_OVERWRITE_AS_NEEDED, IDC_NO_OVERWRITE, IDC_OVERWRITE_OLDER_THAN);
4036             EnableDlgItem(hDlg, IDC_EDIT_EVENTS_AGE, TRUE);
4037             EnableDlgItem(hDlg, IDC_UPDOWN_EVENTS_AGE, TRUE);
4038         }
4039     }
4040     else
4041     {
4042         // TODO: Hide the unused controls! Or, just use another type of property sheet!
4043     }
4044 }
4045 
4046 static
4047 VOID
4048 SavePropertiesDlg(HWND hDlg, PEVENTLOG EventLog)
4049 {
4050     LPWSTR lpLogName = EventLog->LogName;
4051 
4052     LONG Result;
4053     DWORD dwMaxSize = 0, dwRetention = 0;
4054     HKEY hLogKey;
4055     WCHAR *KeyPath;
4056     SIZE_T cbKeyPath;
4057 
4058     if (!EventLog->Permanent)
4059         return;
4060 
4061     cbKeyPath = (wcslen(EVENTLOG_BASE_KEY) + wcslen(lpLogName) + 1) * sizeof(WCHAR);
4062     KeyPath = HeapAlloc(GetProcessHeap(), 0, cbKeyPath);
4063     if (!KeyPath)
4064     {
4065         ShowWin32Error(ERROR_NOT_ENOUGH_MEMORY);
4066         return;
4067     }
4068 
4069     StringCbCopyW(KeyPath, cbKeyPath, EVENTLOG_BASE_KEY);
4070     StringCbCatW(KeyPath, cbKeyPath, lpLogName);
4071 
4072     Result = RegOpenKeyExW(hkMachine, KeyPath, 0, KEY_SET_VALUE, &hLogKey);
4073     HeapFree(GetProcessHeap(), 0, KeyPath);
4074     if (Result != ERROR_SUCCESS)
4075     {
4076         ShowWin32Error(Result);
4077         return;
4078     }
4079 
4080     dwMaxSize = GetDlgItemInt(hDlg, IDC_EDIT_MAXLOGSIZE, NULL, FALSE) * 1024;
4081     RegSetValueExW(hLogKey,
4082                    L"MaxSize",
4083                    0,
4084                    REG_DWORD,
4085                    (LPBYTE)&dwMaxSize,
4086                    sizeof(dwMaxSize));
4087 
4088     if (IsDlgButtonChecked(hDlg, IDC_OVERWRITE_AS_NEEDED) == BST_CHECKED)
4089         dwRetention = 0;
4090     else if (IsDlgButtonChecked(hDlg, IDC_NO_OVERWRITE) == BST_CHECKED)
4091         dwRetention = INFINITE;
4092     else // if (IsDlgButtonChecked(hDlg, IDC_OVERWRITE_OLDER_THAN) == BST_CHECKED)
4093         dwRetention = GetDlgItemInt(hDlg, IDC_EDIT_EVENTS_AGE, NULL, FALSE) * (24*3600);
4094 
4095     RegSetValueExW(hLogKey,
4096                    L"Retention",
4097                    0,
4098                    REG_DWORD,
4099                    (LPBYTE)&dwRetention,
4100                    sizeof(dwRetention));
4101 
4102     RegCloseKey(hLogKey);
4103 }
4104 
4105 /* Message handler for EventLog Properties dialog */
4106 INT_PTR CALLBACK
4107 EventLogPropProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
4108 {
4109     PEVENTLOG EventLog;
4110     WCHAR szText[MAX_LOADSTRING];
4111 
4112     EventLog = (PEVENTLOG)GetWindowLongPtrW(hDlg, DWLP_USER);
4113 
4114     switch (uMsg)
4115     {
4116         case WM_INITDIALOG:
4117         {
4118             EventLog = (PEVENTLOG)((LPPROPSHEETPAGE)lParam)->lParam;
4119             SetWindowLongPtrW(hDlg, DWLP_USER, (LONG_PTR)EventLog);
4120 
4121             InitPropertiesDlg(hDlg, EventLog);
4122 
4123             PropSheet_UnChanged(GetParent(hDlg), hDlg);
4124             return (INT_PTR)TRUE;
4125         }
4126 
4127         case WM_DESTROY:
4128             return (INT_PTR)TRUE;
4129 
4130         case WM_NOTIFY:
4131             switch (((LPNMHDR)lParam)->code)
4132             {
4133                 case PSN_APPLY:
4134                     PropSheet_UnChanged(GetParent(hDlg), hDlg);
4135                     SavePropertiesDlg(hDlg, EventLog);
4136                     return (INT_PTR)TRUE;
4137             }
4138             break;
4139 
4140         case WM_COMMAND:
4141             switch (LOWORD(wParam))
4142             {
4143                 case IDOK:
4144                 case IDCANCEL:
4145                     EndDialog(hDlg, LOWORD(wParam));
4146                     return (INT_PTR)TRUE;
4147 
4148                 case ID_CLEARLOG:
4149                 {
4150                     PEVENTLOGFILTER EventLogFilter = GetSelectedFilter(NULL);
4151                     if (EventLogFilter && ClearEvents(EventLogFilter))
4152                     {
4153                         Refresh(EventLogFilter);
4154                         InitPropertiesDlg(hDlg, EventLog);
4155                     }
4156                     return (INT_PTR)TRUE;
4157                 }
4158 
4159                 case IDC_EDIT_EVENTS_AGE:
4160                 case IDC_EDIT_MAXLOGSIZE:
4161                     if (HIWORD(wParam) == EN_CHANGE)
4162                     {
4163                         PropSheet_Changed(GetParent(hDlg), hDlg);
4164                     }
4165                     return (INT_PTR)TRUE;
4166 
4167                 case IDC_OVERWRITE_AS_NEEDED:
4168                 {
4169                     CheckRadioButton(hDlg, IDC_OVERWRITE_AS_NEEDED, IDC_NO_OVERWRITE, IDC_OVERWRITE_AS_NEEDED);
4170                     EnableDlgItem(hDlg, IDC_EDIT_EVENTS_AGE, FALSE);
4171                     EnableDlgItem(hDlg, IDC_UPDOWN_EVENTS_AGE, FALSE);
4172                     PropSheet_Changed(GetParent(hDlg), hDlg);
4173                     return (INT_PTR)TRUE;
4174                 }
4175 
4176                 case IDC_OVERWRITE_OLDER_THAN:
4177                 {
4178                     CheckRadioButton(hDlg, IDC_OVERWRITE_AS_NEEDED, IDC_NO_OVERWRITE, IDC_OVERWRITE_OLDER_THAN);
4179                     EnableDlgItem(hDlg, IDC_EDIT_EVENTS_AGE, TRUE);
4180                     EnableDlgItem(hDlg, IDC_UPDOWN_EVENTS_AGE, TRUE);
4181                     PropSheet_Changed(GetParent(hDlg), hDlg);
4182                     return (INT_PTR)TRUE;
4183                 }
4184 
4185                 case IDC_NO_OVERWRITE:
4186                 {
4187                     CheckRadioButton(hDlg, IDC_OVERWRITE_AS_NEEDED, IDC_NO_OVERWRITE, IDC_NO_OVERWRITE);
4188                     EnableDlgItem(hDlg, IDC_EDIT_EVENTS_AGE, FALSE);
4189                     EnableDlgItem(hDlg, IDC_UPDOWN_EVENTS_AGE, FALSE);
4190                     PropSheet_Changed(GetParent(hDlg), hDlg);
4191                     return (INT_PTR)TRUE;
4192                 }
4193 
4194                 case IDC_RESTOREDEFAULTS:
4195                 {
4196                     LoadStringW(hInst, IDS_RESTOREDEFAULTS, szText, _countof(szText));
4197 
4198                     if (MessageBoxW(hDlg, szText, szTitle, MB_YESNO | MB_ICONQUESTION) == IDYES)
4199                     {
4200                         CheckRadioButton(hDlg, IDC_OVERWRITE_AS_NEEDED, IDC_NO_OVERWRITE, IDC_OVERWRITE_AS_NEEDED);
4201                         /* Workstation: 512 KB; Server: 16384 KB */
4202                         SetDlgItemInt(hDlg, IDC_EDIT_MAXLOGSIZE, 5120, FALSE);
4203                         SetDlgItemInt(hDlg, IDC_EDIT_EVENTS_AGE, 7, FALSE);
4204                         EnableDlgItem(hDlg, IDC_EDIT_EVENTS_AGE, FALSE);
4205                         EnableDlgItem(hDlg, IDC_UPDOWN_EVENTS_AGE, FALSE);
4206                         PropSheet_Changed(GetParent(hDlg), hDlg);
4207                     }
4208                     return (INT_PTR)TRUE;
4209                 }
4210 
4211                 case IDHELP:
4212                     MessageBoxW(hDlg,
4213                                 L"Help not implemented yet!",
4214                                 L"Event Log",
4215                                 MB_OK | MB_ICONINFORMATION);
4216                     return (INT_PTR)TRUE;
4217 
4218                 default:
4219                     break;
4220             }
4221             break;
4222     }
4223 
4224     return (INT_PTR)FALSE;
4225 }
4226 
4227 INT_PTR
4228 EventLogProperties(HINSTANCE hInstance, HWND hWndParent, PEVENTLOGFILTER EventLogFilter)
4229 {
4230     INT_PTR ret = 0;
4231     PROPSHEETHEADERW psh;
4232     PROPSHEETPAGEW   psp[1]; // 2
4233 
4234     /*
4235      * Bail out if there is no available filter, or if the filter
4236      * contains more than one log.
4237      */
4238     if (!EventLogFilter)
4239         return 0;
4240 
4241     EventLogFilter_AddRef(EventLogFilter);
4242 
4243     if (EventLogFilter->NumOfEventLogs > 1 ||
4244         EventLogFilter->EventLogs[0] == NULL)
4245     {
4246         goto Quit;
4247     }
4248 
4249     /* Header */
4250     psh.dwSize      = sizeof(psh);
4251     psh.dwFlags     = PSH_PROPSHEETPAGE /*| PSH_USEICONID */ | PSH_PROPTITLE | PSH_HASHELP /*| PSH_NOCONTEXTHELP */ /*| PSH_USECALLBACK */;
4252     psh.hInstance   = hInstance;
4253     psh.hwndParent  = hWndParent;
4254     // psh.pszIcon     = MAKEINTRESOURCEW(IDI_APPICON); // Disabled because it only sets the small icon; the big icon is a stretched version of the small one.
4255     psh.pszCaption  = EventLogFilter->EventLogs[0]->LogName;
4256     psh.nStartPage  = 0;
4257     psh.ppsp        = psp;
4258     psh.nPages      = ARRAYSIZE(psp);
4259     // psh.pfnCallback = PropSheetCallback;
4260 
4261     /* Log properties page */
4262     psp[0].dwSize      = sizeof(psp[0]);
4263     psp[0].dwFlags     = PSP_HASHELP;
4264     psp[0].hInstance   = hInstance;
4265     psp[0].pszTemplate = MAKEINTRESOURCEW(IDD_LOGPROPERTIES_GENERAL);
4266     psp[0].pfnDlgProc  = EventLogPropProc;
4267     psp[0].lParam      = (LPARAM)EventLogFilter->EventLogs[0];
4268 
4269 #if 0
4270     /* TODO: Log sources page */
4271     psp[1].dwSize      = sizeof(psp[1]);
4272     psp[1].dwFlags     = PSP_HASHELP;
4273     psp[1].hInstance   = hInstance;
4274     psp[1].pszTemplate = MAKEINTRESOURCEW(IDD_GENERAL_PAGE);
4275     psp[1].pfnDlgProc  = GeneralPageWndProc;
4276     psp[1].lParam      = (LPARAM)EventLogFilter->EventLogs[0];
4277 #endif
4278 
4279     /* Create the property sheet */
4280     ret = PropertySheetW(&psh);
4281 
4282 Quit:
4283     EventLogFilter_Release(EventLogFilter);
4284     return ret;
4285 }
4286 
4287 /* Message handler for Event Details dialog */
4288 static HWND hWndDetailsCtrl = NULL; // May go into the DWLP_USER
4289 static HWND hWndGrip = NULL;
4290 static INT cxMin, cyMin;    // In window coordinates
4291 static INT cxOld, cyOld;    // In client coordinates
4292 
4293 INT_PTR CALLBACK
4294 EventDetails(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
4295 {
4296     switch (uMsg)
4297     {
4298         case WM_INITDIALOG:
4299         {
4300             LONG_PTR dwStyle;
4301             INT sbVXSize, sbHYSize;
4302             RECT rcWnd, rect;
4303 
4304             hWndDetailsCtrl = CreateEventDetailsCtrl(hInst, hDlg, lParam);
4305             if (!hWndDetailsCtrl)
4306             {
4307                 EndDialog(hDlg, 0);
4308                 return (INT_PTR)TRUE;
4309             }
4310 
4311             /* Create a size grip if the dialog has a sizing border */
4312             GetClientRect(hDlg, &rcWnd);
4313             dwStyle  = GetWindowLongPtrW(hDlg, GWL_STYLE);
4314             sbVXSize = GetSystemMetrics(SM_CXVSCROLL);
4315             sbHYSize = GetSystemMetrics(SM_CYHSCROLL);
4316             if (dwStyle & WS_THICKFRAME /* == WS_SIZEBOX */)
4317             {
4318                 hWndGrip = CreateWindowW(WC_SCROLLBARW,
4319                                          NULL,
4320                                          WS_CHILD | WS_VISIBLE | /**/ WS_CLIPSIBLINGS | /**/ SBS_SIZEGRIP | SBS_SIZEBOXBOTTOMRIGHTALIGN,
4321                                          rcWnd.right - sbVXSize,
4322                                          rcWnd.bottom - sbHYSize,
4323                                          sbVXSize, sbHYSize,
4324                                          hDlg,
4325                                          NULL,
4326                                          hInst,
4327                                          NULL);
4328             }
4329 
4330             // SetWindowLongPtrW(hDlg, DWLP_USER, (LONG_PTR)hWndDetailsCtrl);
4331 
4332             /*
4333              * Compute the minimum window size (in window coordinates) by
4334              * adding the widths/heights of the "Help" and "Close" buttons,
4335              * together with the margins, and add some minimal spacing
4336              * between the buttons.
4337              */
4338             GetWindowRect(hDlg, &rcWnd);
4339             cxMin = cyMin = 0;
4340 
4341             GetWindowRect(GetDlgItem(hDlg, IDHELP), &rect);
4342             cxMin += (rect.right - rect.left) + (rect.left - rcWnd.left); // == (rect.right - rcWnd.left);
4343             cyMin += (rect.bottom - rect.top) + (rcWnd.bottom - rect.bottom); // == (rcWnd.bottom - rect.top);
4344 
4345             GetWindowRect(GetDlgItem(hDlg, IDOK), &rect);
4346             cxMin += (rect.right - rect.left) + (rcWnd.right - rect.right); // == (rcWnd.right - rect.left);
4347             cyMin += (rect.bottom - rect.top) + (rcWnd.bottom - rect.bottom); // == (rcWnd.bottom - rect.top);
4348 
4349             /*
4350              * Convert the window rect from window to client coordinates
4351              * in order to retrieve the sizes of the left and top margins,
4352              * and add some extra space.
4353              */
4354             MapWindowPoints(HWND_DESKTOP /*NULL*/, hDlg, (LPPOINT)&rcWnd, sizeof(RECT)/sizeof(POINT));
4355 
4356             cxMin += -2*rcWnd.left;   // Minimal spacing between the buttons == 2 * left margin
4357             cyMin += -rcWnd.top + 12; // Add some space on top
4358 
4359             GetClientRect(hDlg, &rcWnd);
4360             cxOld = rcWnd.right - rcWnd.left;
4361             cyOld = rcWnd.bottom - rcWnd.top;
4362 
4363             /* Show event info on dialog control */
4364             SendMessageW(hWndDetailsCtrl, EVT_DISPLAY, 0, 0);
4365 
4366             // SetWindowPos(hWndDetailsCtrl, NULL,
4367                          // 0, 0,
4368                          // (rcWnd.right - rcWnd.left),
4369                          // (rcWnd.bottom - rcWnd.top),
4370                          // SWP_NOZORDER | SWP_NOACTIVATE | SWP_SHOWWINDOW);
4371 
4372             /*
4373              * Hide the placeholder static control and show the event details
4374              * control instead. Note that the placeholder is here so far just
4375              * to get the dimensions right in the dialog resource editor.
4376              * I plan to remove it and use a custom control with a suitable
4377              * window class for it, that would create the event details control
4378              * instead.
4379              */
4380             ShowWindow(GetDlgItem(hDlg, IDC_STATIC), SW_HIDE);
4381             ShowWindow(hWndDetailsCtrl, SW_SHOW);
4382             return (INT_PTR)TRUE;
4383         }
4384 
4385         case WM_DESTROY:
4386             if (IsWindow(hWndDetailsCtrl))
4387                 DestroyWindow(hWndDetailsCtrl);
4388             hWndDetailsCtrl = NULL;
4389             return (INT_PTR)TRUE;
4390 
4391         case WM_COMMAND:
4392             switch (LOWORD(wParam))
4393             {
4394                 case IDOK:
4395                 case IDCANCEL:
4396                     EndDialog(hDlg, LOWORD(wParam));
4397                     return (INT_PTR)TRUE;
4398 
4399                 case IDHELP:
4400                     MessageBoxW(hDlg,
4401                                 L"Help not implemented yet!",
4402                                 L"Event Log",
4403                                 MB_OK | MB_ICONINFORMATION);
4404                     return (INT_PTR)TRUE;
4405 
4406                 default:
4407                     break;
4408             }
4409             break;
4410 
4411         case WM_SETCURSOR:
4412             if (((HWND)wParam == hWndGrip) && (LOWORD(lParam) == HTCLIENT))
4413             {
4414                 SetCursor(LoadCursorW(NULL, IDC_SIZENWSE));
4415                 SetWindowLongPtrW(hDlg, DWLP_MSGRESULT, TRUE);
4416                 return (INT_PTR)TRUE;
4417             }
4418             break;
4419 
4420         case WM_SIZING:
4421         {
4422             /* Forbid resizing the dialog smaller than its minimal size */
4423             PRECT dragRect = (PRECT)lParam;
4424 
4425             if ((wParam == WMSZ_LEFT) || (wParam == WMSZ_TOPLEFT) || (wParam == WMSZ_BOTTOMLEFT))
4426             {
4427                 if (dragRect->right - dragRect->left < cxMin)
4428                     dragRect->left = dragRect->right - cxMin;
4429             }
4430 
4431             if ((wParam == WMSZ_RIGHT) || (wParam == WMSZ_TOPRIGHT) || (wParam == WMSZ_BOTTOMRIGHT))
4432             {
4433                 if (dragRect->right - dragRect->left < cxMin)
4434                     dragRect->right = dragRect->left + cxMin;
4435             }
4436 
4437             if ((wParam == WMSZ_TOP) || (wParam == WMSZ_TOPLEFT) || (wParam == WMSZ_TOPRIGHT))
4438             {
4439                 if (dragRect->bottom - dragRect->top < cyMin)
4440                     dragRect->top = dragRect->bottom - cyMin;
4441             }
4442 
4443             if ((wParam == WMSZ_BOTTOM) || (wParam == WMSZ_BOTTOMLEFT) || (wParam == WMSZ_BOTTOMRIGHT))
4444             {
4445                 if (dragRect->bottom - dragRect->top < cyMin)
4446                     dragRect->bottom = dragRect->top + cyMin;
4447             }
4448 
4449             SetWindowLongPtrW(hDlg, DWLP_MSGRESULT, TRUE);
4450             return (INT_PTR)TRUE;
4451         }
4452 
4453         case WM_SIZE:
4454         {
4455             INT cx = LOWORD(lParam);
4456             INT cy = HIWORD(lParam);
4457 
4458             HDWP hdwp;
4459             HWND hItemWnd;
4460             RECT rect;
4461 
4462             hdwp = BeginDeferWindowPos(4);
4463 
4464             /* Resize the event details control window */
4465 
4466             hItemWnd = hWndDetailsCtrl;
4467             GetWindowRect(hItemWnd, &rect);
4468             MapWindowPoints(HWND_DESKTOP /*NULL*/, hDlg, (LPPOINT)&rect, sizeof(RECT)/sizeof(POINT));
4469 
4470             if (hdwp)
4471                 hdwp = DeferWindowPos(hdwp,
4472                                       hItemWnd,
4473                                       HWND_TOP,
4474                                       0, 0,
4475                                       (rect.right - rect.left) + (cx - cxOld),
4476                                       (rect.bottom - rect.top) + (cy - cyOld),
4477                                       SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
4478 
4479             /* Move the buttons */
4480 
4481             hItemWnd = GetDlgItem(hDlg, IDHELP);
4482             GetWindowRect(hItemWnd, &rect);
4483             MapWindowPoints(HWND_DESKTOP /*NULL*/, hDlg, (LPPOINT)&rect, sizeof(RECT)/sizeof(POINT));
4484 
4485             if (hdwp)
4486                 hdwp = DeferWindowPos(hdwp,
4487                                       hItemWnd,
4488                                       HWND_TOP,
4489                                       rect.left,
4490                                       rect.top + (cy - cyOld),
4491                                       0, 0,
4492                                       SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
4493 
4494             hItemWnd = GetDlgItem(hDlg, IDOK);
4495             GetWindowRect(hItemWnd, &rect);
4496             MapWindowPoints(HWND_DESKTOP /*NULL*/, hDlg, (LPPOINT)&rect, sizeof(RECT)/sizeof(POINT));
4497 
4498             if (hdwp)
4499                 hdwp = DeferWindowPos(hdwp,
4500                                       hItemWnd,
4501                                       HWND_TOP,
4502                                       rect.left + (cx - cxOld),
4503                                       rect.top  + (cy - cyOld),
4504                                       0, 0,
4505                                       SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
4506 
4507             /* Move the size grip */
4508             if (hWndGrip && hdwp)
4509             {
4510                 GetWindowRect(hWndGrip, &rect);
4511                 MapWindowPoints(HWND_DESKTOP /*NULL*/, hDlg, (LPPOINT)&rect, sizeof(RECT)/sizeof(POINT));
4512 
4513                 hdwp = DeferWindowPos(hdwp,
4514                                       hWndGrip,
4515                                       HWND_TOP,
4516                                       rect.left + (cx - cxOld),
4517                                       rect.top  + (cy - cyOld),
4518                                       0, 0,
4519                                       SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
4520             }
4521 
4522             if (hdwp)
4523                 EndDeferWindowPos(hdwp);
4524 
4525             /* Hide the size grip if we are in maximized mode */
4526             if (hWndGrip)
4527                 ShowWindow(hWndGrip, (wParam == SIZE_MAXIMIZED) ? SW_HIDE : SW_SHOW);
4528 
4529             cxOld = cx;
4530             cyOld = cy;
4531 
4532             SetWindowLongPtrW(hDlg, DWLP_MSGRESULT, 0);
4533             return (INT_PTR)TRUE;
4534         }
4535     }
4536 
4537     return (INT_PTR)FALSE;
4538 }
4539