// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin // SPDX-License-Identifier: CC-BY-NC-ND-4.0 #include "win32_progress_callback.h" #include "resource.h" #include "common/log.h" #include "common/string_util.h" #include #include LOG_CHANNEL(Host); Win32ProgressCallback::Win32ProgressCallback() : ProgressCallback() { Create(); } Win32ProgressCallback::~Win32ProgressCallback() { Destroy(); } void Win32ProgressCallback::PushState() { ProgressCallback::PushState(); } void Win32ProgressCallback::PopState() { ProgressCallback::PopState(); Redraw(true); } void Win32ProgressCallback::SetCancellable(bool cancellable) { ProgressCallback::SetCancellable(cancellable); Redraw(true); } void Win32ProgressCallback::SetTitle(const std::string_view title) { SetWindowText(m_window_hwnd, StringUtil::UTF8StringToWideString(title).c_str()); } void Win32ProgressCallback::SetStatusText(const std::string_view text) { ProgressCallback::SetStatusText(text); Redraw(true); } void Win32ProgressCallback::SetProgressRange(u32 range) { ProgressCallback::SetProgressRange(range); Redraw(false); } void Win32ProgressCallback::SetProgressValue(u32 value) { ProgressCallback::SetProgressValue(value); Redraw(false); } bool Win32ProgressCallback::Create() { static constexpr LPCWSTR CLASS_NAME = L"DSWin32ProgressCallbackWindow"; static bool class_registered = false; if (!class_registered) { InitCommonControls(); WNDCLASSEX wc = {}; wc.cbSize = sizeof(WNDCLASSEX); wc.lpfnWndProc = WndProcThunk; wc.hInstance = GetModuleHandle(nullptr); wc.hIcon = LoadIcon(wc.hInstance, MAKEINTRESOURCE(IDI_ICON1)); wc.hIconSm = LoadIcon(wc.hInstance, MAKEINTRESOURCE(IDI_ICON1)); wc.hCursor = LoadCursor(NULL, IDC_WAIT); wc.hbrBackground = (HBRUSH)COLOR_WINDOW; wc.lpszClassName = CLASS_NAME; if (!RegisterClassEx(&wc)) { ERROR_LOG("Failed to register window class"); return false; } class_registered = true; } m_dpi = GetDpiForSystem(); RECT adjusted_rect = {0, 0, Scale(WINDOW_WIDTH), Scale(WINDOW_HEIGHT)}; AdjustWindowRectExForDpi(&adjusted_rect, WS_OVERLAPPEDWINDOW, FALSE, WS_EX_CLIENTEDGE, m_dpi); m_window_hwnd = CreateWindowEx(WS_EX_CLIENTEDGE, CLASS_NAME, L"DuckStation Update Installer", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, adjusted_rect.right - adjusted_rect.left, adjusted_rect.bottom - adjusted_rect.top, nullptr, nullptr, GetModuleHandle(nullptr), this); if (!m_window_hwnd) { ERROR_LOG("Failed to create window"); return false; } SetWindowLongPtr(m_window_hwnd, GWLP_USERDATA, reinterpret_cast(this)); ShowWindow(m_window_hwnd, SW_SHOW); PumpMessages(); return true; } void Win32ProgressCallback::Destroy() { if (!m_window_hwnd) return; if (m_font) { DeleteObject(m_font); m_font = nullptr; } DestroyWindow(m_window_hwnd); m_window_hwnd = {}; m_text_hwnd = {}; m_progress_hwnd = {}; } void Win32ProgressCallback::PumpMessages() { MSG msg; while (PeekMessage(&msg, m_window_hwnd, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); } } void Win32ProgressCallback::Redraw(bool force) { const int percent = static_cast((static_cast(m_progress_value) / static_cast(m_progress_range)) * 100.0f); if (percent == m_last_progress_percent && !force) { PumpMessages(); return; } m_last_progress_percent = percent; SendMessage(m_progress_hwnd, PBM_SETRANGE, 0, MAKELPARAM(0, m_progress_range)); SendMessage(m_progress_hwnd, PBM_SETPOS, static_cast(m_progress_value), 0); SetWindowText(m_text_hwnd, StringUtil::UTF8StringToWideString(m_status_text).c_str()); RedrawWindow(m_text_hwnd, nullptr, nullptr, RDW_INVALIDATE); PumpMessages(); } LRESULT CALLBACK Win32ProgressCallback::WndProcThunk(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { Win32ProgressCallback* cb; if (msg == WM_CREATE) { const CREATESTRUCT* cs = reinterpret_cast(lparam); cb = static_cast(cs->lpCreateParams); } else { cb = reinterpret_cast(GetWindowLongPtrA(hwnd, GWLP_USERDATA)); } return cb->WndProc(hwnd, msg, wparam, lparam); } LRESULT CALLBACK Win32ProgressCallback::WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { switch (msg) { case WM_CREATE: { const CREATESTRUCT* cs = reinterpret_cast(lparam); m_dpi = GetDpiForWindow(hwnd); EnableMenuItem(GetSystemMenu(hwnd, FALSE), SC_CLOSE, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED); LOGFONT lf = {}; SystemParametersInfoForDpi(SPI_GETICONTITLELOGFONT, sizeof(lf), &lf, 0, m_dpi); m_font = CreateFontIndirect(&lf); SendMessage(hwnd, WM_SETFONT, WPARAM(m_font), TRUE); m_text_hwnd = CreateWindowEx(0, L"Static", nullptr, WS_VISIBLE | WS_CHILD, 0, 0, 0, 0, hwnd, nullptr, cs->hInstance, nullptr); SendMessage(m_text_hwnd, WM_SETFONT, WPARAM(m_font), TRUE); m_progress_hwnd = CreateWindowEx(0, PROGRESS_CLASSW, nullptr, WS_VISIBLE | WS_CHILD, 0, 0, 0, 0, hwnd, nullptr, cs->hInstance, nullptr); m_list_box_hwnd = CreateWindowEx(0, L"LISTBOX", nullptr, WS_VISIBLE | WS_CHILD | WS_VSCROLL | WS_HSCROLL | WS_BORDER | LBS_NOSEL, 0, 0, 0, 0, hwnd, nullptr, cs->hInstance, nullptr); SendMessage(m_list_box_hwnd, WM_SETFONT, WPARAM(m_font), TRUE); } [[fallthrough]]; case WM_SIZE: { RECT window_rect = {}; GetClientRect(m_window_hwnd, &window_rect); const int control_width = (window_rect.right - window_rect.left) - (Scale(WINDOW_MARGIN) * 2); int y = Scale(WINDOW_MARGIN); SetWindowPos(m_text_hwnd, nullptr, Scale(WINDOW_MARGIN), y, control_width, Scale(STATUS_TEXT_HEIGHT), SWP_NOZORDER | SWP_NOACTIVATE); y += Scale(STATUS_TEXT_HEIGHT) + Scale(CONTROL_SPACING); SetWindowPos(m_progress_hwnd, nullptr, Scale(WINDOW_MARGIN), y, control_width, Scale(PROGRESS_BAR_HEIGHT), SWP_NOZORDER | SWP_NOACTIVATE); y += Scale(PROGRESS_BAR_HEIGHT) + Scale(CONTROL_SPACING); const int listbox_height = (window_rect.bottom - window_rect.top) - y - Scale(WINDOW_MARGIN); SetWindowPos(m_list_box_hwnd, nullptr, Scale(WINDOW_MARGIN), y, control_width, listbox_height, SWP_NOZORDER | SWP_NOACTIVATE); } break; case WM_DPICHANGED: { m_dpi = HIWORD(wparam); // Need to update the font. if (m_font) { DeleteObject(m_font); m_font = nullptr; } LOGFONTW lf = {}; SystemParametersInfoForDpi(SPI_GETICONTITLELOGFONT, sizeof(lf), &lf, 0, m_dpi); m_font = CreateFontIndirect(&lf); SendMessage(m_window_hwnd, WM_SETFONT, WPARAM(m_font), TRUE); SendMessage(m_text_hwnd, WM_SETFONT, WPARAM(m_font), TRUE); SendMessage(m_progress_hwnd, WM_SETFONT, WPARAM(m_font), TRUE); SendMessage(m_list_box_hwnd, WM_SETFONT, WPARAM(m_font), TRUE); // Will trigger WM_SIZE. const RECT* new_rect = reinterpret_cast(lparam); SetWindowPos(m_window_hwnd, nullptr, new_rect->left, new_rect->top, new_rect->right - new_rect->left, new_rect->bottom - new_rect->top, SWP_NOZORDER | SWP_NOACTIVATE); } break; default: return DefWindowProc(hwnd, msg, wparam, lparam); } return 0; } int Win32ProgressCallback::Scale(int value) const { return MulDiv(value, m_dpi, 96); } void Win32ProgressCallback::DisplayError(const std::string_view message) { ERROR_LOG(message); SendMessage(m_list_box_hwnd, LB_ADDSTRING, 0, reinterpret_cast(StringUtil::UTF8StringToWideString(message).c_str())); SendMessage(m_list_box_hwnd, WM_VSCROLL, SB_BOTTOM, 0); PumpMessages(); } void Win32ProgressCallback::DisplayWarning(const std::string_view message) { WARNING_LOG(message); SendMessage(m_list_box_hwnd, LB_ADDSTRING, 0, reinterpret_cast(StringUtil::UTF8StringToWideString(message).c_str())); SendMessage(m_list_box_hwnd, WM_VSCROLL, SB_BOTTOM, 0); PumpMessages(); } void Win32ProgressCallback::DisplayInformation(const std::string_view message) { INFO_LOG(message); SendMessage(m_list_box_hwnd, LB_ADDSTRING, 0, reinterpret_cast(StringUtil::UTF8StringToWideString(message).c_str())); SendMessage(m_list_box_hwnd, WM_VSCROLL, SB_BOTTOM, 0); PumpMessages(); } void Win32ProgressCallback::DisplayDebugMessage(const std::string_view message) { DEV_LOG(message); } void Win32ProgressCallback::ModalError(const std::string_view message) { PumpMessages(); MessageBox(m_window_hwnd, StringUtil::UTF8StringToWideString(message).c_str(), L"Error", MB_ICONERROR | MB_OK); PumpMessages(); } bool Win32ProgressCallback::ModalConfirmation(const std::string_view message) { PumpMessages(); bool result = MessageBox(m_window_hwnd, StringUtil::UTF8StringToWideString(message).c_str(), L"Confirmation", MB_ICONQUESTION | MB_YESNO) == IDYES; PumpMessages(); return result; } void Win32ProgressCallback::ModalInformation(const std::string_view message) { MessageBox(m_window_hwnd, StringUtil::UTF8StringToWideString(message).c_str(), L"Information", MB_ICONINFORMATION | MB_OK); }