1 // Copyright 2018 Dolphin Emulator Project
2 // Licensed under GPLv2+
3 // Refer to the license.txt file included.
4
5 #include "UpdaterCommon/UI.h"
6
7 #include <string>
8 #include <thread>
9
10 #include <Windows.h>
11 #include <CommCtrl.h>
12 #include <ShObjIdl.h>
13 #include <shellapi.h>
14 #include <wrl/client.h>
15
16 #include "Common/Event.h"
17 #include "Common/ScopeGuard.h"
18 #include "Common/StringUtil.h"
19
20 namespace
21 {
22 HWND window_handle = nullptr;
23 HWND label_handle = nullptr;
24 HWND total_progressbar_handle = nullptr;
25 HWND current_progressbar_handle = nullptr;
26 Microsoft::WRL::ComPtr<ITaskbarList3> taskbar_list;
27
28 std::thread ui_thread;
29 Common::Event window_created_event;
30
GetWindowHeight(HWND hwnd)31 int GetWindowHeight(HWND hwnd)
32 {
33 RECT rect;
34 GetWindowRect(hwnd, &rect);
35
36 return rect.bottom - rect.top;
37 }
38
WindowProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam)39 LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
40 {
41 switch (uMsg)
42 {
43 case WM_DESTROY:
44 PostQuitMessage(0);
45 return 0;
46 }
47 return DefWindowProc(hwnd, uMsg, wParam, lParam);
48 }
49 }; // namespace
50
51 constexpr int PROGRESSBAR_FLAGS = WS_VISIBLE | WS_CHILD | PBS_SMOOTH | PBS_SMOOTHREVERSE;
52 constexpr int WINDOW_FLAGS = WS_CLIPCHILDREN;
53 constexpr int PADDING_HEIGHT = 5;
54
55 namespace UI
56 {
InitWindow()57 bool InitWindow()
58 {
59 InitCommonControls();
60
61 // Notify main thread we're done creating the window when we return
62 Common::ScopeGuard ui_guard{[] { window_created_event.Set(); }};
63
64 WNDCLASS wndcl = {};
65 wndcl.lpfnWndProc = WindowProc;
66 wndcl.hbrBackground = GetSysColorBrush(COLOR_MENU);
67 wndcl.lpszClassName = L"UPDATER";
68
69 if (!RegisterClass(&wndcl))
70 return false;
71
72 window_handle =
73 CreateWindow(L"UPDATER", L"Dolphin Updater", WINDOW_FLAGS, CW_USEDEFAULT, CW_USEDEFAULT, 500,
74 100, nullptr, nullptr, GetModuleHandle(nullptr), 0);
75
76 if (!window_handle)
77 return false;
78
79 if (SUCCEEDED(CoCreateInstance(CLSID_TaskbarList, NULL, CLSCTX_INPROC_SERVER,
80 IID_PPV_ARGS(taskbar_list.GetAddressOf()))))
81 {
82 if (FAILED(taskbar_list->HrInit()))
83 {
84 taskbar_list.Reset();
85 }
86 }
87
88 int y = PADDING_HEIGHT;
89
90 label_handle = CreateWindow(L"STATIC", NULL, WS_VISIBLE | WS_CHILD, 5, y, 500, 25, window_handle,
91 nullptr, nullptr, 0);
92
93 if (!label_handle)
94 return false;
95
96 // Get the default system font
97 NONCLIENTMETRICS metrics = {};
98 metrics.cbSize = sizeof(NONCLIENTMETRICS);
99
100 if (!SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(metrics), &metrics, 0))
101 return false;
102
103 SendMessage(label_handle, WM_SETFONT,
104 reinterpret_cast<WPARAM>(CreateFontIndirect(&metrics.lfMessageFont)), 0);
105
106 y += GetWindowHeight(label_handle) + PADDING_HEIGHT;
107
108 total_progressbar_handle = CreateWindow(PROGRESS_CLASS, NULL, PROGRESSBAR_FLAGS, 5, y, 470, 25,
109 window_handle, nullptr, nullptr, 0);
110
111 y += GetWindowHeight(total_progressbar_handle) + PADDING_HEIGHT;
112
113 if (!total_progressbar_handle)
114 return false;
115
116 current_progressbar_handle = CreateWindow(PROGRESS_CLASS, NULL, PROGRESSBAR_FLAGS, 5, y, 470, 25,
117 window_handle, nullptr, nullptr, 0);
118
119 y += GetWindowHeight(current_progressbar_handle) + PADDING_HEIGHT;
120
121 if (!current_progressbar_handle)
122 return false;
123
124 RECT rect;
125 GetWindowRect(window_handle, &rect);
126
127 // Account for the title bar
128 y += GetSystemMetrics(SM_CYFRAME) + GetSystemMetrics(SM_CYCAPTION) +
129 GetSystemMetrics(SM_CXPADDEDBORDER);
130 // ...and window border
131 y += GetSystemMetrics(SM_CYBORDER);
132
133 // Add some padding for good measure
134 y += PADDING_HEIGHT * 3;
135
136 SetWindowPos(window_handle, HWND_TOPMOST, 0, 0, rect.right - rect.left, y, SWP_NOMOVE);
137
138 return true;
139 }
140
SetTotalMarquee(bool marquee)141 void SetTotalMarquee(bool marquee)
142 {
143 SetWindowLong(total_progressbar_handle, GWL_STYLE,
144 PROGRESSBAR_FLAGS | (marquee ? PBS_MARQUEE : 0));
145 SendMessage(total_progressbar_handle, PBM_SETMARQUEE, marquee, 0);
146 if (taskbar_list)
147 {
148 taskbar_list->SetProgressState(window_handle, marquee ? TBPF_INDETERMINATE : TBPF_NORMAL);
149 }
150 }
151
ResetTotalProgress()152 void ResetTotalProgress()
153 {
154 SendMessage(total_progressbar_handle, PBM_SETPOS, 0, 0);
155 SetCurrentMarquee(true);
156 }
157
SetTotalProgress(int current,int total)158 void SetTotalProgress(int current, int total)
159 {
160 SendMessage(total_progressbar_handle, PBM_SETRANGE32, 0, total);
161 SendMessage(total_progressbar_handle, PBM_SETPOS, current, 0);
162 if (taskbar_list)
163 {
164 taskbar_list->SetProgressValue(window_handle, current, total);
165 }
166 }
167
SetCurrentMarquee(bool marquee)168 void SetCurrentMarquee(bool marquee)
169 {
170 SetWindowLong(current_progressbar_handle, GWL_STYLE,
171 PROGRESSBAR_FLAGS | (marquee ? PBS_MARQUEE : 0));
172 SendMessage(current_progressbar_handle, PBM_SETMARQUEE, marquee, 0);
173 }
174
ResetCurrentProgress()175 void ResetCurrentProgress()
176 {
177 SendMessage(current_progressbar_handle, PBM_SETPOS, 0, 0);
178 SetCurrentMarquee(true);
179 }
180
Error(const std::string & text)181 void Error(const std::string& text)
182 {
183 auto wide_text = UTF8ToWString(text);
184
185 MessageBox(nullptr,
186 (L"A fatal error occured and the updater cannot continue:\n " + wide_text).c_str(),
187 L"Error", MB_ICONERROR);
188
189 if (taskbar_list)
190 {
191 taskbar_list->SetProgressState(window_handle, TBPF_ERROR);
192 }
193 }
194
SetCurrentProgress(int current,int total)195 void SetCurrentProgress(int current, int total)
196 {
197 SendMessage(current_progressbar_handle, PBM_SETRANGE32, 0, total);
198 SendMessage(current_progressbar_handle, PBM_SETPOS, current, 0);
199 }
200
SetDescription(const std::string & text)201 void SetDescription(const std::string& text)
202 {
203 SetWindowText(label_handle, UTF8ToWString(text).c_str());
204 }
205
MessageLoop()206 void MessageLoop()
207 {
208 HRESULT result = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
209
210 Common::ScopeGuard ui_guard{[result] {
211 taskbar_list.Reset();
212 if (SUCCEEDED(result))
213 CoUninitialize();
214 }};
215
216 if (!InitWindow())
217 {
218 MessageBox(nullptr, L"Window init failed!", L"", MB_ICONERROR);
219 // Destroying the parent (if exists) destroys all children windows
220 if (window_handle)
221 {
222 DestroyWindow(window_handle);
223 window_handle = nullptr;
224 }
225 return;
226 }
227
228 SetTotalMarquee(true);
229 SetCurrentMarquee(true);
230
231 MSG msg;
232 while (GetMessage(&msg, nullptr, 0, 0))
233 {
234 TranslateMessage(&msg);
235 DispatchMessage(&msg);
236 }
237 }
238
Init()239 void Init()
240 {
241 ui_thread = std::thread(MessageLoop);
242
243 // Wait for UI thread to finish creating the window (or at least attempting to)
244 window_created_event.Wait();
245 }
246
Stop()247 void Stop()
248 {
249 if (window_handle)
250 PostMessage(window_handle, WM_CLOSE, 0, 0);
251
252 ui_thread.join();
253 }
254
LaunchApplication(std::string path)255 void LaunchApplication(std::string path)
256 {
257 // Hack: Launching the updater over the explorer ensures that admin priviliges are dropped. Why?
258 // Ask Microsoft.
259 ShellExecuteW(nullptr, nullptr, L"explorer.exe", UTF8ToWString(path).c_str(), nullptr, SW_SHOW);
260 }
261
Sleep(int sleep)262 void Sleep(int sleep)
263 {
264 ::Sleep(sleep * 1000);
265 }
266
WaitForPID(u32 pid)267 void WaitForPID(u32 pid)
268 {
269 HANDLE parent_handle = OpenProcess(SYNCHRONIZE, FALSE, static_cast<DWORD>(pid));
270 if (parent_handle)
271 {
272 WaitForSingleObject(parent_handle, INFINITE);
273 CloseHandle(parent_handle);
274 }
275 }
276
SetVisible(bool visible)277 void SetVisible(bool visible)
278 {
279 ShowWindow(window_handle, visible ? SW_SHOW : SW_HIDE);
280 }
281
282 }; // namespace UI
283