aboutsummaryrefslogtreecommitdiff
path: root/src/tools/thmviewer
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/thmviewer')
-rw-r--r--src/tools/thmviewer/Resources/LoremIpsum.rtfbin0 -> 4870 bytes
-rw-r--r--src/tools/thmviewer/Resources/thm.xml11
-rw-r--r--src/tools/thmviewer/display.cpp339
-rw-r--r--src/tools/thmviewer/load.cpp221
-rw-r--r--src/tools/thmviewer/precomp.cpp3
-rw-r--r--src/tools/thmviewer/precomp.h72
-rw-r--r--src/tools/thmviewer/resource.h16
-rw-r--r--src/tools/thmviewer/thmviewer.cpp564
-rw-r--r--src/tools/thmviewer/thmviewer.manifest19
-rw-r--r--src/tools/thmviewer/thmviewer.rc12
-rw-r--r--src/tools/thmviewer/thmviewer.v3.ncrunchproject7
-rw-r--r--src/tools/thmviewer/thmviewer.vcxproj69
-rw-r--r--src/tools/thmviewer/thmviewer.vcxproj.filters56
13 files changed, 1389 insertions, 0 deletions
diff --git a/src/tools/thmviewer/Resources/LoremIpsum.rtf b/src/tools/thmviewer/Resources/LoremIpsum.rtf
new file mode 100644
index 00000000..1ab0e65b
--- /dev/null
+++ b/src/tools/thmviewer/Resources/LoremIpsum.rtf
Binary files differ
diff --git a/src/tools/thmviewer/Resources/thm.xml b/src/tools/thmviewer/Resources/thm.xml
new file mode 100644
index 00000000..6394f0f1
--- /dev/null
+++ b/src/tools/thmviewer/Resources/thm.xml
@@ -0,0 +1,11 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- 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. -->
3
4
5<Theme>
6 <Font Id="0" Height="-48" Weight="700" Foreground="$windowtext">Consolas</Font>
7 <Font Id="1" Height="-12" Weight="700" Foreground="$windowtext" Background="$window">Consolas</Font>
8 <Window Width="220" Height="200" FontId="0" Caption="Theme Viewer">
9 <TreeView Name="Tree" X="0" Y="0" Width="0" Height="0" FontId="1" Visible="yes" FullRowSelect="yes" HasButtons="yes" AlwaysShowSelect="yes" HasLines="yes" LinesAtRoot="yes"/>
10 </Window>
11</Theme>
diff --git a/src/tools/thmviewer/display.cpp b/src/tools/thmviewer/display.cpp
new file mode 100644
index 00000000..444c6cfb
--- /dev/null
+++ b/src/tools/thmviewer/display.cpp
@@ -0,0 +1,339 @@
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
5static const LPCWSTR THMVWR_WINDOW_CLASS_DISPLAY = L"ThmViewerDisplay";
6
7struct DISPLAY_THREAD_CONTEXT
8{
9 HWND hWnd;
10 HINSTANCE hInstance;
11
12 HANDLE hInit;
13};
14
15static DWORD WINAPI DisplayThreadProc(
16 __in LPVOID pvContext
17 );
18static LRESULT CALLBACK DisplayWndProc(
19 __in HWND hWnd,
20 __in UINT uMsg,
21 __in WPARAM wParam,
22 __in LPARAM lParam
23 );
24static BOOL DisplayOnThmLoadedControl(
25 __in THEME* pTheme,
26 __in const THEME_LOADEDCONTROL_ARGS* args,
27 __in THEME_LOADEDCONTROL_RESULTS* results
28 );
29
30
31extern "C" HRESULT DisplayStart(
32 __in HINSTANCE hInstance,
33 __in HWND hWnd,
34 __out HANDLE *phThread,
35 __out DWORD* pdwThreadId
36 )
37{
38 HRESULT hr = S_OK;
39 HANDLE rgHandles[2] = { };
40 DISPLAY_THREAD_CONTEXT context = { };
41
42 rgHandles[0] = ::CreateEventW(NULL, TRUE, FALSE, NULL);
43 ExitOnNullWithLastError(rgHandles[0], hr, "Failed to create load init event.");
44
45 context.hWnd = hWnd;
46 context.hInstance = hInstance;
47 context.hInit = rgHandles[0];
48
49 rgHandles[1] = ::CreateThread(NULL, 0, DisplayThreadProc, reinterpret_cast<LPVOID>(&context), 0, pdwThreadId);
50 ExitOnNullWithLastError(rgHandles[1], hr, "Failed to create display thread.");
51
52 ::WaitForMultipleObjects(countof(rgHandles), rgHandles, FALSE, INFINITE);
53
54 *phThread = rgHandles[1];
55 rgHandles[1] = NULL;
56
57LExit:
58 ReleaseHandle(rgHandles[1]);
59 ReleaseHandle(rgHandles[0]);
60 return hr;
61}
62
63static DWORD WINAPI DisplayThreadProc(
64 __in LPVOID pvContext
65 )
66{
67 HRESULT hr = S_OK;
68
69 DISPLAY_THREAD_CONTEXT* pContext = static_cast<DISPLAY_THREAD_CONTEXT*>(pvContext);
70 HINSTANCE hInstance = pContext->hInstance;
71 HWND hwndParent = pContext->hWnd;
72
73 // We can signal the initialization event as soon as we have copied the context
74 // values into local variables.
75 ::SetEvent(pContext->hInit);
76
77 BOOL fComInitialized = FALSE;
78
79 HANDLE_THEME* pCurrentHandle = NULL;
80 ATOM atomWc = 0;
81 WNDCLASSW wc = { };
82 HWND hWnd = NULL;
83 RECT rc = { };
84 int x = CW_USEDEFAULT;
85 int y = CW_USEDEFAULT;
86
87 BOOL fRedoMsg = FALSE;
88 BOOL fRet = FALSE;
89 MSG msg = { };
90
91 BOOL fCreateIfNecessary = FALSE;
92
93 hr = ::CoInitialize(NULL);
94 ExitOnFailure(hr, "Failed to initialize COM on display thread.");
95 fComInitialized = TRUE;
96
97 // As long as the parent window is alive and kicking, keep this thread going (with or without a theme to display ).
98 while (::IsWindow(hwndParent))
99 {
100 if (pCurrentHandle && fCreateIfNecessary)
101 {
102 THEME* pTheme = pCurrentHandle->pTheme;
103
104 if (CW_USEDEFAULT == x && CW_USEDEFAULT == y && ::GetWindowRect(hwndParent, &rc))
105 {
106 x = rc.left;
107 y = rc.bottom + 20;
108 }
109
110 hr = ThemeCreateParentWindow(pTheme, 0, wc.lpszClassName, pTheme->sczCaption, pTheme->dwStyle, x, y, hwndParent, hInstance, pCurrentHandle, THEME_WINDOW_INITIAL_POSITION_DEFAULT, &hWnd);
111 ExitOnFailure(hr, "Failed to create display window.");
112
113 fCreateIfNecessary = FALSE;
114 }
115
116 // message pump
117 while (fRedoMsg || 0 != (fRet = ::GetMessageW(&msg, NULL, 0, 0)))
118 {
119 if (fRedoMsg)
120 {
121 fRedoMsg = FALSE;
122 }
123
124 if (-1 == fRet)
125 {
126 hr = E_UNEXPECTED;
127 ExitOnFailure(hr, "Unexpected return value from display message pump.");
128 }
129 else if (NULL == msg.hwnd) // Thread message.
130 {
131 if (WM_THMVWR_NEW_THEME == msg.message)
132 {
133 // If there is already a handle, release it.
134 if (pCurrentHandle)
135 {
136 DecrementHandleTheme(pCurrentHandle);
137 pCurrentHandle = NULL;
138 }
139
140 // If the window was created, remember its window location before we destroy
141 // it so so we can open the new window in the same place.
142 if (::IsWindow(hWnd))
143 {
144 ::GetWindowRect(hWnd, &rc);
145 x = rc.left;
146 y = rc.top;
147
148 ::DestroyWindow(hWnd);
149 }
150
151 // If the display window class was registered, unregister it so we can
152 // reuse the same window class name for the new theme.
153 if (atomWc)
154 {
155 if (!::UnregisterClassW(reinterpret_cast<LPCWSTR>(atomWc), hInstance))
156 {
157 DWORD er = ::GetLastError();
158 er = er;
159 }
160
161 atomWc = 0;
162 }
163
164 // If we were provided a new theme handle, create a new window class to
165 // support it.
166 pCurrentHandle = reinterpret_cast<HANDLE_THEME*>(msg.lParam);
167 if (pCurrentHandle)
168 {
169 ThemeInitializeWindowClass(pCurrentHandle->pTheme, &wc, DisplayWndProc, hInstance, THMVWR_WINDOW_CLASS_DISPLAY);
170
171 atomWc = ::RegisterClassW(&wc);
172 if (!atomWc)
173 {
174 ExitWithLastError(hr, "Failed to register display window class.");
175 }
176 }
177 }
178 else if (WM_THMVWR_SHOWPAGE == msg.message)
179 {
180 if (pCurrentHandle && ::IsWindow(hWnd) && pCurrentHandle->pTheme->hwndParent == hWnd)
181 {
182 DWORD dwPageId = static_cast<DWORD>(msg.lParam);
183 int nCmdShow = static_cast<int>(msg.wParam);
184
185 // First show/hide the controls not associated with a page.
186 for (DWORD i = 0; i < pCurrentHandle->pTheme->cControls; ++i)
187 {
188 THEME_CONTROL* pControl = pCurrentHandle->pTheme->rgControls + i;
189 if (!pControl->wPageId)
190 {
191 ThemeShowControl(pControl, nCmdShow);
192 }
193 }
194
195 // If a page id was provided also, show/hide those controls
196 if (dwPageId)
197 {
198 // Ignore error since we aren't using variables and it can only fail when using variables.
199 ThemeShowPage(pCurrentHandle->pTheme, dwPageId, nCmdShow);
200 }
201 }
202 else // display window isn't visible or it doesn't match the current handle.
203 {
204 // Keep the current message around to try again after we break out of this loop
205 // and create the window.
206 fRedoMsg = TRUE;
207 fCreateIfNecessary = TRUE;
208 break;
209 }
210 }
211 }
212 else if (!ThemeHandleKeyboardMessage(pCurrentHandle->pTheme, hwndParent, &msg)) // Window message.
213 {
214 ::TranslateMessage(&msg);
215 ::DispatchMessageW(&msg);
216 }
217 }
218 }
219
220LExit:
221 if (::IsWindow(hWnd))
222 {
223 ::DestroyWindow(hWnd);
224 }
225
226 if (atomWc)
227 {
228 if (!::UnregisterClassW(THMVWR_WINDOW_CLASS_DISPLAY, hInstance))
229 {
230 DWORD er = ::GetLastError();
231 er = er;
232 }
233 }
234
235 DecrementHandleTheme(pCurrentHandle);
236
237 if (fComInitialized)
238 {
239 ::CoUninitialize();
240 }
241
242 return hr;
243}
244
245static LRESULT CALLBACK DisplayWndProc(
246 __in HWND hWnd,
247 __in UINT uMsg,
248 __in WPARAM wParam,
249 __in LPARAM lParam
250 )
251{
252 static DWORD dwProgress = 0;
253 HANDLE_THEME* pHandleTheme = reinterpret_cast<HANDLE_THEME*>(::GetWindowLongPtrW(hWnd, GWLP_USERDATA));
254
255 switch (uMsg)
256 {
257 case WM_NCCREATE:
258 {
259 LPCREATESTRUCT lpcs = reinterpret_cast<LPCREATESTRUCT>(lParam);
260 pHandleTheme = reinterpret_cast<HANDLE_THEME*>(lpcs->lpCreateParams);
261 IncrementHandleTheme(pHandleTheme);
262 ::SetWindowLongPtrW(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pHandleTheme));
263 }
264 break;
265
266 case WM_TIMER:
267 if (!lParam && SUCCEEDED(ThemeSetProgressControl(reinterpret_cast<THEME_CONTROL*>(wParam), dwProgress)))
268 {
269 dwProgress += rand() % 10 + 1;
270 if (dwProgress > 100)
271 {
272 dwProgress = 0;
273 }
274
275 return 0;
276 }
277 break;
278
279 case WM_COMMAND:
280 {
281 WCHAR wzText[1024];
282 ::StringCchPrintfW(wzText, countof(wzText), L"Command %u\r\n", LOWORD(wParam));
283 OutputDebugStringW(wzText);
284 //::MessageBoxW(hWnd, wzText, L"Command fired", MB_OK);
285 }
286 break;
287
288 case WM_SYSCOMMAND:
289 {
290 WCHAR wzText[1024];
291 ::StringCchPrintfW(wzText, countof(wzText), L"SysCommand %u\r\n", LOWORD(wParam));
292 OutputDebugStringW(wzText);
293 //::MessageBoxW(hWnd, wzText, L"Command fired", MB_OK);
294 }
295 break;
296
297 case WM_NCDESTROY:
298 DecrementHandleTheme(pHandleTheme);
299 ::SetWindowLongPtrW(hWnd, GWLP_USERDATA, 0);
300 ::PostQuitMessage(0);
301 break;
302
303 case WM_THMUTIL_LOADED_CONTROL:
304 if (pHandleTheme)
305 {
306 return DisplayOnThmLoadedControl(pHandleTheme->pTheme, reinterpret_cast<THEME_LOADEDCONTROL_ARGS*>(wParam), reinterpret_cast<THEME_LOADEDCONTROL_RESULTS*>(lParam));
307 }
308 }
309
310 return ThemeDefWindowProc(pHandleTheme ? pHandleTheme->pTheme : NULL, hWnd, uMsg, wParam, lParam);
311}
312
313static BOOL DisplayOnThmLoadedControl(
314 __in THEME* pTheme,
315 __in const THEME_LOADEDCONTROL_ARGS* args,
316 __in THEME_LOADEDCONTROL_RESULTS* results
317 )
318{
319 HRESULT hr = S_OK;
320 const THEME_CONTROL* pControl = args->pThemeControl;
321
322 // Pre-populate some control types with data.
323 if (THEME_CONTROL_TYPE_RICHEDIT == pControl->type)
324 {
325 hr = WnduLoadRichEditFromResource(pControl->hWnd, MAKEINTRESOURCEA(THMVWR_RES_RICHEDIT_FILE), ::GetModuleHandleW(NULL));
326 ExitOnFailure(hr, "Failed to load richedit text.");
327 }
328 else if (THEME_CONTROL_TYPE_PROGRESSBAR == pControl->type)
329 {
330 UINT_PTR timerId = reinterpret_cast<UINT_PTR>(pControl);
331 UINT_PTR id = ::SetTimer(pTheme->hwndParent, timerId, 500, NULL);
332 id = id; // prevents warning in "ship" build.
333 Assert(id == timerId);
334 }
335
336LExit:
337 results->hr = hr;
338 return TRUE;
339}
diff --git a/src/tools/thmviewer/load.cpp b/src/tools/thmviewer/load.cpp
new file mode 100644
index 00000000..0267402a
--- /dev/null
+++ b/src/tools/thmviewer/load.cpp
@@ -0,0 +1,221 @@
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
5struct LOAD_THREAD_CONTEXT
6{
7 HWND hWnd;
8 LPCWSTR wzThemePath;
9 LPCWSTR wzWxlPath;
10
11 HANDLE hInit;
12};
13
14static DWORD WINAPI LoadThreadProc(
15 __in LPVOID pvContext
16 );
17
18
19extern "C" HRESULT LoadStart(
20 __in_z LPCWSTR wzThemePath,
21 __in_z_opt LPCWSTR wzWxlPath,
22 __in HWND hWnd,
23 __out HANDLE* phThread
24 )
25{
26 HRESULT hr = S_OK;
27 HANDLE rgHandles[2] = { };
28 LOAD_THREAD_CONTEXT context = { };
29
30 rgHandles[0] = ::CreateEventW(NULL, TRUE, FALSE, NULL);
31 ExitOnNullWithLastError(rgHandles[0], hr, "Failed to create load init event.");
32
33 context.hWnd = hWnd;
34 context.wzThemePath = wzThemePath;
35 context.wzWxlPath = wzWxlPath;
36 context.hInit = rgHandles[0];
37
38 rgHandles[1] = ::CreateThread(NULL, 0, LoadThreadProc, &context, 0, NULL);
39 ExitOnNullWithLastError(rgHandles[1], hr, "Failed to create load thread.");
40
41 ::WaitForMultipleObjects(countof(rgHandles), rgHandles, FALSE, INFINITE);
42
43 *phThread = rgHandles[1];
44 rgHandles[1] = NULL;
45
46LExit:
47 ReleaseHandle(rgHandles[1]);
48 ReleaseHandle(rgHandles[0]);
49 return hr;
50}
51
52
53static DWORD WINAPI LoadThreadProc(
54 __in LPVOID pvContext
55 )
56{
57 HRESULT hr = S_OK;
58 WIX_LOCALIZATION* pWixLoc = NULL;
59 LPWSTR sczThemePath = NULL;
60 LPWSTR sczWxlPath = NULL;
61 BOOL fComInitialized = FALSE;
62 HANDLE hDirectory = INVALID_HANDLE_VALUE;
63 LPWSTR sczDirectory = NULL;
64 LPWSTR wzFileName = NULL;
65
66 THEME* pTheme = NULL;
67 HANDLE_THEME* pHandle = NULL;
68
69 LOAD_THREAD_CONTEXT* pContext = static_cast<LOAD_THREAD_CONTEXT*>(pvContext);
70 HWND hWnd = pContext->hWnd;
71
72 hr = StrAllocString(&sczThemePath, pContext->wzThemePath, 0);
73 ExitOnFailure(hr, "Failed to copy path to initial theme file.");
74
75 if (pContext->wzWxlPath)
76 {
77 hr = StrAllocString(&sczWxlPath, pContext->wzWxlPath, 0);
78 ExitOnFailure(hr, "Failed to copy .wxl path to initial file.");
79 }
80
81 // We can signal the initialization event as soon as we have copied the context
82 // values into local variables.
83 ::SetEvent(pContext->hInit);
84
85 hr = ::CoInitialize(NULL);
86 ExitOnFailure(hr, "Failed to initialize COM on load thread.");
87 fComInitialized = TRUE;
88
89 // Open a handle to the directory so we can put a notification on it.
90 hr = PathGetDirectory(sczThemePath, &sczDirectory);
91 ExitOnFailure(hr, "Failed to get path directory.");
92
93 wzFileName = PathFile(sczThemePath);
94
95 hDirectory = ::CreateFileW(sczDirectory, FILE_LIST_DIRECTORY, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
96 if (INVALID_HANDLE_VALUE == hDirectory)
97 {
98 ExitWithLastError(hr, "Failed to open directory: %ls", sczDirectory);
99 }
100
101 BOOL fUpdated = FALSE;
102 do
103 {
104 // Get the last modified time on the file we're loading for verification that the
105 // file actually gets changed down below.
106 FILETIME ftModified = { };
107 FileGetTime(sczThemePath, NULL, NULL, &ftModified);
108
109 ::SendMessageW(hWnd, WM_THMVWR_THEME_LOAD_BEGIN, 0, 0);
110
111 // Try to load the theme file.
112 hr = ThemeLoadFromFile(sczThemePath, &pTheme);
113 if (FAILED(hr))
114 {
115 ::SendMessageW(hWnd, WM_THMVWR_THEME_LOAD_ERROR, 0, hr);
116 }
117 else
118 {
119 if (sczWxlPath)
120 {
121 hr = LocLoadFromFile(sczWxlPath, &pWixLoc);
122 ExitOnFailure(hr, "Failed to load loc file from path: %ls", sczWxlPath);
123
124 hr = ThemeLocalize(pTheme, pWixLoc);
125 ExitOnFailure(hr, "Failed to localize theme: %ls", sczWxlPath);
126 }
127
128 hr = AllocHandleTheme(pTheme, &pHandle);
129 ExitOnFailure(hr, "Failed to allocate handle to theme");
130
131 ::SendMessageW(hWnd, WM_THMVWR_NEW_THEME, 0, reinterpret_cast<LPARAM>(pHandle));
132 pHandle = NULL;
133 }
134
135 fUpdated = FALSE;
136 do
137 {
138 DWORD rgbNotifications[1024];
139 DWORD cbRead = 0;
140 if (!::ReadDirectoryChangesW(hDirectory, rgbNotifications, sizeof(rgbNotifications), FALSE, FILE_NOTIFY_CHANGE_LAST_WRITE, &cbRead, NULL, NULL))
141 {
142 ExitWithLastError(hr, "Failed while watching directory: %ls", sczDirectory);
143 }
144
145 // Wait for half a second to let all the file handles get closed to minimize access
146 // denied errors.
147 ::Sleep(500);
148
149 FILE_NOTIFY_INFORMATION* pNotification = reinterpret_cast<FILE_NOTIFY_INFORMATION*>(rgbNotifications);
150 while (pNotification)
151 {
152 // If our file was updated, check to see if the modified time really changed. The notifications
153 // are often trigger happy thinking the file changed two or three times in a row. Maybe it's AV
154 // software creating the problems but actually checking the modified date works well.
155 if (CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, NORM_IGNORECASE, pNotification->FileName, pNotification->FileNameLength / sizeof(WCHAR), wzFileName, -1))
156 {
157 FILETIME ft = { };
158 FileGetTime(sczThemePath, NULL, NULL, &ft);
159
160 fUpdated = (ftModified.dwHighDateTime < ft.dwHighDateTime) || (ftModified.dwHighDateTime == ft.dwHighDateTime && ftModified.dwLowDateTime < ft.dwLowDateTime);
161 break;
162 }
163
164 pNotification = pNotification->NextEntryOffset ? reinterpret_cast<FILE_NOTIFY_INFORMATION*>(reinterpret_cast<BYTE*>(pNotification) + pNotification->NextEntryOffset) : NULL;
165 }
166 } while (!fUpdated);
167 } while(fUpdated);
168
169LExit:
170 if (fComInitialized)
171 {
172 ::CoUninitialize();
173 }
174
175 LocFree(pWixLoc);
176 ReleaseFileHandle(hDirectory);
177 ReleaseStr(sczDirectory);
178 ReleaseStr(sczThemePath);
179 ReleaseStr(sczWxlPath);
180 return hr;
181}
182
183extern "C" HRESULT AllocHandleTheme(
184 __in THEME* pTheme,
185 __out HANDLE_THEME** ppHandle
186 )
187{
188 HRESULT hr = S_OK;
189 HANDLE_THEME* pHandle = NULL;
190
191 pHandle = static_cast<HANDLE_THEME*>(MemAlloc(sizeof(HANDLE_THEME), TRUE));
192 ExitOnNull(pHandle, hr, E_OUTOFMEMORY, "Failed to allocate theme handle.");
193
194 pHandle->cReferences = 1;
195 pHandle->pTheme = pTheme;
196
197 *ppHandle = pHandle;
198 pHandle = NULL;
199
200LExit:
201 ReleaseMem(pHandle);
202 return hr;
203}
204
205extern "C" void IncrementHandleTheme(
206 __in HANDLE_THEME* pHandle
207 )
208{
209 ::InterlockedIncrement(reinterpret_cast<LONG*>(&pHandle->cReferences));
210}
211
212extern "C" void DecrementHandleTheme(
213 __in HANDLE_THEME* pHandle
214 )
215{
216 if (pHandle && 0 == ::InterlockedDecrement(reinterpret_cast<LONG*>(&pHandle->cReferences)))
217 {
218 ThemeFree(pHandle->pTheme);
219 MemFree(pHandle);
220 }
221}
diff --git a/src/tools/thmviewer/precomp.cpp b/src/tools/thmviewer/precomp.cpp
new file mode 100644
index 00000000..37664a1c
--- /dev/null
+++ b/src/tools/thmviewer/precomp.cpp
@@ -0,0 +1,3 @@
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"
diff --git a/src/tools/thmviewer/precomp.h b/src/tools/thmviewer/precomp.h
new file mode 100644
index 00000000..762a0623
--- /dev/null
+++ b/src/tools/thmviewer/precomp.h
@@ -0,0 +1,72 @@
1#pragma once
2// 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.
3
4
5#include <windows.h>
6#include <msiquery.h>
7#include <objbase.h>
8#include <shlobj.h>
9#include <shlwapi.h>
10#include <stdlib.h>
11#include <strsafe.h>
12
13#pragma warning(push)
14#pragma warning(disable:4458)
15#include <gdiplus.h>
16#pragma warning(pop)
17
18#include "dutil.h"
19#include "apputil.h"
20#include "memutil.h"
21#include "dictutil.h"
22#include "dirutil.h"
23#include "fileutil.h"
24#include "locutil.h"
25#include "logutil.h"
26#include "pathutil.h"
27#include "resrutil.h"
28#include "shelutil.h"
29#include "strutil.h"
30#include "thmutil.h"
31#include "wndutil.h"
32
33#include "resource.h"
34
35struct HANDLE_THEME
36{
37 DWORD cReferences;
38 THEME* pTheme;
39};
40
41enum WM_THMVWR
42{
43 WM_THMVWR_SHOWPAGE = WM_APP,
44 WM_THMVWR_PARSE_FILE,
45 WM_THMVWR_NEW_THEME,
46 WM_THMVWR_THEME_LOAD_ERROR,
47 WM_THMVWR_THEME_LOAD_BEGIN,
48};
49
50extern "C" HRESULT DisplayStart(
51 __in HINSTANCE hInstance,
52 __in HWND hWnd,
53 __out HANDLE *phThread,
54 __out DWORD* pdwThreadId
55 );
56extern "C" HRESULT LoadStart(
57 __in_z LPCWSTR wzThemePath,
58 __in_z LPCWSTR wzWxlPath,
59 __in HWND hWnd,
60 __out HANDLE* phThread
61 );
62
63extern "C" HRESULT AllocHandleTheme(
64 __in THEME* pTheme,
65 __out HANDLE_THEME** ppHandle
66 );
67extern "C" void IncrementHandleTheme(
68 __in HANDLE_THEME* pHandle
69 );
70extern "C" void DecrementHandleTheme(
71 __in HANDLE_THEME* pHandle
72 );
diff --git a/src/tools/thmviewer/resource.h b/src/tools/thmviewer/resource.h
new file mode 100644
index 00000000..4acc32cc
--- /dev/null
+++ b/src/tools/thmviewer/resource.h
@@ -0,0 +1,16 @@
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#define IDC_STATIC -1
4#define THMVWR_RES_THEME_FILE 1
5#define THMVWR_RES_RICHEDIT_FILE 2
6
7// Next default values for new objects
8//
9#ifdef APSTUDIO_INVOKED
10#ifndef APSTUDIO_READONLY_SYMBOLS
11#define _APS_NEXT_RESOURCE_VALUE 102
12#define _APS_NEXT_COMMAND_VALUE 40001
13#define _APS_NEXT_CONTROL_VALUE 1003
14#define _APS_NEXT_SYMED_VALUE 101
15#endif
16#endif
diff --git a/src/tools/thmviewer/thmviewer.cpp b/src/tools/thmviewer/thmviewer.cpp
new file mode 100644
index 00000000..38f3c4dc
--- /dev/null
+++ b/src/tools/thmviewer/thmviewer.cpp
@@ -0,0 +1,564 @@
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
5static const LPCWSTR THMVWR_WINDOW_CLASS_MAIN = L"ThmViewerMain";
6
7static THEME* vpTheme = NULL;
8static DWORD vdwDisplayThreadId = 0;
9static LPWSTR vsczThemeLoadErrors = NULL;
10
11enum THMVWR_CONTROL
12{
13 // Non-paged controls
14 THMVWR_CONTROL_TREE = THEME_FIRST_ASSIGN_CONTROL_ID,
15};
16
17// Internal functions
18
19static HRESULT ProcessCommandLine(
20 __in_z_opt LPCWSTR wzCommandLine,
21 __out_z LPWSTR* psczThemeFile,
22 __out_z LPWSTR* psczWxlFile
23 );
24static HRESULT CreateTheme(
25 __in HINSTANCE hInstance,
26 __out THEME** ppTheme
27 );
28static HRESULT CreateMainWindowClass(
29 __in HINSTANCE hInstance,
30 __in THEME* pTheme,
31 __out ATOM* pAtom
32 );
33static LRESULT CALLBACK MainWndProc(
34 __in HWND hWnd,
35 __in UINT uMsg,
36 __in WPARAM wParam,
37 __in LPARAM lParam
38 );
39static void OnThemeLoadBegin(
40 __in_z_opt LPWSTR sczThemeLoadErrors
41 );
42static void OnThemeLoadError(
43 __in THEME* pTheme,
44 __in HRESULT hrFailure
45 );
46static void OnNewTheme(
47 __in THEME* pTheme,
48 __in HWND hWnd,
49 __in HANDLE_THEME* pHandle
50 );
51static BOOL OnThemeLoadingControl(
52 __in const THEME_LOADINGCONTROL_ARGS* pArgs,
53 __in THEME_LOADINGCONTROL_RESULTS* pResults
54 );
55static BOOL OnThemeControlWmNotify(
56 __in const THEME_CONTROLWMNOTIFY_ARGS* pArgs,
57 __in THEME_CONTROLWMNOTIFY_RESULTS* pResults
58 );
59static 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
70int WINAPI wWinMain(
71 __in HINSTANCE hInstance,
72 __in_opt HINSTANCE /* hPrevInstance */,
73 __in_z LPWSTR lpCmdLine,
74 __in int /*nCmdShow*/
75 )
76{
77 ::HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, NULL, 0);
78
79 HRESULT hr = S_OK;
80 BOOL fComInitialized = FALSE;
81 LPWSTR sczThemeFile = NULL;
82 LPWSTR sczWxlFile = NULL;
83 ATOM atom = 0;
84 HWND hWnd = NULL;
85
86 HANDLE hDisplayThread = NULL;
87 HANDLE hLoadThread = NULL;
88
89 BOOL fRet = FALSE;
90 MSG msg = { };
91
92 hr = ::CoInitialize(NULL);
93 ExitOnFailure(hr, "Failed to initialize COM.");
94 fComInitialized = TRUE;
95
96 DutilInitialize(&ThmviewerTraceError);
97
98 hr = ProcessCommandLine(lpCmdLine, &sczThemeFile, &sczWxlFile);
99 ExitOnFailure(hr, "Failed to process command line.");
100
101 hr = CreateTheme(hInstance, &vpTheme);
102 ExitOnFailure(hr, "Failed to create theme.");
103
104 hr = CreateMainWindowClass(hInstance, vpTheme, &atom);
105 ExitOnFailure(hr, "Failed to create main window.");
106
107 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);
108 ExitOnFailure(hr, "Failed to create window.");
109
110 if (!sczThemeFile)
111 {
112 // Prompt for a path to the theme file.
113 OPENFILENAMEW ofn = { };
114 WCHAR wzFile[MAX_PATH] = { };
115
116 ofn.lStructSize = sizeof(ofn);
117 ofn.hwndOwner = hWnd;
118 ofn.lpstrFile = wzFile;
119 ofn.nMaxFile = countof(wzFile);
120 ofn.lpstrFilter = L"Theme Files (*.thm)\0*.thm\0XML Files (*.xml)\0*.xml\0All Files (*.*)\0*.*\0";
121 ofn.nFilterIndex = 1;
122 ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
123 ofn.lpstrTitle = vpTheme->sczCaption;
124
125 if (::GetOpenFileNameW(&ofn))
126 {
127 hr = StrAllocString(&sczThemeFile, wzFile, 0);
128 ExitOnFailure(hr, "Failed to copy opened file to theme file.");
129 }
130 else
131 {
132 ::MessageBoxW(hWnd, L"Must specify a path to theme file.", vpTheme->sczCaption, MB_OK | MB_ICONERROR);
133 ExitFunction1(hr = E_INVALIDARG);
134 }
135 }
136
137 hr = DisplayStart(hInstance, hWnd, &hDisplayThread, &vdwDisplayThreadId);
138 ExitOnFailure(hr, "Failed to start display.");
139
140 hr = LoadStart(sczThemeFile, sczWxlFile, hWnd, &hLoadThread);
141 ExitOnFailure(hr, "Failed to start load.");
142
143 // message pump
144 while (0 != (fRet = ::GetMessageW(&msg, NULL, 0, 0)))
145 {
146 if (-1 == fRet)
147 {
148 hr = E_UNEXPECTED;
149 ExitOnFailure(hr, "Unexpected return value from message pump.");
150 }
151 else if (!ThemeHandleKeyboardMessage(vpTheme, msg.hwnd, &msg))
152 {
153 ::TranslateMessage(&msg);
154 ::DispatchMessageW(&msg);
155 }
156 }
157
158LExit:
159 if (::IsWindow(hWnd))
160 {
161 ::DestroyWindow(hWnd);
162 }
163
164 if (hDisplayThread)
165 {
166 ::PostThreadMessageW(vdwDisplayThreadId, WM_QUIT, 0, 0);
167 ::WaitForSingleObject(hDisplayThread, 10000);
168 ::CloseHandle(hDisplayThread);
169 }
170
171 // TODO: come up with a good way to kill the load thread, probably need to switch
172 // the ReadDirectoryW() to overlapped mode.
173 ReleaseHandle(hLoadThread);
174
175 if (atom && !::UnregisterClassW(reinterpret_cast<LPCWSTR>(atom), hInstance))
176 {
177 DWORD er = ::GetLastError();
178 er = er;
179 }
180
181 ThemeFree(vpTheme);
182 ThemeUninitialize();
183 DutilUninitialize();
184
185 // uninitialize COM
186 if (fComInitialized)
187 {
188 ::CoUninitialize();
189 }
190
191 ReleaseNullStr(vsczThemeLoadErrors);
192 ReleaseStr(sczThemeFile);
193 ReleaseStr(sczWxlFile);
194 return hr;
195}
196
197static void CALLBACK ThmviewerTraceError(
198 __in_z LPCSTR /*szFile*/,
199 __in int /*iLine*/,
200 __in REPORT_LEVEL /*rl*/,
201 __in UINT source,
202 __in HRESULT hrError,
203 __in_z __format_string LPCSTR szFormat,
204 __in va_list args
205 )
206{
207 HRESULT hr = S_OK;
208 LPSTR sczFormattedAnsi = NULL;
209 LPWSTR sczMessage = NULL;
210
211 if (DUTIL_SOURCE_THMUTIL != source)
212 {
213 ExitFunction();
214 }
215
216 hr = StrAnsiAllocFormattedArgs(&sczFormattedAnsi, szFormat, args);
217 ExitOnFailure(hr, "Failed to format error log string.");
218
219 hr = StrAllocFormatted(&sczMessage, L"Error 0x%08x: %S\r\n", hrError, sczFormattedAnsi);
220 ExitOnFailure(hr, "Failed to prepend error number to error log string.");
221
222 hr = StrAllocConcat(&vsczThemeLoadErrors, sczMessage, 0);
223 ExitOnFailure(hr, "Failed to append theme load error.");
224
225LExit:
226 ReleaseStr(sczFormattedAnsi);
227 ReleaseStr(sczMessage);
228}
229
230
231//
232// ProcessCommandLine - process the provided command line arguments.
233//
234static HRESULT ProcessCommandLine(
235 __in_z_opt LPCWSTR wzCommandLine,
236 __out_z LPWSTR* psczThemeFile,
237 __out_z LPWSTR* psczWxlFile
238 )
239{
240 HRESULT hr = S_OK;
241 int argc = 0;
242 LPWSTR* argv = NULL;
243
244 if (wzCommandLine && *wzCommandLine)
245 {
246 hr = AppParseCommandLine(wzCommandLine, &argc, &argv);
247 ExitOnFailure(hr, "Failed to parse command line.");
248
249 for (int i = 0; i < argc; ++i)
250 {
251 if (argv[i][0] == L'-' || argv[i][0] == L'/')
252 {
253 if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], -1, L"lang", -1))
254 {
255 if (i + 1 >= argc)
256 {
257 ExitOnRootFailure(hr = E_INVALIDARG, "Must specify a language.");
258 }
259
260 ++i;
261 }
262 }
263 else
264 {
265 LPCWSTR wzExtension = PathExtension(argv[i]);
266 if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, wzExtension, -1, L".wxl", -1))
267 {
268 hr = StrAllocString(psczWxlFile, argv[i], 0);
269 }
270 else
271 {
272 hr = StrAllocString(psczThemeFile, argv[i], 0);
273 }
274 ExitOnFailure(hr, "Failed to copy path to file.");
275 }
276 }
277 }
278
279LExit:
280 if (argv)
281 {
282 AppFreeCommandLineArgs(argv);
283 }
284
285 return hr;
286}
287
288static HRESULT CreateTheme(
289 __in HINSTANCE hInstance,
290 __out THEME** ppTheme
291 )
292{
293 HRESULT hr = S_OK;
294
295 hr = ThemeInitialize(hInstance);
296 ExitOnFailure(hr, "Failed to initialize theme manager.");
297
298 hr = ThemeLoadFromResource(hInstance, MAKEINTRESOURCEA(THMVWR_RES_THEME_FILE), ppTheme);
299 ExitOnFailure(hr, "Failed to load theme from thmviewer.thm.");
300
301LExit:
302 return hr;
303}
304
305static HRESULT CreateMainWindowClass(
306 __in HINSTANCE hInstance,
307 __in THEME* pTheme,
308 __out ATOM* pAtom
309 )
310{
311 HRESULT hr = S_OK;
312 ATOM atom = 0;
313 WNDCLASSW wc = { };
314
315 ThemeInitializeWindowClass(pTheme, &wc, MainWndProc, hInstance, THMVWR_WINDOW_CLASS_MAIN);
316
317 atom = ::RegisterClassW(&wc);
318 if (!atom)
319 {
320 ExitWithLastError(hr, "Failed to register main windowclass .");
321 }
322
323 *pAtom = atom;
324
325LExit:
326 return hr;
327}
328
329static LRESULT CALLBACK MainWndProc(
330 __in HWND hWnd,
331 __in UINT uMsg,
332 __in WPARAM wParam,
333 __in LPARAM lParam
334 )
335{
336 HANDLE_THEME* pHandleTheme = reinterpret_cast<HANDLE_THEME*>(::GetWindowLongPtrW(hWnd, GWLP_USERDATA));
337
338 switch (uMsg)
339 {
340 case WM_NCCREATE:
341 {
342 //LPCREATESTRUCT lpcs = reinterpret_cast<LPCREATESTRUCT>(lParam);
343 //pBA = reinterpret_cast<CWixStandardBootstrapperApplication*>(lpcs->lpCreateParams);
344 //::SetWindowLongPtrW(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pBA));
345 }
346 break;
347
348 case WM_NCDESTROY:
349 DecrementHandleTheme(pHandleTheme);
350 ::SetWindowLongPtrW(hWnd, GWLP_USERDATA, 0);
351 ::PostQuitMessage(0);
352 break;
353
354 case WM_THMVWR_THEME_LOAD_BEGIN:
355 OnThemeLoadBegin(vsczThemeLoadErrors);
356 return 0;
357
358 case WM_THMVWR_THEME_LOAD_ERROR:
359 OnThemeLoadError(vpTheme, lParam);
360 return 0;
361
362 case WM_THMVWR_NEW_THEME:
363 OnNewTheme(vpTheme, hWnd, reinterpret_cast<HANDLE_THEME*>(lParam));
364 return 0;
365
366 case WM_THMUTIL_LOADING_CONTROL:
367 return OnThemeLoadingControl(reinterpret_cast<THEME_LOADINGCONTROL_ARGS*>(wParam), reinterpret_cast<THEME_LOADINGCONTROL_RESULTS*>(lParam));
368
369 case WM_THMUTIL_CONTROL_WM_NOTIFY:
370 return OnThemeControlWmNotify(reinterpret_cast<THEME_CONTROLWMNOTIFY_ARGS*>(wParam), reinterpret_cast<THEME_CONTROLWMNOTIFY_RESULTS*>(lParam));
371 }
372
373 return ThemeDefWindowProc(vpTheme, hWnd, uMsg, wParam, lParam);
374}
375
376static void OnThemeLoadBegin(
377 __in_z_opt LPWSTR sczThemeLoadErrors
378 )
379{
380 ReleaseNullStr(sczThemeLoadErrors);
381}
382
383static void OnThemeLoadError(
384 __in THEME* pTheme,
385 __in HRESULT hrFailure
386 )
387{
388 HRESULT hr = S_OK;
389 LPWSTR sczMessage = NULL;
390 LPWSTR* psczErrors = NULL;
391 UINT cErrors = 0;
392 TVINSERTSTRUCTW tvi = { };
393 const THEME_CONTROL* pTreeControl = NULL;
394
395 if (!ThemeControlExistsById(pTheme, THMVWR_CONTROL_TREE, &pTreeControl))
396 {
397 ExitWithRootFailure(hr, E_INVALIDSTATE, "THMVWR_CONTROL_TREE control doesn't exist.");
398 }
399
400 // Add the application node.
401 tvi.hParent = NULL;
402 tvi.hInsertAfter = TVI_ROOT;
403 tvi.item.mask = TVIF_TEXT | TVIF_PARAM;
404 tvi.item.lParam = 0;
405 tvi.item.pszText = L"Failed to load theme.";
406 tvi.hParent = reinterpret_cast<HTREEITEM>(::SendMessage(pTreeControl->hWnd, TVM_INSERTITEMW, 0, reinterpret_cast<LPARAM>(&tvi)));
407
408 if (!vsczThemeLoadErrors)
409 {
410 hr = StrAllocFormatted(&sczMessage, L"Error 0x%08x.", hrFailure);
411 ExitOnFailure(hr, "Failed to format error message.");
412
413 tvi.item.pszText = sczMessage;
414 ::SendMessage(pTreeControl->hWnd, TVM_INSERTITEMW, 0, reinterpret_cast<LPARAM>(&tvi));
415
416 hr = StrAllocFromError(&sczMessage, hrFailure, NULL);
417 ExitOnFailure(hr, "Failed to format error message text.");
418
419 tvi.item.pszText = sczMessage;
420 ::SendMessage(pTreeControl->hWnd, TVM_INSERTITEMW, 0, reinterpret_cast<LPARAM>(&tvi));
421 }
422 else
423 {
424 hr = StrSplitAllocArray(&psczErrors, &cErrors, vsczThemeLoadErrors, L"\r\n");
425 ExitOnFailure(hr, "Failed to split theme load errors.");
426
427 for (DWORD i = 0; i < cErrors; ++i)
428 {
429 tvi.item.pszText = psczErrors[i];
430 ::SendMessage(pTreeControl->hWnd, TVM_INSERTITEMW, 0, reinterpret_cast<LPARAM>(&tvi));
431 }
432 }
433
434 ::SendMessage(pTreeControl->hWnd, TVM_EXPAND, TVE_EXPAND, reinterpret_cast<LPARAM>(tvi.hParent));
435
436LExit:
437 ReleaseStr(sczMessage);
438 ReleaseMem(psczErrors);
439}
440
441
442static void OnNewTheme(
443 __in THEME* pTheme,
444 __in HWND hWnd,
445 __in HANDLE_THEME* pHandle
446 )
447{
448 const THEME_CONTROL* pTreeControl = NULL;
449 HANDLE_THEME* pOldHandle = reinterpret_cast<HANDLE_THEME*>(::GetWindowLongPtrW(hWnd, GWLP_USERDATA));
450 THEME* pNewTheme = pHandle->pTheme;
451
452 WCHAR wzSelectedPage[MAX_PATH] = { };
453 HTREEITEM htiSelected = NULL;
454 TVINSERTSTRUCTW tvi = { };
455 TVITEMW item = { };
456
457 if (pOldHandle)
458 {
459 DecrementHandleTheme(pOldHandle);
460 pOldHandle = NULL;
461 }
462
463 // Pass the new theme handle to the display thread so it can get the display window prepared
464 // to show the new theme.
465 IncrementHandleTheme(pHandle);
466 ::PostThreadMessageW(vdwDisplayThreadId, WM_THMVWR_NEW_THEME, 0, reinterpret_cast<LPARAM>(pHandle));
467
468 ::SetWindowLongPtrW(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pHandle));
469
470 if (!ThemeControlExistsById(pTheme, THMVWR_CONTROL_TREE, &pTreeControl))
471 {
472 TraceError(E_INVALIDSTATE, "Tree control doesn't exist.");
473 return;
474 }
475
476 // Remember the currently selected item by name so we can try to automatically select it later.
477 // Otherwise, the user would see their window destroyed after every save of their theme file and
478 // have to click to get the window back.
479 item.mask = TVIF_TEXT;
480 item.pszText = wzSelectedPage;
481 item.cchTextMax = countof(wzSelectedPage);
482 item.hItem = reinterpret_cast<HTREEITEM>(::SendMessage(pTreeControl->hWnd, TVM_GETNEXTITEM, TVGN_CARET, NULL));
483 ::SendMessage(pTreeControl->hWnd, TVM_GETITEM, 0, reinterpret_cast<LPARAM>(&item));
484
485 // Remove the previous items in the tree.
486 ::SendMessage(pTreeControl->hWnd, TVM_DELETEITEM, 0, reinterpret_cast<LPARAM>(TVI_ROOT));
487
488 // Add the application node.
489 tvi.hParent = NULL;
490 tvi.hInsertAfter = TVI_ROOT;
491 tvi.item.mask = TVIF_TEXT | TVIF_PARAM;
492 tvi.item.lParam = 0;
493 tvi.item.pszText = pHandle && pHandle->pTheme && pHandle->pTheme->sczCaption ? pHandle->pTheme->sczCaption : L"Window";
494
495 // Add the pages.
496 tvi.hParent = reinterpret_cast<HTREEITEM>(::SendMessage(pTreeControl->hWnd, TVM_INSERTITEMW, 0, reinterpret_cast<LPARAM>(&tvi)));
497 tvi.hInsertAfter = TVI_SORT;
498 for (DWORD i = 0; i < pNewTheme->cPages; ++i)
499 {
500 THEME_PAGE* pPage = pNewTheme->rgPages + i;
501 if (pPage->sczName && *pPage->sczName)
502 {
503 tvi.item.pszText = pPage->sczName;
504 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.
505
506 HTREEITEM hti = reinterpret_cast<HTREEITEM>(::SendMessage(pTreeControl->hWnd, TVM_INSERTITEMW, 0, reinterpret_cast<LPARAM>(&tvi)));
507 if (*wzSelectedPage && CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, pPage->sczName, -1, wzSelectedPage, -1))
508 {
509 htiSelected = hti;
510 }
511 }
512 }
513
514 if (*wzSelectedPage && CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, L"Application", -1, wzSelectedPage, -1))
515 {
516 htiSelected = tvi.hParent;
517 }
518
519 ::SendMessage(pTreeControl->hWnd, TVM_EXPAND, TVE_EXPAND, reinterpret_cast<LPARAM>(tvi.hParent));
520 if (htiSelected)
521 {
522 ::SendMessage(pTreeControl->hWnd, TVM_SELECTITEM, TVGN_CARET, reinterpret_cast<LPARAM>(htiSelected));
523 }
524}
525
526static BOOL OnThemeLoadingControl(
527 __in const THEME_LOADINGCONTROL_ARGS* pArgs,
528 __in THEME_LOADINGCONTROL_RESULTS* pResults
529 )
530{
531 if (CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, pArgs->pThemeControl->sczName, -1, L"Tree", -1))
532 {
533 pResults->wId = THMVWR_CONTROL_TREE;
534 }
535
536 pResults->hr = S_OK;
537 return TRUE;
538}
539
540static BOOL OnThemeControlWmNotify(
541 __in const THEME_CONTROLWMNOTIFY_ARGS* pArgs,
542 __in THEME_CONTROLWMNOTIFY_RESULTS* /*pResults*/
543 )
544{
545 BOOL fProcessed = FALSE;
546
547 switch (pArgs->lParam->code)
548 {
549 case TVN_SELCHANGEDW:
550 switch (pArgs->pThemeControl->wId)
551 {
552 case THMVWR_CONTROL_TREE:
553 NMTREEVIEWW* ptv = reinterpret_cast<NMTREEVIEWW*>(pArgs->lParam);
554 ::PostThreadMessageW(vdwDisplayThreadId, WM_THMVWR_SHOWPAGE, SW_HIDE, ptv->itemOld.lParam);
555 ::PostThreadMessageW(vdwDisplayThreadId, WM_THMVWR_SHOWPAGE, SW_SHOW, ptv->itemNew.lParam);
556
557 fProcessed = TRUE;
558 break;
559 }
560 break;
561 }
562
563 return fProcessed;
564}
diff --git a/src/tools/thmviewer/thmviewer.manifest b/src/tools/thmviewer/thmviewer.manifest
new file mode 100644
index 00000000..4663b61c
--- /dev/null
+++ b/src/tools/thmviewer/thmviewer.manifest
@@ -0,0 +1,19 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- 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. -->
3
4
5<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
6 <assemblyIdentity name="thmviewer.exe" version="1.0.0.0" processorArchitecture="x86" type="win32"/>
7 <description>WiX Toolset Theme Viewer</description>
8 <dependency><dependentAssembly><assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="X86" publicKeyToken="6595b64144ccf1df" language="*" /></dependentAssembly></dependency>
9 <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"><application><supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/><supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/></application></compatibility>
10 <application xmlns="urn:schemas-microsoft-com:asm.v3">
11 <windowsSettings xmlns="urn:schemas-microsoft-com:asm.v3">
12 <!-- pre-Win10 1607 -->
13 <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
14 <!-- Win10 picks the first one it recognizes -->
15 <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor, System</dpiAwareness>
16 </windowsSettings>
17 </application>
18 <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3"><security><requestedPrivileges><requestedExecutionLevel level="asInvoker" uiAccess="false"/></requestedPrivileges></security></trustInfo>
19</assembly>
diff --git a/src/tools/thmviewer/thmviewer.rc b/src/tools/thmviewer/thmviewer.rc
new file mode 100644
index 00000000..dc6d7242
--- /dev/null
+++ b/src/tools/thmviewer/thmviewer.rc
@@ -0,0 +1,12 @@
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 <winver.h>
4#include <windows.h>
5#include "resource.h"
6
7//#define MANIFEST_RESOURCE_ID 1
8//MANIFEST_RESOURCE_ID RT_MANIFEST "thmviewer.manifest"
9
10LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
11THMVWR_RES_THEME_FILE RCDATA "Resources\\thm.xml"
12THMVWR_RES_RICHEDIT_FILE RCDATA "Resources\\LoremIpsum.rtf"
diff --git a/src/tools/thmviewer/thmviewer.v3.ncrunchproject b/src/tools/thmviewer/thmviewer.v3.ncrunchproject
new file mode 100644
index 00000000..3cffd6ce
--- /dev/null
+++ b/src/tools/thmviewer/thmviewer.v3.ncrunchproject
@@ -0,0 +1,7 @@
1<ProjectConfiguration>
2 <Settings>
3 <AdditionalFilesToIncludeForProject>
4 <Value>thmviewer.manifest</Value>
5 </AdditionalFilesToIncludeForProject>
6 </Settings>
7</ProjectConfiguration> \ No newline at end of file
diff --git a/src/tools/thmviewer/thmviewer.vcxproj b/src/tools/thmviewer/thmviewer.vcxproj
new file mode 100644
index 00000000..3fd87878
--- /dev/null
+++ b/src/tools/thmviewer/thmviewer.vcxproj
@@ -0,0 +1,69 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- 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. -->
3
4<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
5 <ItemGroup Label="ProjectConfigurations">
6 <ProjectConfiguration Include="Debug|Win32">
7 <Configuration>Debug</Configuration>
8 <Platform>Win32</Platform>
9 </ProjectConfiguration>
10 <ProjectConfiguration Include="Release|Win32">
11 <Configuration>Release</Configuration>
12 <Platform>Win32</Platform>
13 </ProjectConfiguration>
14 </ItemGroup>
15
16 <PropertyGroup Label="Globals">
17 <ProjectGuid>{95228C13-97F5-484A-B4A2-ECF4618B0881}</ProjectGuid>
18 <Keyword>Win32Proj</Keyword>
19 <ConfigurationType>Application</ConfigurationType>
20 <CharacterSet>Unicode</CharacterSet>
21 <Description>WiX Toolset Theme Viewer</Description>
22 </PropertyGroup>
23
24 <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
25 <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
26
27 <ImportGroup Label="ExtensionSettings">
28 </ImportGroup>
29
30 <ImportGroup Label="Shared">
31 </ImportGroup>
32
33 <PropertyGroup>
34 <ProjectAdditionalLinkLibraries>comctl32.lib;gdiplus.lib;msimg32.lib;shlwapi.lib</ProjectAdditionalLinkLibraries>
35 </PropertyGroup>
36
37 <ItemGroup>
38 <ClCompile Include="display.cpp" />
39 <ClCompile Include="load.cpp" />
40 <ClCompile Include="precomp.cpp">
41 <PrecompiledHeader>Create</PrecompiledHeader>
42 </ClCompile>
43 <ClCompile Include="thmviewer.cpp" />
44 </ItemGroup>
45 <ItemGroup>
46 <ClInclude Include="precomp.h" />
47 <ClInclude Include="resource.h" />
48 </ItemGroup>
49 <ItemGroup>
50 <None Include="packages.config" />
51 <None Include="Resources\LoremIpsum.rtf" />
52 <None Include="Resources\thm.xml" />
53 </ItemGroup>
54 <ItemGroup>
55 <ResourceCompile Include="thmviewer.rc" />
56 </ItemGroup>
57 <ItemGroup>
58 <Manifest Include="thmviewer.manifest" />
59 </ItemGroup>
60
61 <ItemGroup>
62 <PackageReference Include="WixToolset.DUtil" />
63
64 <PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" />
65 <PackageReference Include="GitInfo" PrivateAssets="All" />
66 </ItemGroup>
67
68 <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
69</Project>
diff --git a/src/tools/thmviewer/thmviewer.vcxproj.filters b/src/tools/thmviewer/thmviewer.vcxproj.filters
new file mode 100644
index 00000000..488d5510
--- /dev/null
+++ b/src/tools/thmviewer/thmviewer.vcxproj.filters
@@ -0,0 +1,56 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3 <ItemGroup>
4 <Filter Include="Source Files">
5 <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
6 <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
7 </Filter>
8 <Filter Include="Header Files">
9 <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
10 <Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions>
11 </Filter>
12 <Filter Include="Resource Files">
13 <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
14 <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav</Extensions>
15 </Filter>
16 </ItemGroup>
17 <ItemGroup>
18 <ClCompile Include="thmviewer.cpp">
19 <Filter>Source Files</Filter>
20 </ClCompile>
21 <ClCompile Include="display.cpp">
22 <Filter>Source Files</Filter>
23 </ClCompile>
24 <ClCompile Include="load.cpp">
25 <Filter>Source Files</Filter>
26 </ClCompile>
27 <ClCompile Include="precomp.cpp">
28 <Filter>Source Files</Filter>
29 </ClCompile>
30 </ItemGroup>
31 <ItemGroup>
32 <None Include="Resources\thm.xml">
33 <Filter>Resource Files</Filter>
34 </None>
35 <None Include="Resources\LoremIpsum.rtf">
36 <Filter>Resource Files</Filter>
37 </None>
38 <None Include="packages.config" />
39 </ItemGroup>
40 <ItemGroup>
41 <ClInclude Include="precomp.h">
42 <Filter>Header Files</Filter>
43 </ClInclude>
44 <ClInclude Include="resource.h">
45 <Filter>Header Files</Filter>
46 </ClInclude>
47 </ItemGroup>
48 <ItemGroup>
49 <ResourceCompile Include="thmviewer.rc">
50 <Filter>Resource Files</Filter>
51 </ResourceCompile>
52 </ItemGroup>
53 <ItemGroup>
54 <Manifest Include="thmviewer.manifest" />
55 </ItemGroup>
56</Project> \ No newline at end of file