Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
stenzek
GitHub Repository: stenzek/duckstation
Path: blob/master/src/installer/installer_ui.cpp
7197 views
1
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <[email protected]>
2
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
3
4
#include "installer_ui.h"
5
#include "installer.h"
6
#include "resource.h"
7
8
#include "updater/win32_progress_callback.h"
9
#include "updater/win32_window_util.h"
10
11
#include "common/error.h"
12
#include "common/scoped_guard.h"
13
#include "common/string_util.h"
14
15
#include <CommCtrl.h>
16
#include <ShlObj.h>
17
#include <combaseapi.h>
18
#include <shellscalingapi.h>
19
20
static constexpr const wchar_t* MSGBOX_TITLE = L"DuckStation Installer";
21
22
InstallerUI::InstallerUI() = default;
23
24
InstallerUI::~InstallerUI()
25
{
26
Destroy();
27
}
28
29
std::wstring InstallerUI::GetDefaultInstallDirectory() const
30
{
31
std::wstring result;
32
33
PWSTR local_app_data_path = nullptr;
34
if (SUCCEEDED(SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, nullptr, &local_app_data_path)))
35
{
36
result = local_app_data_path;
37
result += L"\\Programs\\DuckStation";
38
CoTaskMemFree(local_app_data_path);
39
}
40
else
41
{
42
result = L"C:\\Program Files\\DuckStation";
43
}
44
45
return result;
46
}
47
48
bool InstallerUI::Execute()
49
{
50
if (!Create())
51
return false;
52
53
m_running = true;
54
55
MSG msg;
56
while (m_running && GetMessageW(&msg, nullptr, 0, 0))
57
{
58
if (!IsDialogMessageW(m_window_hwnd, &msg))
59
{
60
TranslateMessage(&msg);
61
DispatchMessageW(&msg);
62
}
63
}
64
65
return true;
66
}
67
68
bool InstallerUI::Create()
69
{
70
static constexpr LPCWSTR CLASS_NAME = L"DSInstallerUIWindow";
71
static bool class_registered = false;
72
73
if (!class_registered)
74
{
75
InitCommonControls();
76
77
WNDCLASSEX wc = {};
78
wc.cbSize = sizeof(WNDCLASSEX);
79
wc.lpfnWndProc = WndProcThunk;
80
wc.hInstance = GetModuleHandle(nullptr);
81
wc.hIcon = LoadIcon(wc.hInstance, MAKEINTRESOURCE(IDI_ICON1));
82
wc.hIconSm = LoadIcon(wc.hInstance, MAKEINTRESOURCE(IDI_ICON1));
83
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
84
wc.hbrBackground = (HBRUSH)(COLOR_3DFACE + 1);
85
wc.lpszClassName = CLASS_NAME;
86
if (!RegisterClassEx(&wc))
87
{
88
MessageBoxW(nullptr, L"Failed to register window class", MSGBOX_TITLE, MB_ICONERROR | MB_OK);
89
return false;
90
}
91
92
class_registered = true;
93
}
94
95
m_dpi = GetDpiForSystem();
96
97
const DWORD window_style = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX;
98
RECT adjusted_rect = {0, 0, Scale(WINDOW_WIDTH), Scale(WINDOW_HEIGHT)};
99
AdjustWindowRectExForDpi(&adjusted_rect, window_style, FALSE, 0, m_dpi);
100
101
const int window_width = adjusted_rect.right - adjusted_rect.left;
102
const int window_height = adjusted_rect.bottom - adjusted_rect.top;
103
104
m_window_hwnd = CreateWindowExW(0, CLASS_NAME, L"DuckStation Installer", window_style, CW_USEDEFAULT, CW_USEDEFAULT,
105
window_width, window_height, nullptr, nullptr, GetModuleHandle(nullptr), this);
106
if (!m_window_hwnd)
107
{
108
MessageBoxW(nullptr, L"Failed to create window", MSGBOX_TITLE, MB_ICONERROR | MB_OK);
109
return false;
110
}
111
112
SetWindowLongPtr(m_window_hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this));
113
Win32WindowUtil::CenterWindowOnMonitorAtCursorPosition(m_window_hwnd);
114
ShowWindow(m_window_hwnd, SW_SHOW);
115
UpdateWindow(m_window_hwnd);
116
return true;
117
}
118
119
void InstallerUI::Destroy()
120
{
121
if (m_heading_font)
122
{
123
DeleteObject(m_heading_font);
124
m_heading_font = nullptr;
125
}
126
127
if (m_font)
128
{
129
DeleteObject(m_font);
130
m_font = nullptr;
131
}
132
133
if (m_logo_icon)
134
{
135
DestroyIcon(m_logo_icon);
136
m_logo_icon = nullptr;
137
}
138
139
if (m_background_brush)
140
{
141
DeleteObject(m_background_brush);
142
m_background_brush = nullptr;
143
}
144
145
if (m_window_hwnd)
146
{
147
DestroyWindow(m_window_hwnd);
148
m_window_hwnd = nullptr;
149
}
150
}
151
152
void InstallerUI::OnBrowseClicked()
153
{
154
BROWSEINFOW bi = {};
155
bi.hwndOwner = m_window_hwnd;
156
bi.lpszTitle = L"Select Installation Directory";
157
bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE | BIF_USENEWUI;
158
159
LPITEMIDLIST pidl = SHBrowseForFolderW(&bi);
160
if (pidl)
161
{
162
WCHAR path[MAX_PATH];
163
if (SHGetPathFromIDListW(pidl, path))
164
{
165
std::wstring destination_directory = path;
166
destination_directory += L"\\DuckStation";
167
SetWindowTextW(m_destination_edit_hwnd, destination_directory.c_str());
168
}
169
CoTaskMemFree(pidl);
170
}
171
}
172
173
void InstallerUI::OnInstallClicked()
174
{
175
std::wstring destination_directory;
176
177
// Get the current text from the edit control
178
const int text_len = GetWindowTextLengthW(m_destination_edit_hwnd);
179
if (text_len > 0)
180
{
181
destination_directory.resize(static_cast<size_t>(text_len) + 1);
182
GetWindowTextW(m_destination_edit_hwnd, destination_directory.data(), text_len + 1);
183
destination_directory.resize(static_cast<size_t>(text_len));
184
}
185
186
if (destination_directory.empty())
187
return;
188
189
// Check if the directory is not empty and warn the user
190
const std::string destination_directory_utf8 = StringUtil::WideStringToUTF8String(destination_directory);
191
if (!Installer::CheckForEmptyDirectory(destination_directory_utf8))
192
{
193
if (MessageBoxW(m_window_hwnd,
194
L"The selected directory is not empty. Files may be overwritten.\n\nDo you want to continue?",
195
MSGBOX_TITLE, MB_ICONWARNING | MB_YESNO) == IDNO)
196
{
197
return;
198
}
199
}
200
201
if (!DoInstall(destination_directory_utf8))
202
{
203
// allow user to try again
204
return;
205
}
206
207
// Ask user if they want to launch the application
208
if (MessageBoxW(m_window_hwnd, L"Do you want to launch DuckStation now?", MSGBOX_TITLE, MB_ICONQUESTION | MB_YESNO) ==
209
IDYES)
210
{
211
Error error;
212
if (!Installer::LaunchApplication(destination_directory_utf8, &error))
213
{
214
MessageBoxW(m_window_hwnd, StringUtil::UTF8StringToWideString(error.GetDescription()).c_str(), MSGBOX_TITLE,
215
MB_ICONERROR | MB_OK);
216
}
217
}
218
219
m_running = false;
220
}
221
222
bool InstallerUI::DoInstall(const std::string& destination_directory_utf8)
223
{
224
// Get checkbox states
225
const bool create_start_menu_shortcut = (SendMessage(m_start_menu_checkbox_hwnd, BM_GETCHECK, 0, 0) == BST_CHECKED);
226
const bool create_desktop_shortcut = (SendMessage(m_desktop_checkbox_hwnd, BM_GETCHECK, 0, 0) == BST_CHECKED);
227
228
Win32ProgressCallback progress(m_window_hwnd);
229
Installer installer(&progress, destination_directory_utf8);
230
if (!installer.Install())
231
return false;
232
233
// Create shortcuts if requested
234
if (create_start_menu_shortcut)
235
installer.CreateStartMenuShortcut();
236
if (create_desktop_shortcut)
237
installer.CreateDesktopShortcut();
238
239
progress.ModalInformation("Installation completed successfully!");
240
return true;
241
}
242
243
void InstallerUI::OnCancelClicked()
244
{
245
m_running = false;
246
}
247
248
void InstallerUI::OnDestinationDirectoryChanged()
249
{
250
const int text_len = GetWindowTextLengthW(m_destination_edit_hwnd);
251
EnableWindow(m_install_button_hwnd, text_len > 0);
252
}
253
254
LRESULT CALLBACK InstallerUI::WndProcThunk(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
255
{
256
InstallerUI* ui;
257
if (msg == WM_CREATE)
258
{
259
const CREATESTRUCT* cs = reinterpret_cast<CREATESTRUCT*>(lparam);
260
ui = static_cast<InstallerUI*>(cs->lpCreateParams);
261
SetWindowLongPtrW(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(ui));
262
}
263
else
264
{
265
ui = reinterpret_cast<InstallerUI*>(GetWindowLongPtrW(hwnd, GWLP_USERDATA));
266
}
267
268
if (ui)
269
return ui->WndProc(hwnd, msg, wparam, lparam);
270
else
271
return DefWindowProc(hwnd, msg, wparam, lparam);
272
}
273
274
LRESULT CALLBACK InstallerUI::WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
275
{
276
switch (msg)
277
{
278
case WM_CREATE:
279
{
280
const CREATESTRUCT* cs = reinterpret_cast<CREATESTRUCT*>(lparam);
281
m_dpi = GetDpiForWindow(hwnd);
282
283
// Create background brush for consistent control backgrounds
284
m_background_brush = CreateSolidBrush(GetSysColor(COLOR_3DFACE));
285
286
// Create fonts
287
LOGFONTW lf = {};
288
SystemParametersInfoForDpi(SPI_GETICONTITLELOGFONT, sizeof(lf), &lf, 0, m_dpi);
289
m_font = CreateFontIndirectW(&lf);
290
291
// Create a larger font for the heading
292
lf.lfHeight = -MulDiv(16, m_dpi, 72);
293
lf.lfWeight = FW_BOLD;
294
m_heading_font = CreateFontIndirectW(&lf);
295
296
// Load the logo icon
297
m_logo_icon = reinterpret_cast<HICON>(
298
LoadImageW(cs->hInstance, MAKEINTRESOURCEW(IDI_ICON1), IMAGE_ICON, Scale(LOGO_SIZE), Scale(LOGO_SIZE), 0));
299
300
// Create logo static control
301
m_logo_hwnd = CreateWindowExW(0, L"Static", nullptr, WS_VISIBLE | WS_CHILD | SS_ICON, 0, 0, 0, 0, hwnd, nullptr,
302
cs->hInstance, nullptr);
303
SendMessageW(m_logo_hwnd, STM_SETICON, reinterpret_cast<WPARAM>(m_logo_icon), 0);
304
305
// Create heading label
306
m_heading_hwnd = CreateWindowExW(0, L"Static", L"DuckStation Installer", WS_VISIBLE | WS_CHILD | SS_LEFT, 0, 0, 0,
307
0, hwnd, nullptr, cs->hInstance, nullptr);
308
SendMessageW(m_heading_hwnd, WM_SETFONT, reinterpret_cast<WPARAM>(m_heading_font), TRUE);
309
310
// Create info text
311
m_info_text_hwnd =
312
CreateWindowExW(0, L"Static",
313
L"This program will install DuckStation on your computer. Choose the destination folder and "
314
L"click Install to continue.",
315
WS_VISIBLE | WS_CHILD | SS_LEFT, 0, 0, 0, 0, hwnd, nullptr, cs->hInstance, nullptr);
316
SendMessageW(m_info_text_hwnd, WM_SETFONT, reinterpret_cast<WPARAM>(m_font), TRUE);
317
318
// Create destination label
319
m_destination_label_hwnd =
320
CreateWindowExW(0, L"Static", L"Installation Directory:", WS_VISIBLE | WS_CHILD | SS_LEFT, 0, 0, 0, 0, hwnd,
321
nullptr, cs->hInstance, nullptr);
322
SendMessageW(m_destination_label_hwnd, WM_SETFONT, reinterpret_cast<WPARAM>(m_font), TRUE);
323
324
// Create destination edit control
325
m_destination_edit_hwnd =
326
CreateWindowExW(WS_EX_CLIENTEDGE, L"Edit", GetDefaultInstallDirectory().c_str(),
327
WS_VISIBLE | WS_CHILD | WS_TABSTOP | ES_AUTOHSCROLL, 0, 0, 0, 0, hwnd,
328
reinterpret_cast<HMENU>(static_cast<INT_PTR>(IDC_DESTINATION_EDIT)), cs->hInstance, nullptr);
329
SendMessageW(m_destination_edit_hwnd, WM_SETFONT, reinterpret_cast<WPARAM>(m_font), TRUE);
330
331
// Create browse button
332
m_browse_button_hwnd =
333
CreateWindowExW(0, L"Button", L"Browse...", WS_VISIBLE | WS_CHILD | WS_TABSTOP | BS_PUSHBUTTON, 0, 0, 0, 0,
334
hwnd, reinterpret_cast<HMENU>(static_cast<INT_PTR>(IDC_BROWSE_BUTTON)), cs->hInstance, nullptr);
335
SendMessageW(m_browse_button_hwnd, WM_SETFONT, reinterpret_cast<WPARAM>(m_font), TRUE);
336
337
// Create start menu checkbox
338
m_start_menu_checkbox_hwnd = CreateWindowExW(
339
0, L"Button", L"Create Start Menu shortcut", WS_VISIBLE | WS_CHILD | WS_TABSTOP | BS_AUTOCHECKBOX, 0, 0, 0, 0,
340
hwnd, reinterpret_cast<HMENU>(static_cast<INT_PTR>(IDC_START_MENU_CHECKBOX)), cs->hInstance, nullptr);
341
SendMessageW(m_start_menu_checkbox_hwnd, WM_SETFONT, reinterpret_cast<WPARAM>(m_font), TRUE);
342
SendMessageW(m_start_menu_checkbox_hwnd, BM_SETCHECK, BST_CHECKED, 0);
343
344
// Create desktop checkbox
345
m_desktop_checkbox_hwnd = CreateWindowExW(
346
0, L"Button", L"Create Desktop shortcut", WS_VISIBLE | WS_CHILD | WS_TABSTOP | BS_AUTOCHECKBOX, 0, 0, 0, 0,
347
hwnd, reinterpret_cast<HMENU>(static_cast<INT_PTR>(IDC_DESKTOP_CHECKBOX)), cs->hInstance, nullptr);
348
SendMessageW(m_desktop_checkbox_hwnd, WM_SETFONT, reinterpret_cast<WPARAM>(m_font), TRUE);
349
SendMessageW(m_desktop_checkbox_hwnd, BM_SETCHECK, BST_UNCHECKED, 0);
350
351
// Create install button
352
m_install_button_hwnd = CreateWindowExW(
353
0, L"Button", L"Install", WS_VISIBLE | WS_CHILD | WS_TABSTOP | BS_DEFPUSHBUTTON, 0, 0, 0, 0, hwnd,
354
reinterpret_cast<HMENU>(static_cast<INT_PTR>(IDC_INSTALL_BUTTON)), cs->hInstance, nullptr);
355
SendMessageW(m_install_button_hwnd, WM_SETFONT, reinterpret_cast<WPARAM>(m_font), TRUE);
356
357
// Create cancel button
358
m_cancel_button_hwnd =
359
CreateWindowExW(0, L"Button", L"Cancel", WS_VISIBLE | WS_CHILD | WS_TABSTOP | BS_PUSHBUTTON, 0, 0, 0, 0, hwnd,
360
reinterpret_cast<HMENU>(static_cast<INT_PTR>(IDC_CANCEL_BUTTON)), cs->hInstance, nullptr);
361
SendMessageW(m_cancel_button_hwnd, WM_SETFONT, reinterpret_cast<WPARAM>(m_font), TRUE);
362
}
363
[[fallthrough]];
364
365
case WM_SIZE:
366
{
367
RECT client_rect = {};
368
GetClientRect(hwnd, &client_rect);
369
370
const int client_width = client_rect.right - client_rect.left;
371
const int margin = Scale(WINDOW_MARGIN);
372
const int control_spacing = Scale(CONTROL_SPACING);
373
const int content_width = client_width - (margin * 2);
374
375
int y = margin;
376
377
// Logo and heading on the same row
378
const int logo_size = Scale(LOGO_SIZE);
379
SetWindowPos(m_logo_hwnd, nullptr, margin, y, logo_size, logo_size, SWP_NOZORDER | SWP_NOACTIVATE);
380
381
const int heading_x = margin + logo_size + control_spacing;
382
const int heading_width = content_width - logo_size - control_spacing;
383
const int heading_height = Scale(HEADING_HEIGHT);
384
SetWindowPos(m_heading_hwnd, nullptr, heading_x, y + (logo_size - heading_height) / 2, heading_width,
385
heading_height, SWP_NOZORDER | SWP_NOACTIVATE);
386
387
y += logo_size + control_spacing;
388
389
// Info text
390
const int info_height = Scale(INFO_TEXT_HEIGHT);
391
SetWindowPos(m_info_text_hwnd, nullptr, margin, y, content_width, info_height, SWP_NOZORDER | SWP_NOACTIVATE);
392
y += info_height + control_spacing;
393
394
// Destination label
395
const int label_height = Scale(16);
396
SetWindowPos(m_destination_label_hwnd, nullptr, margin, y, content_width, label_height,
397
SWP_NOZORDER | SWP_NOACTIVATE);
398
y += label_height + control_spacing / 2;
399
400
// Destination edit and browse button
401
const int edit_height = Scale(EDIT_HEIGHT);
402
const int browse_width = Scale(BUTTON_WIDTH);
403
const int edit_width = content_width - browse_width - control_spacing;
404
SetWindowPos(m_destination_edit_hwnd, nullptr, margin, y, edit_width, edit_height, SWP_NOZORDER | SWP_NOACTIVATE);
405
SetWindowPos(m_browse_button_hwnd, nullptr, margin + edit_width + control_spacing, y, browse_width, edit_height,
406
SWP_NOZORDER | SWP_NOACTIVATE);
407
y += edit_height + control_spacing;
408
409
// Checkboxes
410
const int checkbox_height = Scale(CHECKBOX_HEIGHT);
411
SetWindowPos(m_start_menu_checkbox_hwnd, nullptr, margin, y, content_width, checkbox_height,
412
SWP_NOZORDER | SWP_NOACTIVATE);
413
y += checkbox_height + control_spacing / 2;
414
415
SetWindowPos(m_desktop_checkbox_hwnd, nullptr, margin, y, content_width, checkbox_height,
416
SWP_NOZORDER | SWP_NOACTIVATE);
417
y += checkbox_height + control_spacing;
418
419
// Install and Cancel buttons at the bottom right
420
const int button_width = Scale(BUTTON_WIDTH);
421
const int button_height = Scale(BUTTON_HEIGHT);
422
const int button_y = client_rect.bottom - margin - button_height;
423
const int cancel_x = client_width - margin - button_width;
424
const int install_x = cancel_x - control_spacing - button_width;
425
426
SetWindowPos(m_install_button_hwnd, nullptr, install_x, button_y, button_width, button_height,
427
SWP_NOZORDER | SWP_NOACTIVATE);
428
SetWindowPos(m_cancel_button_hwnd, nullptr, cancel_x, button_y, button_width, button_height,
429
SWP_NOZORDER | SWP_NOACTIVATE);
430
}
431
break;
432
433
case WM_CTLCOLORSTATIC:
434
{
435
HDC hdc = reinterpret_cast<HDC>(wparam);
436
SetBkColor(hdc, GetSysColor(COLOR_3DFACE));
437
return reinterpret_cast<LRESULT>(m_background_brush);
438
}
439
440
case WM_COMMAND:
441
{
442
const int control_id = LOWORD(wparam);
443
const int notification = HIWORD(wparam);
444
445
if (control_id == IDCANCEL)
446
{
447
OnCancelClicked();
448
}
449
else if (notification == BN_CLICKED)
450
{
451
switch (control_id)
452
{
453
case IDC_BROWSE_BUTTON:
454
OnBrowseClicked();
455
break;
456
case IDC_INSTALL_BUTTON:
457
OnInstallClicked();
458
break;
459
case IDC_CANCEL_BUTTON:
460
OnCancelClicked();
461
break;
462
}
463
}
464
else if (control_id == IDC_DESTINATION_EDIT && notification == EN_CHANGE)
465
{
466
OnDestinationDirectoryChanged();
467
}
468
}
469
break;
470
471
case DM_GETDEFID:
472
return MAKELRESULT(IDC_INSTALL_BUTTON, DC_HASDEFID);
473
474
case WM_CLOSE:
475
OnCancelClicked();
476
return 0;
477
478
case WM_DESTROY:
479
PostQuitMessage(0);
480
return 0;
481
482
case WM_DPICHANGED:
483
{
484
m_dpi = HIWORD(wparam);
485
486
// Recreate fonts
487
if (m_font)
488
{
489
DeleteObject(m_font);
490
m_font = nullptr;
491
}
492
if (m_heading_font)
493
{
494
DeleteObject(m_heading_font);
495
m_heading_font = nullptr;
496
}
497
498
LOGFONTW lf = {};
499
SystemParametersInfoForDpi(SPI_GETICONTITLELOGFONT, sizeof(lf), &lf, 0, m_dpi);
500
m_font = CreateFontIndirectW(&lf);
501
502
lf.lfHeight = -MulDiv(16, m_dpi, 72);
503
lf.lfWeight = FW_BOLD;
504
m_heading_font = CreateFontIndirectW(&lf);
505
506
// Update fonts on controls
507
SendMessageW(m_heading_hwnd, WM_SETFONT, reinterpret_cast<WPARAM>(m_heading_font), TRUE);
508
SendMessageW(m_info_text_hwnd, WM_SETFONT, reinterpret_cast<WPARAM>(m_font), TRUE);
509
SendMessageW(m_destination_label_hwnd, WM_SETFONT, reinterpret_cast<WPARAM>(m_font), TRUE);
510
SendMessageW(m_destination_edit_hwnd, WM_SETFONT, reinterpret_cast<WPARAM>(m_font), TRUE);
511
SendMessageW(m_browse_button_hwnd, WM_SETFONT, reinterpret_cast<WPARAM>(m_font), TRUE);
512
SendMessageW(m_start_menu_checkbox_hwnd, WM_SETFONT, reinterpret_cast<WPARAM>(m_font), TRUE);
513
SendMessageW(m_desktop_checkbox_hwnd, WM_SETFONT, reinterpret_cast<WPARAM>(m_font), TRUE);
514
SendMessageW(m_install_button_hwnd, WM_SETFONT, reinterpret_cast<WPARAM>(m_font), TRUE);
515
SendMessageW(m_cancel_button_hwnd, WM_SETFONT, reinterpret_cast<WPARAM>(m_font), TRUE);
516
517
// Reload icon at new size
518
if (m_logo_icon)
519
{
520
DestroyIcon(m_logo_icon);
521
m_logo_icon = reinterpret_cast<HICON>(LoadImageW(GetModuleHandle(nullptr), MAKEINTRESOURCEW(IDI_ICON1),
522
IMAGE_ICON, Scale(LOGO_SIZE), Scale(LOGO_SIZE), 0));
523
SendMessageW(m_logo_hwnd, STM_SETICON, reinterpret_cast<WPARAM>(m_logo_icon), 0);
524
}
525
526
// Resize window
527
const RECT* new_rect = reinterpret_cast<RECT*>(lparam);
528
SetWindowPos(hwnd, nullptr, new_rect->left, new_rect->top, new_rect->right - new_rect->left,
529
new_rect->bottom - new_rect->top, SWP_NOZORDER | SWP_NOACTIVATE);
530
}
531
break;
532
533
default:
534
return DefWindowProc(hwnd, msg, wparam, lparam);
535
}
536
537
return 0;
538
}
539
540
int InstallerUI::Scale(int value) const
541
{
542
return MulDiv(value, m_dpi, 96);
543
}
544
545
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nShowCmd)
546
{
547
// Shell dialogs (SHBrowseForFolder) require single-threaded apartment mode
548
const bool com_initialized = SUCCEEDED(CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED));
549
const ScopedGuard com_guard = [com_initialized]() {
550
if (com_initialized)
551
CoUninitialize();
552
};
553
554
InstallerUI ui;
555
return ui.Execute() ? EXIT_SUCCESS : EXIT_FAILURE;
556
}
557
558