Path: blob/master/src/updater/win32_progress_callback.cpp
7365 views
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <[email protected]>1// SPDX-License-Identifier: CC-BY-NC-ND-4.023#include "win32_progress_callback.h"4#include "resource.h"5#include "win32_window_util.h"67#include "common/log.h"8#include "common/string_util.h"910#include <CommCtrl.h>11#include <shellscalingapi.h>1213LOG_CHANNEL(Host);1415Win32ProgressCallback::Win32ProgressCallback(HWND parent_hwnd) : UpdaterProgressCallback(), m_parent_hwnd(parent_hwnd)16{17Create();18}1920Win32ProgressCallback::~Win32ProgressCallback()21{22Destroy();23}2425void Win32ProgressCallback::PushState()26{27UpdaterProgressCallback::PushState();28}2930void Win32ProgressCallback::PopState()31{32UpdaterProgressCallback::PopState();33Redraw(true);34}3536void Win32ProgressCallback::SetCancellable(bool cancellable)37{38UpdaterProgressCallback::SetCancellable(cancellable);39Redraw(true);40}4142void Win32ProgressCallback::SetTitle(const std::string_view title)43{44SetWindowText(m_window_hwnd, StringUtil::UTF8StringToWideString(title).c_str());45}4647void Win32ProgressCallback::SetStatusText(const std::string_view text)48{49UpdaterProgressCallback::SetStatusText(text);50Redraw(true);51}5253void Win32ProgressCallback::SetProgressRange(u32 range)54{55UpdaterProgressCallback::SetProgressRange(range);56Redraw(false);57}5859void Win32ProgressCallback::SetProgressValue(u32 value)60{61UpdaterProgressCallback::SetProgressValue(value);62Redraw(false);63}6465bool Win32ProgressCallback::Create()66{67static constexpr LPCWSTR CLASS_NAME = L"DSWin32ProgressCallbackWindow";68static bool class_registered = false;6970if (!class_registered)71{72InitCommonControls();7374WNDCLASSEX wc = {};75wc.cbSize = sizeof(WNDCLASSEX);76wc.lpfnWndProc = WndProcThunk;77wc.hInstance = GetModuleHandle(nullptr);78wc.hIcon = LoadIcon(wc.hInstance, MAKEINTRESOURCE(IDI_ICON1));79wc.hIconSm = LoadIcon(wc.hInstance, MAKEINTRESOURCE(IDI_ICON1));80wc.hCursor = LoadCursor(NULL, IDC_WAIT);81wc.hbrBackground = (HBRUSH)COLOR_WINDOW;82wc.lpszClassName = CLASS_NAME;83if (!RegisterClassEx(&wc))84{85ERROR_LOG("Failed to register window class");86return false;87}8889class_registered = true;90}9192m_dpi = GetDpiForSystem();9394RECT adjusted_rect = {0, 0, Scale(WINDOW_WIDTH), Scale(WINDOW_HEIGHT)};95AdjustWindowRectExForDpi(&adjusted_rect, WS_OVERLAPPEDWINDOW, FALSE, WS_EX_CLIENTEDGE, m_dpi);9697const int window_width = adjusted_rect.right - adjusted_rect.left;98const int window_height = adjusted_rect.bottom - adjusted_rect.top;99100m_window_hwnd =101CreateWindowEx(WS_EX_CLIENTEDGE, CLASS_NAME, L"DuckStation Update Installer", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,102CW_USEDEFAULT, window_width, window_height, m_parent_hwnd, nullptr, GetModuleHandle(nullptr), this);103if (!m_window_hwnd)104{105ERROR_LOG("Failed to create window");106return false;107}108109// Disable parent window to make this modal110if (m_parent_hwnd)111EnableWindow(m_parent_hwnd, FALSE);112113SetWindowLongPtr(m_window_hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this));114Win32WindowUtil::CenterWindowOnMonitorAtCursorPosition(m_window_hwnd);115ShowWindow(m_window_hwnd, SW_SHOW);116PumpMessages();117return true;118}119120void Win32ProgressCallback::Destroy()121{122if (!m_window_hwnd)123return;124125if (m_font)126{127DeleteObject(m_font);128m_font = nullptr;129}130131DestroyWindow(m_window_hwnd);132m_window_hwnd = {};133m_text_hwnd = {};134m_progress_hwnd = {};135136// Re-enable and restore focus to parent window137if (m_parent_hwnd)138{139EnableWindow(m_parent_hwnd, TRUE);140SetForegroundWindow(m_parent_hwnd);141}142}143144void Win32ProgressCallback::PumpMessages()145{146MSG msg;147while (PeekMessage(&msg, m_window_hwnd, 0, 0, PM_REMOVE))148{149TranslateMessage(&msg);150DispatchMessage(&msg);151}152}153154void Win32ProgressCallback::Redraw(bool force)155{156const int percent =157static_cast<int>((static_cast<float>(m_progress_value) / static_cast<float>(m_progress_range)) * 100.0f);158if (percent == m_last_progress_percent && !force)159{160PumpMessages();161return;162}163164m_last_progress_percent = percent;165166SendMessage(m_progress_hwnd, PBM_SETRANGE, 0, MAKELPARAM(0, m_progress_range));167SendMessage(m_progress_hwnd, PBM_SETPOS, static_cast<WPARAM>(m_progress_value), 0);168SetWindowText(m_text_hwnd, StringUtil::UTF8StringToWideString(m_status_text).c_str());169RedrawWindow(m_text_hwnd, nullptr, nullptr, RDW_INVALIDATE);170PumpMessages();171}172173LRESULT CALLBACK Win32ProgressCallback::WndProcThunk(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)174{175Win32ProgressCallback* cb;176if (msg == WM_CREATE)177{178const CREATESTRUCT* cs = reinterpret_cast<CREATESTRUCT*>(lparam);179cb = static_cast<Win32ProgressCallback*>(cs->lpCreateParams);180}181else182{183cb = reinterpret_cast<Win32ProgressCallback*>(GetWindowLongPtrA(hwnd, GWLP_USERDATA));184}185186return cb->WndProc(hwnd, msg, wparam, lparam);187}188189LRESULT CALLBACK Win32ProgressCallback::WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)190{191switch (msg)192{193case WM_CREATE:194{195const CREATESTRUCT* cs = reinterpret_cast<CREATESTRUCT*>(lparam);196m_dpi = GetDpiForWindow(hwnd);197EnableMenuItem(GetSystemMenu(hwnd, FALSE), SC_CLOSE, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);198199LOGFONT lf = {};200SystemParametersInfoForDpi(SPI_GETICONTITLELOGFONT, sizeof(lf), &lf, 0, m_dpi);201m_font = CreateFontIndirect(&lf);202203SendMessage(hwnd, WM_SETFONT, WPARAM(m_font), TRUE);204205m_text_hwnd =206CreateWindowEx(0, L"Static", nullptr, WS_VISIBLE | WS_CHILD, 0, 0, 0, 0, hwnd, nullptr, cs->hInstance, nullptr);207SendMessage(m_text_hwnd, WM_SETFONT, WPARAM(m_font), TRUE);208209m_progress_hwnd = CreateWindowEx(0, PROGRESS_CLASSW, nullptr, WS_VISIBLE | WS_CHILD, 0, 0, 0, 0, hwnd, nullptr,210cs->hInstance, nullptr);211212m_list_box_hwnd =213CreateWindowEx(0, L"LISTBOX", nullptr, WS_VISIBLE | WS_CHILD | WS_VSCROLL | WS_HSCROLL | WS_BORDER | LBS_NOSEL,2140, 0, 0, 0, hwnd, nullptr, cs->hInstance, nullptr);215SendMessage(m_list_box_hwnd, WM_SETFONT, WPARAM(m_font), TRUE);216}217[[fallthrough]];218219case WM_SIZE:220{221RECT window_rect = {};222GetClientRect(m_window_hwnd, &window_rect);223224const int control_width = (window_rect.right - window_rect.left) - (Scale(WINDOW_MARGIN) * 2);225int y = Scale(WINDOW_MARGIN);226227SetWindowPos(m_text_hwnd, nullptr, Scale(WINDOW_MARGIN), y, control_width, Scale(STATUS_TEXT_HEIGHT),228SWP_NOZORDER | SWP_NOACTIVATE);229y += Scale(STATUS_TEXT_HEIGHT) + Scale(CONTROL_SPACING);230231SetWindowPos(m_progress_hwnd, nullptr, Scale(WINDOW_MARGIN), y, control_width, Scale(PROGRESS_BAR_HEIGHT),232SWP_NOZORDER | SWP_NOACTIVATE);233y += Scale(PROGRESS_BAR_HEIGHT) + Scale(CONTROL_SPACING);234235const int listbox_height = (window_rect.bottom - window_rect.top) - y - Scale(WINDOW_MARGIN);236SetWindowPos(m_list_box_hwnd, nullptr, Scale(WINDOW_MARGIN), y, control_width, listbox_height,237SWP_NOZORDER | SWP_NOACTIVATE);238}239break;240241case WM_DPICHANGED:242{243m_dpi = HIWORD(wparam);244245// Need to update the font.246if (m_font)247{248DeleteObject(m_font);249m_font = nullptr;250}251252LOGFONTW lf = {};253SystemParametersInfoForDpi(SPI_GETICONTITLELOGFONT, sizeof(lf), &lf, 0, m_dpi);254m_font = CreateFontIndirect(&lf);255SendMessage(m_window_hwnd, WM_SETFONT, WPARAM(m_font), TRUE);256SendMessage(m_text_hwnd, WM_SETFONT, WPARAM(m_font), TRUE);257SendMessage(m_progress_hwnd, WM_SETFONT, WPARAM(m_font), TRUE);258SendMessage(m_list_box_hwnd, WM_SETFONT, WPARAM(m_font), TRUE);259260// Will trigger WM_SIZE.261const RECT* new_rect = reinterpret_cast<RECT*>(lparam);262SetWindowPos(m_window_hwnd, nullptr, new_rect->left, new_rect->top, new_rect->right - new_rect->left,263new_rect->bottom - new_rect->top, SWP_NOZORDER | SWP_NOACTIVATE);264}265break;266267default:268return DefWindowProc(hwnd, msg, wparam, lparam);269}270271return 0;272}273274int Win32ProgressCallback::Scale(int value) const275{276return MulDiv(value, m_dpi, 96);277}278279void Win32ProgressCallback::DisplayError(const std::string_view message)280{281ERROR_LOG(message);282SendMessage(m_list_box_hwnd, LB_ADDSTRING, 0,283reinterpret_cast<LPARAM>(StringUtil::UTF8StringToWideString(message).c_str()));284SendMessage(m_list_box_hwnd, WM_VSCROLL, SB_BOTTOM, 0);285PumpMessages();286}287288void Win32ProgressCallback::DisplayWarning(const std::string_view message)289{290WARNING_LOG(message);291SendMessage(m_list_box_hwnd, LB_ADDSTRING, 0,292reinterpret_cast<LPARAM>(StringUtil::UTF8StringToWideString(message).c_str()));293SendMessage(m_list_box_hwnd, WM_VSCROLL, SB_BOTTOM, 0);294PumpMessages();295}296297void Win32ProgressCallback::DisplayInformation(const std::string_view message)298{299INFO_LOG(message);300SendMessage(m_list_box_hwnd, LB_ADDSTRING, 0,301reinterpret_cast<LPARAM>(StringUtil::UTF8StringToWideString(message).c_str()));302SendMessage(m_list_box_hwnd, WM_VSCROLL, SB_BOTTOM, 0);303PumpMessages();304}305306void Win32ProgressCallback::DisplayDebugMessage(const std::string_view message)307{308DEV_LOG(message);309}310311void Win32ProgressCallback::ModalError(const std::string_view message)312{313PumpMessages();314MessageBox(m_window_hwnd, StringUtil::UTF8StringToWideString(message).c_str(), L"Error", MB_ICONERROR | MB_OK);315PumpMessages();316}317318bool Win32ProgressCallback::ModalConfirmation(const std::string_view message)319{320PumpMessages();321bool result = MessageBox(m_window_hwnd, StringUtil::UTF8StringToWideString(message).c_str(), L"Confirmation",322MB_ICONQUESTION | MB_YESNO) == IDYES;323PumpMessages();324return result;325}326327void Win32ProgressCallback::ModalInformation(const std::string_view message)328{329MessageBox(m_window_hwnd, StringUtil::UTF8StringToWideString(message).c_str(), L"Information",330MB_ICONINFORMATION | MB_OK);331}332333334