Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
stenzek
GitHub Repository: stenzek/duckstation
Path: blob/master/src/updater/win32_progress_callback.cpp
7365 views
1
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <[email protected]>
2
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
3
4
#include "win32_progress_callback.h"
5
#include "resource.h"
6
#include "win32_window_util.h"
7
8
#include "common/log.h"
9
#include "common/string_util.h"
10
11
#include <CommCtrl.h>
12
#include <shellscalingapi.h>
13
14
LOG_CHANNEL(Host);
15
16
Win32ProgressCallback::Win32ProgressCallback(HWND parent_hwnd) : UpdaterProgressCallback(), m_parent_hwnd(parent_hwnd)
17
{
18
Create();
19
}
20
21
Win32ProgressCallback::~Win32ProgressCallback()
22
{
23
Destroy();
24
}
25
26
void Win32ProgressCallback::PushState()
27
{
28
UpdaterProgressCallback::PushState();
29
}
30
31
void Win32ProgressCallback::PopState()
32
{
33
UpdaterProgressCallback::PopState();
34
Redraw(true);
35
}
36
37
void Win32ProgressCallback::SetCancellable(bool cancellable)
38
{
39
UpdaterProgressCallback::SetCancellable(cancellable);
40
Redraw(true);
41
}
42
43
void Win32ProgressCallback::SetTitle(const std::string_view title)
44
{
45
SetWindowText(m_window_hwnd, StringUtil::UTF8StringToWideString(title).c_str());
46
}
47
48
void Win32ProgressCallback::SetStatusText(const std::string_view text)
49
{
50
UpdaterProgressCallback::SetStatusText(text);
51
Redraw(true);
52
}
53
54
void Win32ProgressCallback::SetProgressRange(u32 range)
55
{
56
UpdaterProgressCallback::SetProgressRange(range);
57
Redraw(false);
58
}
59
60
void Win32ProgressCallback::SetProgressValue(u32 value)
61
{
62
UpdaterProgressCallback::SetProgressValue(value);
63
Redraw(false);
64
}
65
66
bool Win32ProgressCallback::Create()
67
{
68
static constexpr LPCWSTR CLASS_NAME = L"DSWin32ProgressCallbackWindow";
69
static bool class_registered = false;
70
71
if (!class_registered)
72
{
73
InitCommonControls();
74
75
WNDCLASSEX wc = {};
76
wc.cbSize = sizeof(WNDCLASSEX);
77
wc.lpfnWndProc = WndProcThunk;
78
wc.hInstance = GetModuleHandle(nullptr);
79
wc.hIcon = LoadIcon(wc.hInstance, MAKEINTRESOURCE(IDI_ICON1));
80
wc.hIconSm = LoadIcon(wc.hInstance, MAKEINTRESOURCE(IDI_ICON1));
81
wc.hCursor = LoadCursor(NULL, IDC_WAIT);
82
wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
83
wc.lpszClassName = CLASS_NAME;
84
if (!RegisterClassEx(&wc))
85
{
86
ERROR_LOG("Failed to register window class");
87
return false;
88
}
89
90
class_registered = true;
91
}
92
93
m_dpi = GetDpiForSystem();
94
95
RECT adjusted_rect = {0, 0, Scale(WINDOW_WIDTH), Scale(WINDOW_HEIGHT)};
96
AdjustWindowRectExForDpi(&adjusted_rect, WS_OVERLAPPEDWINDOW, FALSE, WS_EX_CLIENTEDGE, m_dpi);
97
98
const int window_width = adjusted_rect.right - adjusted_rect.left;
99
const int window_height = adjusted_rect.bottom - adjusted_rect.top;
100
101
m_window_hwnd =
102
CreateWindowEx(WS_EX_CLIENTEDGE, CLASS_NAME, L"DuckStation Update Installer", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,
103
CW_USEDEFAULT, window_width, window_height, m_parent_hwnd, nullptr, GetModuleHandle(nullptr), this);
104
if (!m_window_hwnd)
105
{
106
ERROR_LOG("Failed to create window");
107
return false;
108
}
109
110
// Disable parent window to make this modal
111
if (m_parent_hwnd)
112
EnableWindow(m_parent_hwnd, FALSE);
113
114
SetWindowLongPtr(m_window_hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this));
115
Win32WindowUtil::CenterWindowOnMonitorAtCursorPosition(m_window_hwnd);
116
ShowWindow(m_window_hwnd, SW_SHOW);
117
PumpMessages();
118
return true;
119
}
120
121
void Win32ProgressCallback::Destroy()
122
{
123
if (!m_window_hwnd)
124
return;
125
126
if (m_font)
127
{
128
DeleteObject(m_font);
129
m_font = nullptr;
130
}
131
132
DestroyWindow(m_window_hwnd);
133
m_window_hwnd = {};
134
m_text_hwnd = {};
135
m_progress_hwnd = {};
136
137
// Re-enable and restore focus to parent window
138
if (m_parent_hwnd)
139
{
140
EnableWindow(m_parent_hwnd, TRUE);
141
SetForegroundWindow(m_parent_hwnd);
142
}
143
}
144
145
void Win32ProgressCallback::PumpMessages()
146
{
147
MSG msg;
148
while (PeekMessage(&msg, m_window_hwnd, 0, 0, PM_REMOVE))
149
{
150
TranslateMessage(&msg);
151
DispatchMessage(&msg);
152
}
153
}
154
155
void Win32ProgressCallback::Redraw(bool force)
156
{
157
const int percent =
158
static_cast<int>((static_cast<float>(m_progress_value) / static_cast<float>(m_progress_range)) * 100.0f);
159
if (percent == m_last_progress_percent && !force)
160
{
161
PumpMessages();
162
return;
163
}
164
165
m_last_progress_percent = percent;
166
167
SendMessage(m_progress_hwnd, PBM_SETRANGE, 0, MAKELPARAM(0, m_progress_range));
168
SendMessage(m_progress_hwnd, PBM_SETPOS, static_cast<WPARAM>(m_progress_value), 0);
169
SetWindowText(m_text_hwnd, StringUtil::UTF8StringToWideString(m_status_text).c_str());
170
RedrawWindow(m_text_hwnd, nullptr, nullptr, RDW_INVALIDATE);
171
PumpMessages();
172
}
173
174
LRESULT CALLBACK Win32ProgressCallback::WndProcThunk(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
175
{
176
Win32ProgressCallback* cb;
177
if (msg == WM_CREATE)
178
{
179
const CREATESTRUCT* cs = reinterpret_cast<CREATESTRUCT*>(lparam);
180
cb = static_cast<Win32ProgressCallback*>(cs->lpCreateParams);
181
}
182
else
183
{
184
cb = reinterpret_cast<Win32ProgressCallback*>(GetWindowLongPtrA(hwnd, GWLP_USERDATA));
185
}
186
187
return cb->WndProc(hwnd, msg, wparam, lparam);
188
}
189
190
LRESULT CALLBACK Win32ProgressCallback::WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
191
{
192
switch (msg)
193
{
194
case WM_CREATE:
195
{
196
const CREATESTRUCT* cs = reinterpret_cast<CREATESTRUCT*>(lparam);
197
m_dpi = GetDpiForWindow(hwnd);
198
EnableMenuItem(GetSystemMenu(hwnd, FALSE), SC_CLOSE, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);
199
200
LOGFONT lf = {};
201
SystemParametersInfoForDpi(SPI_GETICONTITLELOGFONT, sizeof(lf), &lf, 0, m_dpi);
202
m_font = CreateFontIndirect(&lf);
203
204
SendMessage(hwnd, WM_SETFONT, WPARAM(m_font), TRUE);
205
206
m_text_hwnd =
207
CreateWindowEx(0, L"Static", nullptr, WS_VISIBLE | WS_CHILD, 0, 0, 0, 0, hwnd, nullptr, cs->hInstance, nullptr);
208
SendMessage(m_text_hwnd, WM_SETFONT, WPARAM(m_font), TRUE);
209
210
m_progress_hwnd = CreateWindowEx(0, PROGRESS_CLASSW, nullptr, WS_VISIBLE | WS_CHILD, 0, 0, 0, 0, hwnd, nullptr,
211
cs->hInstance, nullptr);
212
213
m_list_box_hwnd =
214
CreateWindowEx(0, L"LISTBOX", nullptr, WS_VISIBLE | WS_CHILD | WS_VSCROLL | WS_HSCROLL | WS_BORDER | LBS_NOSEL,
215
0, 0, 0, 0, hwnd, nullptr, cs->hInstance, nullptr);
216
SendMessage(m_list_box_hwnd, WM_SETFONT, WPARAM(m_font), TRUE);
217
}
218
[[fallthrough]];
219
220
case WM_SIZE:
221
{
222
RECT window_rect = {};
223
GetClientRect(m_window_hwnd, &window_rect);
224
225
const int control_width = (window_rect.right - window_rect.left) - (Scale(WINDOW_MARGIN) * 2);
226
int y = Scale(WINDOW_MARGIN);
227
228
SetWindowPos(m_text_hwnd, nullptr, Scale(WINDOW_MARGIN), y, control_width, Scale(STATUS_TEXT_HEIGHT),
229
SWP_NOZORDER | SWP_NOACTIVATE);
230
y += Scale(STATUS_TEXT_HEIGHT) + Scale(CONTROL_SPACING);
231
232
SetWindowPos(m_progress_hwnd, nullptr, Scale(WINDOW_MARGIN), y, control_width, Scale(PROGRESS_BAR_HEIGHT),
233
SWP_NOZORDER | SWP_NOACTIVATE);
234
y += Scale(PROGRESS_BAR_HEIGHT) + Scale(CONTROL_SPACING);
235
236
const int listbox_height = (window_rect.bottom - window_rect.top) - y - Scale(WINDOW_MARGIN);
237
SetWindowPos(m_list_box_hwnd, nullptr, Scale(WINDOW_MARGIN), y, control_width, listbox_height,
238
SWP_NOZORDER | SWP_NOACTIVATE);
239
}
240
break;
241
242
case WM_DPICHANGED:
243
{
244
m_dpi = HIWORD(wparam);
245
246
// Need to update the font.
247
if (m_font)
248
{
249
DeleteObject(m_font);
250
m_font = nullptr;
251
}
252
253
LOGFONTW lf = {};
254
SystemParametersInfoForDpi(SPI_GETICONTITLELOGFONT, sizeof(lf), &lf, 0, m_dpi);
255
m_font = CreateFontIndirect(&lf);
256
SendMessage(m_window_hwnd, WM_SETFONT, WPARAM(m_font), TRUE);
257
SendMessage(m_text_hwnd, WM_SETFONT, WPARAM(m_font), TRUE);
258
SendMessage(m_progress_hwnd, WM_SETFONT, WPARAM(m_font), TRUE);
259
SendMessage(m_list_box_hwnd, WM_SETFONT, WPARAM(m_font), TRUE);
260
261
// Will trigger WM_SIZE.
262
const RECT* new_rect = reinterpret_cast<RECT*>(lparam);
263
SetWindowPos(m_window_hwnd, nullptr, new_rect->left, new_rect->top, new_rect->right - new_rect->left,
264
new_rect->bottom - new_rect->top, SWP_NOZORDER | SWP_NOACTIVATE);
265
}
266
break;
267
268
default:
269
return DefWindowProc(hwnd, msg, wparam, lparam);
270
}
271
272
return 0;
273
}
274
275
int Win32ProgressCallback::Scale(int value) const
276
{
277
return MulDiv(value, m_dpi, 96);
278
}
279
280
void Win32ProgressCallback::DisplayError(const std::string_view message)
281
{
282
ERROR_LOG(message);
283
SendMessage(m_list_box_hwnd, LB_ADDSTRING, 0,
284
reinterpret_cast<LPARAM>(StringUtil::UTF8StringToWideString(message).c_str()));
285
SendMessage(m_list_box_hwnd, WM_VSCROLL, SB_BOTTOM, 0);
286
PumpMessages();
287
}
288
289
void Win32ProgressCallback::DisplayWarning(const std::string_view message)
290
{
291
WARNING_LOG(message);
292
SendMessage(m_list_box_hwnd, LB_ADDSTRING, 0,
293
reinterpret_cast<LPARAM>(StringUtil::UTF8StringToWideString(message).c_str()));
294
SendMessage(m_list_box_hwnd, WM_VSCROLL, SB_BOTTOM, 0);
295
PumpMessages();
296
}
297
298
void Win32ProgressCallback::DisplayInformation(const std::string_view message)
299
{
300
INFO_LOG(message);
301
SendMessage(m_list_box_hwnd, LB_ADDSTRING, 0,
302
reinterpret_cast<LPARAM>(StringUtil::UTF8StringToWideString(message).c_str()));
303
SendMessage(m_list_box_hwnd, WM_VSCROLL, SB_BOTTOM, 0);
304
PumpMessages();
305
}
306
307
void Win32ProgressCallback::DisplayDebugMessage(const std::string_view message)
308
{
309
DEV_LOG(message);
310
}
311
312
void Win32ProgressCallback::ModalError(const std::string_view message)
313
{
314
PumpMessages();
315
MessageBox(m_window_hwnd, StringUtil::UTF8StringToWideString(message).c_str(), L"Error", MB_ICONERROR | MB_OK);
316
PumpMessages();
317
}
318
319
bool Win32ProgressCallback::ModalConfirmation(const std::string_view message)
320
{
321
PumpMessages();
322
bool result = MessageBox(m_window_hwnd, StringUtil::UTF8StringToWideString(message).c_str(), L"Confirmation",
323
MB_ICONQUESTION | MB_YESNO) == IDYES;
324
PumpMessages();
325
return result;
326
}
327
328
void Win32ProgressCallback::ModalInformation(const std::string_view message)
329
{
330
MessageBox(m_window_hwnd, StringUtil::UTF8StringToWideString(message).c_str(), L"Information",
331
MB_ICONINFORMATION | MB_OK);
332
}
333
334