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