main
cpp 415 lines 12 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 #define BURN_SPLASHSCREEN_CLASS_WINDOW L"WixBurnSplashScreen"
6
7 // struct
8
9 struct SPLASHSCREEN_INFO
10 {
11 HBITMAP hBitmap;
12 SIZE defaultDpiSize;
13 SIZE size;
14 UINT nDpi;
15 HWND hWnd;
16 HWND hwndPrevious;
17 };
18
19 struct SPLASHSCREEN_CONTEXT
20 {
21 HANDLE hInitializedEvent;
22 HINSTANCE hInstance;
23 LPCWSTR wzCaption;
24 BURN_SPLASH_SCREEN_CONFIGURATION* pSplashScreenConfiguration;
25
26 HWND* pHwnd;
27 };
28
29 // internal function definitions
30
31 static HRESULT LoadConfiguration(
32 __in HINSTANCE hInstance,
33 __in BURN_SPLASH_SCREEN_CONFIGURATION** ppSplashScreenConfiguration
34 );
35 static DWORD WINAPI ThreadProc(
36 __in LPVOID pvContext
37 );
38 static LRESULT CALLBACK WndProc(
39 __in HWND hWnd,
40 __in UINT uMsg,
41 __in WPARAM wParam,
42 __in LPARAM lParam
43 );
44 static HRESULT LoadSplashScreen(
45 __in SPLASHSCREEN_CONTEXT* pContext,
46 __in SPLASHSCREEN_INFO* pSplashScreen
47 );
48 static BOOL OnDpiChanged(
49 __in SPLASHSCREEN_INFO* pSplashScreen,
50 __in WPARAM wParam,
51 __in LPARAM lParam
52 );
53 static void OnEraseBkgnd(
54 __in SPLASHSCREEN_INFO* pSplashScreen,
55 __in WPARAM wParam
56 );
57 static void OnNcCreate(
58 __in HWND hWnd,
59 __in LPARAM lParam
60 );
61 static void ScaleSplashScreen(
62 __in SPLASHSCREEN_INFO* pSplashScreen,
63 __in UINT nDpi,
64 __in int x,
65 __in int y
66 );
67
68
69 // function definitions
70
71 extern "C" void SplashScreenCreate(
72 __in HINSTANCE hInstance,
73 __in_z_opt LPCWSTR wzCaption,
74 __out HWND* pHwnd
75 )
76 {
77 HRESULT hr = S_OK;
78 SPLASHSCREEN_CONTEXT context = { };
79 HANDLE rgSplashScreenEvents[2] = { };
80 DWORD dwSplashScreenThreadId = 0;
81
82 hr = LoadConfiguration(hInstance, &context.pSplashScreenConfiguration);
83 ExitOnFailure(hr, "Failed to load splash screen configuration.");
84
85 switch (context.pSplashScreenConfiguration->type)
86 {
87 case BURN_SPLASH_SCREEN_TYPE_NONE:
88 ExitFunction();
89 case BURN_SPLASH_SCREEN_TYPE_BITMAP_RESOURCE:
90 break;
91 default:
92 ExitWithRootFailure(hr, E_INVALIDDATA, "Invalid splash screen type: %i", context.pSplashScreenConfiguration->type);
93 }
94
95 rgSplashScreenEvents[0] = ::CreateEventW(NULL, TRUE, FALSE, NULL);
96 ExitOnNullWithLastError(rgSplashScreenEvents[0], hr, "Failed to create modal event.");
97
98 // create splash screen thread.
99 context.hInitializedEvent = rgSplashScreenEvents[0];
100 context.hInstance = hInstance;
101 context.wzCaption = wzCaption;
102 context.pHwnd = pHwnd;
103
104 rgSplashScreenEvents[1] = ::CreateThread(NULL, 0, ThreadProc, &context, 0, &dwSplashScreenThreadId);
105 ExitOnNullWithLastError(rgSplashScreenEvents[1], hr, "Failed to create UI thread.");
106
107 // It doesn't really matter if the thread gets initialized (WAIT_OBJECT_0) or fails and exits
108 // prematurely (WAIT_OBJECT_0 + 1), we just want to wait long enough for one of those two
109 // events to happen.
110 ::WaitForMultipleObjects(countof(rgSplashScreenEvents), rgSplashScreenEvents, FALSE, INFINITE);
111
112 LExit:
113 ReleaseHandle(rgSplashScreenEvents[1]);
114 ReleaseHandle(rgSplashScreenEvents[0]);
115 }
116
117 extern "C" HRESULT SplashScreenDisplayError(
118 __in BOOTSTRAPPER_DISPLAY display,
119 __in_z LPCWSTR wzBundleName,
120 __in HRESULT hrError
121 )
122 {
123 HRESULT hr = S_OK;
124 LPWSTR sczDisplayString = NULL;
125
126 hr = StrAllocFromError(&sczDisplayString, hrError, NULL);
127 ExitOnFailure(hr, "Failed to allocate string to display error message");
128
129 Trace(REPORT_STANDARD, "Error message displayed because: %ls", sczDisplayString);
130
131 if (BOOTSTRAPPER_DISPLAY_NONE == display || BOOTSTRAPPER_DISPLAY_PASSIVE == display || BOOTSTRAPPER_DISPLAY_EMBEDDED == display)
132 {
133 // Don't display the error dialog in these modes
134 ExitFunction1(hr = S_OK);
135 }
136
137 ::MessageBoxW(NULL, sczDisplayString, wzBundleName, MB_OK | MB_ICONERROR | MB_SYSTEMMODAL);
138
139 LExit:
140 ReleaseStr(sczDisplayString);
141
142 return hr;
143 }
144
145
146 static HRESULT LoadConfiguration(
147 __in HINSTANCE hInstance,
148 __in BURN_SPLASH_SCREEN_CONFIGURATION** ppSplashScreenConfiguration
149 )
150 {
151 HRESULT hr = S_OK;
152 DWORD cbData = 0;
153
154 hr = ResReadData(hInstance, MAKEINTRESOURCEA(IDD_BURN_SPLASH_SCREEN_CONFIGURATION), reinterpret_cast<PVOID*>(ppSplashScreenConfiguration), &cbData);
155 ExitOnFailure(hr, "Failed to read splash screen configuration resource.");
156
157 AssertSz(sizeof(BURN_SPLASH_SCREEN_CONFIGURATION) == cbData, "Splash screen configuration resource size is wrong");
158
159 LExit:
160 return hr;
161 }
162
163
164 static DWORD WINAPI ThreadProc(
165 __in LPVOID pvContext
166 )
167 {
168 HRESULT hr = S_OK;
169 SPLASHSCREEN_CONTEXT* pContext = static_cast<SPLASHSCREEN_CONTEXT*>(pvContext);
170
171 SPLASHSCREEN_INFO splashScreenInfo = { };
172
173 WNDCLASSW wc = { };
174 BOOL fRegistered = FALSE;
175
176 BOOL fRet = FALSE;
177 MSG msg = { };
178
179 // Register the window class.
180 wc.lpfnWndProc = WndProc;
181 wc.hInstance = pContext->hInstance;
182 wc.hCursor = ::LoadCursorW(NULL, (LPCWSTR)IDC_ARROW);
183 wc.lpszClassName = BURN_SPLASHSCREEN_CLASS_WINDOW;
184 if (!::RegisterClassW(&wc))
185 {
186 ExitWithLastError(hr, "Failed to register window.");
187 }
188
189 fRegistered = TRUE;
190
191 hr = LoadSplashScreen(pContext, &splashScreenInfo);
192 ExitOnFailure(hr, "Failed to load splash screen.");
193
194 // Return the splash screen window and free the main thread waiting for us to be initialized.
195 *pContext->pHwnd = splashScreenInfo.hWnd;
196 ::SetEvent(pContext->hInitializedEvent);
197
198 // Pump messages until the bootstrapper application destroys the window.
199 while (0 != (fRet = ::GetMessageW(&msg, NULL, 0, 0)))
200 {
201 if (-1 == fRet)
202 {
203 hr = E_UNEXPECTED;
204 ExitOnFailure(hr, "Unexpected return value from message pump.");
205 }
206 else if (!::IsDialogMessageW(splashScreenInfo.hWnd, &msg))
207 {
208 ::TranslateMessage(&msg);
209 ::DispatchMessageW(&msg);
210 }
211 }
212
213 LExit:
214 if (fRegistered)
215 {
216 ::UnregisterClassW(BURN_SPLASHSCREEN_CLASS_WINDOW, pContext->hInstance);
217 }
218
219 if (splashScreenInfo.hBitmap)
220 {
221 ::DeleteObject(splashScreenInfo.hBitmap);
222 }
223
224 if (splashScreenInfo.hwndPrevious)
225 {
226 ::PostMessageW(splashScreenInfo.hwndPrevious, WM_CLOSE, 0, 0);
227 }
228
229 return hr;
230 }
231
232 static LRESULT CALLBACK WndProc(
233 __in HWND hWnd,
234 __in UINT uMsg,
235 __in WPARAM wParam,
236 __in LPARAM lParam
237 )
238 {
239 LRESULT lres = 0;
240 SPLASHSCREEN_INFO* pSplashScreen = reinterpret_cast<SPLASHSCREEN_INFO*>(::GetWindowLongPtrW(hWnd, GWLP_USERDATA));
241
242 switch (uMsg)
243 {
244 case WM_NCCREATE:
245 OnNcCreate(hWnd, lParam);
246 break;
247
248 case WM_NCDESTROY:
249 lres = ::DefWindowProcW(hWnd, uMsg, wParam, lParam);
250 ::SetWindowLongPtrW(hWnd, GWLP_USERDATA, 0);
251 ::PostQuitMessage(0);
252 return lres;
253
254 case WM_NCHITTEST:
255 return HTCAPTION; // allow window to be moved by grabbing any pixel.
256
257 case WM_DPICHANGED:
258 if (OnDpiChanged(pSplashScreen, wParam, lParam))
259 {
260 return 0;
261 }
262 break;
263
264 case WM_ERASEBKGND:
265 OnEraseBkgnd(pSplashScreen, wParam);
266 return 1;
267
268 case WM_ENTERIDLE:
269 case WM_MOVING:
270 lres = ::DefWindowProcW(hWnd, uMsg, wParam, lParam);
271
272 // We had to create our own splash screen so that Windows would automatically transfer focus from the other process's splash screen.
273 // Try to make sure new splash screen has painted before closing old one to avoid flickering.
274 if (pSplashScreen->hwndPrevious)
275 {
276 ::PostMessageW(pSplashScreen->hwndPrevious, WM_CLOSE, 0, 0);
277 pSplashScreen->hwndPrevious = NULL;
278 }
279
280 return lres;
281 }
282
283 return ::DefWindowProcW(hWnd, uMsg, wParam, lParam);
284 }
285
286 static HRESULT LoadSplashScreen(
287 __in SPLASHSCREEN_CONTEXT* pContext,
288 __in SPLASHSCREEN_INFO* pSplashScreen
289 )
290 {
291 HRESULT hr = S_OK;
292 BITMAP bmp = { };
293 POINT pt = { };
294 int x = 0;
295 int y = 0;
296 DPIU_MONITOR_CONTEXT* pMonitorContext = NULL;
297 RECT* pMonitorRect = NULL;
298
299 if (::IsWindow(*pContext->pHwnd))
300 {
301 pSplashScreen->hwndPrevious = *pContext->pHwnd;
302 }
303
304 pSplashScreen->nDpi = USER_DEFAULT_SCREEN_DPI;
305 pSplashScreen->hBitmap = ::LoadBitmapW(pContext->hInstance, MAKEINTRESOURCEW(pContext->pSplashScreenConfiguration->wResourceId));
306 ExitOnNullWithLastError(pSplashScreen->hBitmap, hr, "Failed to load splash screen bitmap.");
307
308 ::GetObject(pSplashScreen->hBitmap, sizeof(bmp), static_cast<void*>(&bmp));
309 pSplashScreen->defaultDpiSize.cx = pSplashScreen->size.cx = bmp.bmWidth;
310 pSplashScreen->defaultDpiSize.cy = pSplashScreen->size.cy = bmp.bmHeight;
311
312 // Try to default to the monitor with the mouse, otherwise default to the primary monitor.
313 if (!::GetCursorPos(&pt))
314 {
315 pt.x = 0;
316 pt.y = 0;
317 }
318
319 // Try to center the window on the chosen monitor.
320 hr = DpiuGetMonitorContextFromPoint(&pt, &pMonitorContext);
321 if (SUCCEEDED(hr))
322 {
323 pMonitorRect = &pMonitorContext->mi.rcWork;
324 if (pMonitorContext->nDpi != pSplashScreen->nDpi)
325 {
326 ScaleSplashScreen(pSplashScreen, pMonitorContext->nDpi, pMonitorRect->left, pMonitorRect->top);
327 }
328
329 x = pMonitorRect->left + (pMonitorRect->right - pMonitorRect->left - pSplashScreen->size.cx) / 2;
330 y = pMonitorRect->top + (pMonitorRect->bottom - pMonitorRect->top - pSplashScreen->size.cy) / 2;
331 }
332 else
333 {
334 hr = S_OK;
335 x = CW_USEDEFAULT;
336 y = CW_USEDEFAULT;
337 }
338
339 pSplashScreen->hWnd = ::CreateWindowExW(WS_EX_TOOLWINDOW, BURN_SPLASHSCREEN_CLASS_WINDOW, pContext->wzCaption, WS_POPUP | WS_VISIBLE, x, y, pSplashScreen->size.cx, pSplashScreen->size.cy, HWND_DESKTOP, NULL, pContext->hInstance, pSplashScreen);
340 ExitOnNullWithLastError(pSplashScreen->hWnd, hr, "Failed to create splash screen window.");
341
342 LExit:
343 MemFree(pMonitorContext);
344
345 return hr;
346 }
347
348 static BOOL OnDpiChanged(
349 __in SPLASHSCREEN_INFO* pSplashScreen,
350 __in WPARAM wParam,
351 __in LPARAM lParam
352 )
353 {
354 UINT nDpi = HIWORD(wParam);
355 RECT* pRect = reinterpret_cast<RECT*>(lParam);
356 BOOL fDpiChanged = pSplashScreen->nDpi != nDpi;
357
358 if (fDpiChanged)
359 {
360 ScaleSplashScreen(pSplashScreen, nDpi, pRect->left, pRect->top);
361 }
362
363 return fDpiChanged;
364 }
365
366 static void OnEraseBkgnd(
367 __in SPLASHSCREEN_INFO* pSplashScreen,
368 __in WPARAM wParam
369 )
370 {
371 HDC hdc = reinterpret_cast<HDC>(wParam);
372 HDC hdcMem = ::CreateCompatibleDC(hdc);
373 HBITMAP hDefaultBitmap = static_cast<HBITMAP>(::SelectObject(hdcMem, pSplashScreen->hBitmap));
374 ::StretchBlt(hdc, 0, 0, pSplashScreen->size.cx, pSplashScreen->size.cy, hdcMem, 0, 0, pSplashScreen->defaultDpiSize.cx, pSplashScreen->defaultDpiSize.cy, SRCCOPY);
375 ::SelectObject(hdcMem, hDefaultBitmap);
376 ::DeleteDC(hdcMem);
377 }
378
379 static void OnNcCreate(
380 __in HWND hWnd,
381 __in LPARAM lParam
382 )
383 {
384 DPIU_WINDOW_CONTEXT windowContext = { };
385 CREATESTRUCTW* pCreateStruct = reinterpret_cast<CREATESTRUCTW*>(lParam);
386 SPLASHSCREEN_INFO* pSplashScreen = reinterpret_cast<SPLASHSCREEN_INFO*>(pCreateStruct->lpCreateParams);
387
388 ::SetWindowLongPtrW(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pSplashScreen));
389 pSplashScreen->hWnd = hWnd;
390
391 DpiuGetWindowContext(pSplashScreen->hWnd, &windowContext);
392
393 if (windowContext.nDpi != pSplashScreen->nDpi)
394 {
395 ScaleSplashScreen(pSplashScreen, windowContext.nDpi, pCreateStruct->x, pCreateStruct->y);
396 }
397 }
398
399 static void ScaleSplashScreen(
400 __in SPLASHSCREEN_INFO* pSplashScreen,
401 __in UINT nDpi,
402 __in int x,
403 __in int y
404 )
405 {
406 pSplashScreen->nDpi = nDpi;
407
408 pSplashScreen->size.cx = DpiuScaleValue(pSplashScreen->defaultDpiSize.cx, pSplashScreen->nDpi);
409 pSplashScreen->size.cy = DpiuScaleValue(pSplashScreen->defaultDpiSize.cy, pSplashScreen->nDpi);
410
411 if (pSplashScreen->hWnd)
412 {
413 ::SetWindowPos(pSplashScreen->hWnd, NULL, x, y, pSplashScreen->size.cx, pSplashScreen->size.cy, SWP_NOACTIVATE | SWP_NOZORDER);
414 }
415 }