main
cpp 555 lines 16.2 KB
Raw
1 // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3 #include "precomp.h"
4
5 static const LPCWSTR THMVWR_WINDOW_CLASS_MAIN = L"ThmViewerMain";
6
7 static THEME* vpTheme = NULL;
8 static DWORD vdwDisplayThreadId = 0;
9 static LPWSTR vsczThemeLoadErrors = NULL;
10
11 enum THMVWR_CONTROL
12 {
13 // Non-paged controls
14 THMVWR_CONTROL_TREE = THEME_FIRST_ASSIGN_CONTROL_ID,
15 };
16
17 // Internal functions
18
19 static HRESULT ProcessCommandLine(
20 __in_z_opt LPCWSTR wzCommandLine,
21 __out_z LPWSTR* psczThemeFile,
22 __out_z LPWSTR* psczWxlFile
23 );
24 static HRESULT CreateTheme(
25 __in HINSTANCE hInstance,
26 __out THEME** ppTheme
27 );
28 static HRESULT CreateMainWindowClass(
29 __in HINSTANCE hInstance,
30 __in THEME* pTheme,
31 __out ATOM* pAtom
32 );
33 static LRESULT CALLBACK MainWndProc(
34 __in HWND hWnd,
35 __in UINT uMsg,
36 __in WPARAM wParam,
37 __in LPARAM lParam
38 );
39 static void OnThemeLoadBegin(
40 __in_z_opt LPWSTR sczThemeLoadErrors
41 );
42 static void OnThemeLoadError(
43 __in THEME* pTheme,
44 __in HRESULT hrFailure
45 );
46 static void OnNewTheme(
47 __in THEME* pTheme,
48 __in HWND hWnd,
49 __in HANDLE_THEME* pHandle
50 );
51 static BOOL OnThemeLoadingControl(
52 __in const THEME_LOADINGCONTROL_ARGS* pArgs,
53 __in THEME_LOADINGCONTROL_RESULTS* pResults
54 );
55 static BOOL OnThemeControlWmNotify(
56 __in const THEME_CONTROLWMNOTIFY_ARGS* pArgs,
57 __in THEME_CONTROLWMNOTIFY_RESULTS* pResults
58 );
59 static void CALLBACK ThmviewerTraceError(
60 __in_z LPCSTR szFile,
61 __in int iLine,
62 __in REPORT_LEVEL rl,
63 __in UINT source,
64 __in HRESULT hrError,
65 __in_z __format_string LPCSTR szFormat,
66 __in va_list args
67 );
68
69 static COMDLG_FILTERSPEC vrgFilters[] =
70 {
71 { L"Theme Files (*.thm)", L"*.thm" },
72 { L"XML Files (*.xml)", L"*.xml" },
73 { L"All Files (*.*)", L"*.*" },
74 };
75
76
77 int WINAPI wWinMain(
78 __in HINSTANCE hInstance,
79 __in_opt HINSTANCE /* hPrevInstance */,
80 __in_z LPWSTR lpCmdLine,
81 __in int /*nCmdShow*/
82 )
83 {
84 ::HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, NULL, 0);
85
86 HRESULT hr = S_OK;
87 BOOL fComInitialized = FALSE;
88 LPWSTR sczThemeFile = NULL;
89 LPWSTR sczWxlFile = NULL;
90 ATOM atom = 0;
91 HWND hWnd = NULL;
92
93 HANDLE hDisplayThread = NULL;
94 HANDLE hLoadThread = NULL;
95
96 BOOL fRet = FALSE;
97 MSG msg = { };
98
99 hr = ::CoInitialize(NULL);
100 ExitOnFailure(hr, "Failed to initialize COM.");
101 fComInitialized = TRUE;
102
103 DutilInitialize(&ThmviewerTraceError);
104
105 hr = ProcessCommandLine(lpCmdLine, &sczThemeFile, &sczWxlFile);
106 ExitOnFailure(hr, "Failed to process command line.");
107
108 hr = CreateTheme(hInstance, &vpTheme);
109 ExitOnFailure(hr, "Failed to create theme.");
110
111 hr = CreateMainWindowClass(hInstance, vpTheme, &atom);
112 ExitOnFailure(hr, "Failed to create thmviewer main window.");
113
114 hr = ThemeCreateParentWindow(vpTheme, 0, reinterpret_cast<LPCWSTR>(atom), vpTheme->sczCaption, vpTheme->dwStyle, CW_USEDEFAULT, CW_USEDEFAULT, HWND_DESKTOP, hInstance, NULL, THEME_WINDOW_INITIAL_POSITION_DEFAULT, &hWnd);
115 ExitOnFailure(hr, "Failed to create thmviewer parent window.");
116
117 if (!sczThemeFile)
118 {
119 // Prompt for a path to the theme file.
120 hr = WnduShowOpenFileDialog(hWnd, TRUE, TRUE, vpTheme->sczCaption, vrgFilters, countof(vrgFilters), 1, NULL, &sczThemeFile);
121 if (FAILED(hr))
122 {
123 ::MessageBoxW(hWnd, L"Must specify a path to theme file.", vpTheme->sczCaption, MB_OK | MB_ICONERROR);
124 ExitFunction1(hr = E_INVALIDARG);
125 }
126 }
127
128 hr = DisplayStart(hInstance, hWnd, &hDisplayThread, &vdwDisplayThreadId);
129 ExitOnFailure(hr, "Failed to start display.");
130
131 hr = LoadStart(sczThemeFile, sczWxlFile, hWnd, &hLoadThread);
132 ExitOnFailure(hr, "Failed to start load.");
133
134 // message pump
135 while (0 != (fRet = ::GetMessageW(&msg, NULL, 0, 0)))
136 {
137 if (-1 == fRet)
138 {
139 hr = E_UNEXPECTED;
140 ExitOnFailure(hr, "Unexpected return value from message pump.");
141 }
142 else if (!ThemeHandleKeyboardMessage(vpTheme, msg.hwnd, &msg))
143 {
144 ::TranslateMessage(&msg);
145 ::DispatchMessageW(&msg);
146 }
147 }
148
149 LExit:
150 if (::IsWindow(hWnd))
151 {
152 ::DestroyWindow(hWnd);
153 }
154
155 if (hDisplayThread)
156 {
157 ::PostThreadMessageW(vdwDisplayThreadId, WM_QUIT, 0, 0);
158 ::WaitForSingleObject(hDisplayThread, 10000);
159 ::CloseHandle(hDisplayThread);
160 }
161
162 // TODO: come up with a good way to kill the load thread, probably need to switch
163 // the ReadDirectoryW() to overlapped mode.
164 ReleaseHandle(hLoadThread);
165
166 if (atom && !::UnregisterClassW(reinterpret_cast<LPCWSTR>(atom), hInstance))
167 {
168 DWORD er = ::GetLastError();
169 er = er;
170 }
171
172 ThemeFree(vpTheme);
173 ThemeUninitialize();
174 DutilUninitialize();
175
176 // uninitialize COM
177 if (fComInitialized)
178 {
179 ::CoUninitialize();
180 }
181
182 ReleaseNullStr(vsczThemeLoadErrors);
183 ReleaseStr(sczThemeFile);
184 ReleaseStr(sczWxlFile);
185 return hr;
186 }
187
188 static void CALLBACK ThmviewerTraceError(
189 __in_z LPCSTR /*szFile*/,
190 __in int /*iLine*/,
191 __in REPORT_LEVEL /*rl*/,
192 __in UINT source,
193 __in HRESULT hrError,
194 __in_z __format_string LPCSTR szFormat,
195 __in va_list args
196 )
197 {
198 HRESULT hr = S_OK;
199 LPSTR sczFormattedAnsi = NULL;
200 LPWSTR sczMessage = NULL;
201
202 if (DUTIL_SOURCE_THMUTIL != source)
203 {
204 ExitFunction();
205 }
206
207 hr = StrAnsiAllocFormattedArgs(&sczFormattedAnsi, szFormat, args);
208 ExitOnFailure(hr, "Failed to format error log string.");
209
210 hr = StrAllocFormatted(&sczMessage, L"Error 0x%08x: %S\r\n", hrError, sczFormattedAnsi);
211 ExitOnFailure(hr, "Failed to prepend error number to error log string.");
212
213 hr = StrAllocConcat(&vsczThemeLoadErrors, sczMessage, 0);
214 ExitOnFailure(hr, "Failed to append theme load error.");
215
216 LExit:
217 ReleaseStr(sczFormattedAnsi);
218 ReleaseStr(sczMessage);
219 }
220
221
222 //
223 // ProcessCommandLine - process the provided command line arguments.
224 //
225 static HRESULT ProcessCommandLine(
226 __in_z_opt LPCWSTR wzCommandLine,
227 __out_z LPWSTR* psczThemeFile,
228 __out_z LPWSTR* psczWxlFile
229 )
230 {
231 HRESULT hr = S_OK;
232 int argc = 0;
233 LPWSTR* argv = NULL;
234
235 if (wzCommandLine && *wzCommandLine)
236 {
237 hr = AppParseCommandLine(wzCommandLine, &argc, &argv);
238 ExitOnFailure(hr, "Failed to parse command line.");
239
240 for (int i = 0; i < argc; ++i)
241 {
242 if (argv[i][0] == L'-' || argv[i][0] == L'/')
243 {
244 if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], -1, L"lang", -1))
245 {
246 if (i + 1 >= argc)
247 {
248 ExitOnRootFailure(hr = E_INVALIDARG, "Must specify a language.");
249 }
250
251 ++i;
252 }
253 }
254 else
255 {
256 LPCWSTR wzExtension = PathExtension(argv[i]);
257 if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, wzExtension, -1, L".wxl", -1))
258 {
259 hr = StrAllocString(psczWxlFile, argv[i], 0);
260 }
261 else
262 {
263 hr = StrAllocString(psczThemeFile, argv[i], 0);
264 }
265 ExitOnFailure(hr, "Failed to copy path to file.");
266 }
267 }
268 }
269
270 LExit:
271 if (argv)
272 {
273 AppFreeCommandLineArgs(argv);
274 }
275
276 return hr;
277 }
278
279 static HRESULT CreateTheme(
280 __in HINSTANCE hInstance,
281 __out THEME** ppTheme
282 )
283 {
284 HRESULT hr = S_OK;
285
286 hr = ThemeInitialize(hInstance);
287 ExitOnFailure(hr, "Failed to initialize theme manager.");
288
289 hr = ThemeLoadFromResource(hInstance, MAKEINTRESOURCEA(THMVWR_RES_THEME_FILE), ppTheme);
290 ExitOnFailure(hr, "Failed to load theme from thmviewer.thm.");
291
292 LExit:
293 return hr;
294 }
295
296 static HRESULT CreateMainWindowClass(
297 __in HINSTANCE hInstance,
298 __in THEME* pTheme,
299 __out ATOM* pAtom
300 )
301 {
302 HRESULT hr = S_OK;
303 ATOM atom = 0;
304 WNDCLASSW wc = { };
305
306 ThemeInitializeWindowClass(pTheme, &wc, MainWndProc, hInstance, THMVWR_WINDOW_CLASS_MAIN);
307
308 atom = ::RegisterClassW(&wc);
309 if (!atom)
310 {
311 ExitWithLastError(hr, "Failed to register main windowclass .");
312 }
313
314 *pAtom = atom;
315
316 LExit:
317 return hr;
318 }
319
320 static LRESULT CALLBACK MainWndProc(
321 __in HWND hWnd,
322 __in UINT uMsg,
323 __in WPARAM wParam,
324 __in LPARAM lParam
325 )
326 {
327 HANDLE_THEME* pHandleTheme = reinterpret_cast<HANDLE_THEME*>(::GetWindowLongPtrW(hWnd, GWLP_USERDATA));
328
329 switch (uMsg)
330 {
331 case WM_NCCREATE:
332 {
333 //LPCREATESTRUCT lpcs = reinterpret_cast<LPCREATESTRUCT>(lParam);
334 //pBA = reinterpret_cast<CWixStandardBootstrapperApplication*>(lpcs->lpCreateParams);
335 //::SetWindowLongPtrW(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pBA));
336 }
337 break;
338
339 case WM_NCDESTROY:
340 DecrementHandleTheme(pHandleTheme);
341 ::SetWindowLongPtrW(hWnd, GWLP_USERDATA, 0);
342 ::PostQuitMessage(0);
343 break;
344
345 case WM_THMVWR_THEME_LOAD_BEGIN:
346 OnThemeLoadBegin(vsczThemeLoadErrors);
347 return 0;
348
349 case WM_THMVWR_THEME_LOAD_ERROR:
350 OnThemeLoadError(vpTheme, lParam);
351 return 0;
352
353 case WM_THMVWR_NEW_THEME:
354 OnNewTheme(vpTheme, hWnd, reinterpret_cast<HANDLE_THEME*>(lParam));
355 return 0;
356
357 case WM_THMUTIL_LOADING_CONTROL:
358 return OnThemeLoadingControl(reinterpret_cast<THEME_LOADINGCONTROL_ARGS*>(wParam), reinterpret_cast<THEME_LOADINGCONTROL_RESULTS*>(lParam));
359
360 case WM_THMUTIL_CONTROL_WM_NOTIFY:
361 return OnThemeControlWmNotify(reinterpret_cast<THEME_CONTROLWMNOTIFY_ARGS*>(wParam), reinterpret_cast<THEME_CONTROLWMNOTIFY_RESULTS*>(lParam));
362 }
363
364 return ThemeDefWindowProc(vpTheme, hWnd, uMsg, wParam, lParam);
365 }
366
367 static void OnThemeLoadBegin(
368 __in_z_opt LPWSTR sczThemeLoadErrors
369 )
370 {
371 ReleaseNullStr(sczThemeLoadErrors);
372 }
373
374 static void OnThemeLoadError(
375 __in THEME* pTheme,
376 __in HRESULT hrFailure
377 )
378 {
379 HRESULT hr = S_OK;
380 LPWSTR sczMessage = NULL;
381 LPWSTR* psczErrors = NULL;
382 UINT cErrors = 0;
383 TVINSERTSTRUCTW tvi = { };
384 const THEME_CONTROL* pTreeControl = NULL;
385
386 if (!ThemeControlExistsById(pTheme, THMVWR_CONTROL_TREE, &pTreeControl))
387 {
388 ExitWithRootFailure(hr, E_INVALIDSTATE, "THMVWR_CONTROL_TREE control doesn't exist.");
389 }
390
391 // Add the application node.
392 tvi.hParent = NULL;
393 tvi.hInsertAfter = TVI_ROOT;
394 tvi.item.mask = TVIF_TEXT | TVIF_PARAM;
395 tvi.item.lParam = 0;
396 tvi.item.pszText = L"Failed to load theme.";
397 tvi.hParent = reinterpret_cast<HTREEITEM>(::SendMessage(pTreeControl->hWnd, TVM_INSERTITEMW, 0, reinterpret_cast<LPARAM>(&tvi)));
398
399 if (!vsczThemeLoadErrors)
400 {
401 hr = StrAllocFormatted(&sczMessage, L"Error 0x%08x.", hrFailure);
402 ExitOnFailure(hr, "Failed to format error message.");
403
404 tvi.item.pszText = sczMessage;
405 ::SendMessage(pTreeControl->hWnd, TVM_INSERTITEMW, 0, reinterpret_cast<LPARAM>(&tvi));
406
407 hr = StrAllocFromError(&sczMessage, hrFailure, NULL);
408 ExitOnFailure(hr, "Failed to format error message text.");
409
410 tvi.item.pszText = sczMessage;
411 ::SendMessage(pTreeControl->hWnd, TVM_INSERTITEMW, 0, reinterpret_cast<LPARAM>(&tvi));
412 }
413 else
414 {
415 hr = StrSplitAllocArray(&psczErrors, &cErrors, vsczThemeLoadErrors, L"\r\n");
416 ExitOnFailure(hr, "Failed to split theme load errors.");
417
418 for (DWORD i = 0; i < cErrors; ++i)
419 {
420 tvi.item.pszText = psczErrors[i];
421 ::SendMessage(pTreeControl->hWnd, TVM_INSERTITEMW, 0, reinterpret_cast<LPARAM>(&tvi));
422 }
423 }
424
425 ::SendMessage(pTreeControl->hWnd, TVM_EXPAND, TVE_EXPAND, reinterpret_cast<LPARAM>(tvi.hParent));
426
427 LExit:
428 ReleaseStr(sczMessage);
429 ReleaseMem(psczErrors);
430 }
431
432
433 static void OnNewTheme(
434 __in THEME* pTheme,
435 __in HWND hWnd,
436 __in HANDLE_THEME* pHandle
437 )
438 {
439 const THEME_CONTROL* pTreeControl = NULL;
440 HANDLE_THEME* pOldHandle = reinterpret_cast<HANDLE_THEME*>(::GetWindowLongPtrW(hWnd, GWLP_USERDATA));
441 THEME* pNewTheme = pHandle->pTheme;
442
443 WCHAR wzSelectedPage[MAX_PATH] = { };
444 HTREEITEM htiSelected = NULL;
445 TVINSERTSTRUCTW tvi = { };
446 TVITEMW item = { };
447
448 if (pOldHandle)
449 {
450 DecrementHandleTheme(pOldHandle);
451 pOldHandle = NULL;
452 }
453
454 // Pass the new theme handle to the display thread so it can get the display window prepared
455 // to show the new theme.
456 IncrementHandleTheme(pHandle);
457 ::PostThreadMessageW(vdwDisplayThreadId, WM_THMVWR_NEW_THEME, 0, reinterpret_cast<LPARAM>(pHandle));
458
459 ::SetWindowLongPtrW(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pHandle));
460
461 if (!ThemeControlExistsById(pTheme, THMVWR_CONTROL_TREE, &pTreeControl))
462 {
463 TraceError(E_INVALIDSTATE, "Tree control doesn't exist.");
464 return;
465 }
466
467 // Remember the currently selected item by name so we can try to automatically select it later.
468 // Otherwise, the user would see their window destroyed after every save of their theme file and
469 // have to click to get the window back.
470 item.mask = TVIF_TEXT;
471 item.pszText = wzSelectedPage;
472 item.cchTextMax = countof(wzSelectedPage);
473 item.hItem = reinterpret_cast<HTREEITEM>(::SendMessage(pTreeControl->hWnd, TVM_GETNEXTITEM, TVGN_CARET, NULL));
474 ::SendMessage(pTreeControl->hWnd, TVM_GETITEM, 0, reinterpret_cast<LPARAM>(&item));
475
476 // Remove the previous items in the tree.
477 ::SendMessage(pTreeControl->hWnd, TVM_DELETEITEM, 0, reinterpret_cast<LPARAM>(TVI_ROOT));
478
479 // Add the application node.
480 tvi.hParent = NULL;
481 tvi.hInsertAfter = TVI_ROOT;
482 tvi.item.mask = TVIF_TEXT | TVIF_PARAM;
483 tvi.item.lParam = 0;
484 tvi.item.pszText = pHandle && pHandle->pTheme && pHandle->pTheme->sczCaption ? pHandle->pTheme->sczCaption : L"Window";
485
486 // Add the pages.
487 tvi.hParent = reinterpret_cast<HTREEITEM>(::SendMessage(pTreeControl->hWnd, TVM_INSERTITEMW, 0, reinterpret_cast<LPARAM>(&tvi)));
488 tvi.hInsertAfter = TVI_SORT;
489 for (DWORD i = 0; i < pNewTheme->cPages; ++i)
490 {
491 THEME_PAGE* pPage = pNewTheme->rgPages + i;
492 if (pPage->sczName && *pPage->sczName)
493 {
494 tvi.item.pszText = pPage->sczName;
495 tvi.item.lParam = i + 1; //prgdwPageIds[i]; - TODO: do the right thing here by calling ThemeGetPageIds(), should not assume we know how the page ids will be calculated.
496
497 HTREEITEM hti = reinterpret_cast<HTREEITEM>(::SendMessage(pTreeControl->hWnd, TVM_INSERTITEMW, 0, reinterpret_cast<LPARAM>(&tvi)));
498 if (*wzSelectedPage && CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, pPage->sczName, -1, wzSelectedPage, -1))
499 {
500 htiSelected = hti;
501 }
502 }
503 }
504
505 if (*wzSelectedPage && CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, L"Application", -1, wzSelectedPage, -1))
506 {
507 htiSelected = tvi.hParent;
508 }
509
510 ::SendMessage(pTreeControl->hWnd, TVM_EXPAND, TVE_EXPAND, reinterpret_cast<LPARAM>(tvi.hParent));
511 if (htiSelected)
512 {
513 ::SendMessage(pTreeControl->hWnd, TVM_SELECTITEM, TVGN_CARET, reinterpret_cast<LPARAM>(htiSelected));
514 }
515 }
516
517 static BOOL OnThemeLoadingControl(
518 __in const THEME_LOADINGCONTROL_ARGS* pArgs,
519 __in THEME_LOADINGCONTROL_RESULTS* pResults
520 )
521 {
522 if (CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, pArgs->pThemeControl->sczName, -1, L"Tree", -1))
523 {
524 pResults->wId = THMVWR_CONTROL_TREE;
525 }
526
527 pResults->hr = S_OK;
528 return TRUE;
529 }
530
531 static BOOL OnThemeControlWmNotify(
532 __in const THEME_CONTROLWMNOTIFY_ARGS* pArgs,
533 __in THEME_CONTROLWMNOTIFY_RESULTS* /*pResults*/
534 )
535 {
536 BOOL fProcessed = FALSE;
537
538 switch (pArgs->lParam->code)
539 {
540 case TVN_SELCHANGEDW:
541 switch (pArgs->pThemeControl->wId)
542 {
543 case THMVWR_CONTROL_TREE:
544 NMTREEVIEWW* ptv = reinterpret_cast<NMTREEVIEWW*>(pArgs->lParam);
545 ::PostThreadMessageW(vdwDisplayThreadId, WM_THMVWR_SHOWPAGE, SW_HIDE, ptv->itemOld.lParam);
546 ::PostThreadMessageW(vdwDisplayThreadId, WM_THMVWR_SHOWPAGE, SW_SHOW, ptv->itemNew.lParam);
547
548 fProcessed = TRUE;
549 break;
550 }
551 break;
552 }
553
554 return fProcessed;
555 }