diff --git a/apps/desktop/src/gui/application/gui_application.cpp b/apps/desktop/src/gui/application/gui_application.cpp index c4900301..7c3fdb52 100644 --- a/apps/desktop/src/gui/application/gui_application.cpp +++ b/apps/desktop/src/gui/application/gui_application.cpp @@ -40,6 +40,7 @@ #include "platform/windows/gui/slint_backend.h" #include "platform/windows/gui/tray/win_tray.h" +#include "platform/windows/gui/window_icons.h" #elif defined(__APPLE__) #include "platform/macos/gui/tray/mac_tray.h" #include "platform/window_drag.h" @@ -1013,6 +1014,9 @@ int GuiApplication::Run() { // when deciding whether the last hidden window should stop the event loop. // Keep the loop alive until the tray's explicit Exit action requests quit. ui_->main->show(); +#if _WIN32 + ConfigureWindowsWindowIcons(ui_->main->window().win32_hwnd()); +#endif slint::run_event_loop(slint::EventLoopMode::RunUntilQuit); Cleanup(); return 0; @@ -1155,6 +1159,9 @@ void GuiApplication::InitializeSystemTray() { return; } ui_->main->show(); +#if _WIN32 + ConfigureWindowsWindowIcons(ui_->main->window().win32_hwnd()); +#endif ui_->main->window().set_minimized(false); #if defined(__APPLE__) MacActivateWindow(ui_->main->window().appkit_view()); @@ -2162,6 +2169,9 @@ void GuiApplication::SyncMainWindow() { if (!ui_) { return; } +#if _WIN32 + ConfigureWindowsWindowIcons(ui_->main->window().win32_hwnd()); +#endif #if defined(__APPLE__) if (ui_->main_native_titlebar_attempts > 0) { if (HideDisabledMainWindowZoomButton()) { @@ -2451,6 +2461,9 @@ void GuiApplication::SyncStreamWindow() { if (!ui_->stream) { return; } +#if _WIN32 + ConfigureWindowsWindowIcons((*ui_->stream)->window().win32_hwnd()); +#endif (*ui_->stream)->set_window_maximized((*ui_->stream)->window().is_maximized()); #if defined(__APPLE__) if (ui_->stream_live_resize_configuration_attempts > 0) { @@ -2895,6 +2908,10 @@ void GuiApplication::SyncServerWindow() { return; } +#if _WIN32 + ConfigureWindowsWindowIcons((*ui_->server)->window().win32_hwnd()); +#endif + std::vector controllers; std::vector names; ui_->controller_ids.clear(); diff --git a/apps/desktop/src/platform/windows/gui/window_icons.cpp b/apps/desktop/src/platform/windows/gui/window_icons.cpp new file mode 100644 index 00000000..137d22d7 --- /dev/null +++ b/apps/desktop/src/platform/windows/gui/window_icons.cpp @@ -0,0 +1,100 @@ +#include "window_icons.h" + +#include + +#include +#include + +namespace crossdesk { +namespace { + +constexpr UINT_PTR kIconSubclassId = 1; + +struct WindowIcons { + HICON small_icon = nullptr; + HICON large_icon = nullptr; + + ~WindowIcons() { + if (small_icon) DestroyIcon(small_icon); + if (large_icon) DestroyIcon(large_icon); + } + + bool Load(UINT dpi) { + const HMODULE module = GetModuleHandleW(nullptr); + small_icon = static_cast(LoadImageW( + module, L"IDI_ICON1", IMAGE_ICON, + GetSystemMetricsForDpi(SM_CXSMICON, dpi), + GetSystemMetricsForDpi(SM_CYSMICON, dpi), 0)); + large_icon = static_cast(LoadImageW( + module, L"IDI_ICON1", IMAGE_ICON, + GetSystemMetricsForDpi(SM_CXICON, dpi), + GetSystemMetricsForDpi(SM_CYICON, dpi), 0)); + return small_icon && large_icon; + } + + void Apply(HWND window) const { + // Do not destroy the replaced handles: Slint/winit owns those images. + SendMessageW(window, WM_SETICON, ICON_SMALL, + reinterpret_cast(small_icon)); + SendMessageW(window, WM_SETICON, ICON_BIG, + reinterpret_cast(large_icon)); + } +}; + +LRESULT CALLBACK IconSubclassProc(HWND window, UINT message, WPARAM wparam, + LPARAM lparam, UINT_PTR subclass_id, + DWORD_PTR reference_data) { + auto* icons = reinterpret_cast(reference_data); + if (message == WM_SETICON) { + // Slint supplies the same PNG for both sizes, including during deferred + // window initialization. Keep using the matching frames from our ICO. + if (wparam == ICON_SMALL) { + lparam = reinterpret_cast(icons->small_icon); + } else if (wparam == ICON_BIG) { + lparam = reinterpret_cast(icons->large_icon); + } + return DefSubclassProc(window, message, wparam, lparam); + } + if (message == WM_DPICHANGED) { + WindowIcons next; + if (next.Load(HIWORD(wparam))) { + std::swap(icons->small_icon, next.small_icon); + std::swap(icons->large_icon, next.large_icon); + icons->Apply(window); + // 'next' releases the previous pair only after both were replaced. + } + } + if (message == WM_NCDESTROY) { + const LRESULT result = DefSubclassProc(window, message, wparam, lparam); + RemoveWindowSubclass(window, IconSubclassProc, subclass_id); + delete icons; + return result; + } + return DefSubclassProc(window, message, wparam, lparam); +} + +} // namespace + +bool ConfigureWindowsWindowIcons(HWND window) { + if (!window) { + return false; + } + DWORD_PTR existing = 0; + if (GetWindowSubclass(window, IconSubclassProc, kIconSubclassId, &existing)) { + return true; + } + auto icons = std::make_unique(); + const UINT dpi = GetDpiForWindow(window); + if (!dpi || !icons->Load(dpi) || + !SetWindowSubclass(window, IconSubclassProc, kIconSubclassId, + reinterpret_cast(icons.get()))) { + return false; + } + // The subclass owns the pair until WM_NCDESTROY, even if Slint hides and + // recreates its native window while the component itself stays alive. + auto* installed = icons.release(); + installed->Apply(window); + return true; +} + +} // namespace crossdesk diff --git a/apps/desktop/src/platform/windows/gui/window_icons.h b/apps/desktop/src/platform/windows/gui/window_icons.h new file mode 100644 index 00000000..82460cb9 --- /dev/null +++ b/apps/desktop/src/platform/windows/gui/window_icons.h @@ -0,0 +1,14 @@ +#ifndef CROSSDESK_WINDOWS_WINDOW_ICONS_H_ +#define CROSSDESK_WINDOWS_WINDOW_ICONS_H_ + +#include + +namespace crossdesk { + +// Call on the window's UI thread once its native handle exists. Repeated calls +// are harmless. Icons follow DPI changes and are released with the window. +bool ConfigureWindowsWindowIcons(HWND window); + +} // namespace crossdesk + +#endif // CROSSDESK_WINDOWS_WINDOW_ICONS_H_ diff --git a/apps/desktop/xmake/targets.lua b/apps/desktop/xmake/targets.lua index 994ab51d..9eaac106 100644 --- a/apps/desktop/xmake/targets.lua +++ b/apps/desktop/xmake/targets.lua @@ -399,8 +399,10 @@ function setup_targets() if is_os("windows") then add_cxxflags("/bigobj") add_links("opengl32") + add_syslinks("Comctl32", {public = true}) add_files("apps/desktop/src/platform/windows/gui/tray/win_tray.cpp", "apps/desktop/src/platform/windows/gui/slint_backend.cpp", + "apps/desktop/src/platform/windows/gui/window_icons.cpp", "apps/desktop/src/platform/windows/gui/slint_renderer_probe.cpp", "apps/desktop/src/platform/common/gui/opengl_video_renderer.cpp", "apps/desktop/src/platform/common/gui/cuda_driver.cpp",