aboutsummaryrefslogtreecommitdiff
path: root/src/burn/engine/uithread.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/burn/engine/uithread.cpp')
-rw-r--r--src/burn/engine/uithread.cpp222
1 files changed, 222 insertions, 0 deletions
diff --git a/src/burn/engine/uithread.cpp b/src/burn/engine/uithread.cpp
new file mode 100644
index 00000000..433cb171
--- /dev/null
+++ b/src/burn/engine/uithread.cpp
@@ -0,0 +1,222 @@
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_UITHREAD_CLASS_WINDOW L"WixBurnMessageWindow"
6
7
8// structs
9
10struct UITHREAD_CONTEXT
11{
12 HANDLE hInitializedEvent;
13 HINSTANCE hInstance;
14 BURN_ENGINE_STATE* pEngineState;
15};
16
17struct UITHREAD_INFO
18{
19 BOOL fElevated;
20 BURN_USER_EXPERIENCE* pUserExperience;
21};
22
23
24// internal function declarations
25
26static DWORD WINAPI ThreadProc(
27 __in LPVOID pvContext
28 );
29
30static LRESULT CALLBACK WndProc(
31 __in HWND hWnd,
32 __in UINT uMsg,
33 __in WPARAM wParam,
34 __in LPARAM lParam
35 );
36
37
38// function definitions
39
40HRESULT UiCreateMessageWindow(
41 __in HINSTANCE hInstance,
42 __in BURN_ENGINE_STATE* pEngineState
43 )
44{
45 HRESULT hr = S_OK;
46 HANDLE rgWaitHandles[2] = { };
47 UITHREAD_CONTEXT context = { };
48
49 // Create event to signal after the UI thread / window is initialized.
50 rgWaitHandles[0] = ::CreateEventW(NULL, TRUE, FALSE, NULL);
51 ExitOnNullWithLastError(rgWaitHandles[0], hr, "Failed to create initialization event.");
52
53 // Pass necessary information to create the window.
54 context.hInitializedEvent = rgWaitHandles[0];
55 context.hInstance = hInstance;
56 context.pEngineState = pEngineState;
57
58 // Create our separate UI thread.
59 rgWaitHandles[1] = ::CreateThread(NULL, 0, ThreadProc, &context, 0, NULL);
60 ExitOnNullWithLastError(rgWaitHandles[1], hr, "Failed to create the UI thread.");
61
62 // Wait for either the thread to be initialized or the window to exit / fail prematurely.
63 ::WaitForMultipleObjects(countof(rgWaitHandles), rgWaitHandles, FALSE, INFINITE);
64
65 pEngineState->hMessageWindowThread = rgWaitHandles[1];
66 rgWaitHandles[1] = NULL;
67
68LExit:
69 ReleaseHandle(rgWaitHandles[1]);
70 ReleaseHandle(rgWaitHandles[0]);
71
72 return hr;
73}
74
75void UiCloseMessageWindow(
76 __in BURN_ENGINE_STATE* pEngineState
77 )
78{
79 if (::IsWindow(pEngineState->hMessageWindow))
80 {
81 ::PostMessageW(pEngineState->hMessageWindow, WM_CLOSE, 0, 0);
82
83 // Give the window 15 seconds to close because if it stays open it can prevent
84 // the engine from starting a reboot (should a reboot actually be necessary).
85 ::WaitForSingleObject(pEngineState->hMessageWindowThread, 15 * 1000);
86 }
87}
88
89
90// internal function definitions
91
92static DWORD WINAPI ThreadProc(
93 __in LPVOID pvContext
94 )
95{
96 HRESULT hr = S_OK;
97 UITHREAD_CONTEXT* pContext = static_cast<UITHREAD_CONTEXT*>(pvContext);
98 UITHREAD_INFO info = { };
99
100 WNDCLASSW wc = { };
101 BOOL fRegistered = TRUE;
102 HWND hWnd = NULL;
103
104 BOOL fRet = FALSE;
105 MSG msg = { };
106
107 BURN_ENGINE_STATE* pEngineState = pContext->pEngineState;
108 BOOL fElevated = BURN_MODE_ELEVATED == pContext->pEngineState->mode;
109
110 // If elevated, set up the thread local storage to store the correct pipe to communicate logging.
111 if (fElevated)
112 {
113 Assert(TLS_OUT_OF_INDEXES != pEngineState->dwElevatedLoggingTlsId);
114
115 if (!::TlsSetValue(pEngineState->dwElevatedLoggingTlsId, pEngineState->companionConnection.hPipe))
116 {
117 // If the function failed we cannot write to the pipe so just terminate.
118 ExitFunction1(hr = E_INVALIDSTATE);
119 }
120 }
121
122 wc.lpfnWndProc = WndProc;
123 wc.hInstance = pContext->hInstance;
124 wc.lpszClassName = BURN_UITHREAD_CLASS_WINDOW;
125
126 if (!::RegisterClassW(&wc))
127 {
128 ExitWithLastError(hr, "Failed to register window.");
129 }
130
131 fRegistered = TRUE;
132
133 info.fElevated = fElevated;
134 info.pUserExperience = &pEngineState->userExperience;
135
136 // Create the window to handle reboots without activating it.
137 hWnd = ::CreateWindowExW(WS_EX_NOACTIVATE, wc.lpszClassName, NULL, WS_POPUP, 0, 0, 0, 0, HWND_DESKTOP, NULL, pContext->hInstance, &info);
138 ExitOnNullWithLastError(hWnd, hr, "Failed to create window.");
139
140 ::ShowWindow(hWnd, SW_SHOWNA);
141
142 // Persist the window handle and let the caller know we've initialized.
143 pEngineState->hMessageWindow = hWnd;
144 ::SetEvent(pContext->hInitializedEvent);
145
146 // Pump messages until the window is closed.
147 while (0 != (fRet = ::GetMessageW(&msg, NULL, 0, 0)))
148 {
149 if (-1 == fRet)
150 {
151 hr = E_UNEXPECTED;
152 ExitOnFailure(hr, "Unexpected return value from message pump.");
153 }
154 else if (!::IsDialogMessageW(msg.hwnd, &msg))
155 {
156 ::TranslateMessage(&msg);
157 ::DispatchMessageW(&msg);
158 }
159 }
160
161LExit:
162 if (fRegistered)
163 {
164 ::UnregisterClassW(BURN_UITHREAD_CLASS_WINDOW, pContext->hInstance);
165 }
166
167 return hr;
168}
169
170static LRESULT CALLBACK WndProc(
171 __in HWND hWnd,
172 __in UINT uMsg,
173 __in WPARAM wParam,
174 __in LPARAM lParam
175 )
176{
177 switch (uMsg)
178 {
179 case WM_NCCREATE:
180 {
181 LPCREATESTRUCTW lpcs = reinterpret_cast<LPCREATESTRUCTW>(lParam);
182 ::SetWindowLongPtrW(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(lpcs->lpCreateParams));
183 break;
184 }
185
186 case WM_NCDESTROY:
187 {
188 LRESULT lRes = ::DefWindowProcW(hWnd, uMsg, wParam, lParam);
189 ::SetWindowLongPtrW(hWnd, GWLP_USERDATA, 0);
190 return lRes;
191 }
192
193 case WM_QUERYENDSESSION:
194 {
195 DWORD dwEndSession = static_cast<DWORD>(lParam);
196 BOOL fCritical = ENDSESSION_CRITICAL & dwEndSession;
197 BOOL fCancel = TRUE;
198 BOOL fRet = FALSE;
199
200 // Always block shutdown in the elevated process, but ask the BA in the non-elevated.
201 UITHREAD_INFO* pInfo = reinterpret_cast<UITHREAD_INFO*>(::GetWindowLongPtrW(hWnd, GWLP_USERDATA));
202 if (!pInfo->fElevated)
203 {
204 // TODO: instead of recommending canceling all non-critical shutdowns, maybe we should only recommend cancel
205 // when the engine is doing work?
206 fCancel = !fCritical;
207 // TODO: There's a race condition here where the BA may not have been loaded, or already was unloaded.
208 UserExperienceOnSystemShutdown(pInfo->pUserExperience, dwEndSession, &fCancel);
209 }
210
211 fRet = !fCancel;
212 LogId(REPORT_STANDARD, MSG_SYSTEM_SHUTDOWN, LoggingBoolToString(fCritical), LoggingBoolToString(pInfo->fElevated), LoggingBoolToString(fRet));
213 return fRet;
214 }
215
216 case WM_DESTROY:
217 ::PostQuitMessage(0);
218 return 0;
219 }
220
221 return ::DefWindowProcW(hWnd, uMsg, wParam, lParam);
222}