[feat] separate Windows taskbar groups for main and remote control windows, refs #148

This commit is contained in:
dijunkun
2026-11-11 00:04:40 +08:00
parent 57c569424e
commit ed58172d2e
6 changed files with 105 additions and 0 deletions
@@ -62,6 +62,7 @@
#include "platform/windows/gui/shutdown_session_guard.h"
#include "platform/windows/gui/tray/win_tray.h"
#include "platform/windows/gui/window_icons.h"
#include "platform/windows/gui/window_taskbar.h"
#include "platform/windows/service/autostart_control.h"
#include "platform/windows/service/interactive_desktop.h"
#include "platform/windows/service/unattended_config.h"
@@ -3882,6 +3883,11 @@ void GuiApplication::SyncStreamWindow() {
if (!ui_->stream) {
return;
}
// Assign the window's taskbar group before its first show or restoration.
// Reapply when Slint recreates the HWND; the helper skips configured handles.
#if _WIN32
ConfigureWindowsStreamTaskbar((*ui_->stream)->window().win32_hwnd());
#endif
// A wall takeover hides an existing stream window without destroying it.
// Opening a recent device must also restore that existing window.
if (need_to_create_stream_window_) {
@@ -0,0 +1,79 @@
#include "window_taskbar.h"
#include <commctrl.h>
#include <propkey.h>
#include <propsys.h>
#include <propvarutil.h>
#include <shellapi.h>
#include <wrl/client.h>
namespace crossdesk {
namespace {
constexpr wchar_t kStreamAppId[] = L"CrossDesk.RemoteControl";
constexpr UINT_PTR kTaskbarSubclassId = 1;
void ClearTaskbarProperties(IPropertyStore* properties) {
const PROPVARIANT empty{};
properties->SetValue(PKEY_AppUserModel_ID, empty);
properties->SetValue(PKEY_AppUserModel_PreventPinning, empty);
}
LRESULT CALLBACK TaskbarSubclassProc(HWND window, UINT message, WPARAM wparam,
LPARAM lparam, UINT_PTR subclass_id,
DWORD_PTR reference_data) {
auto* properties = reinterpret_cast<IPropertyStore*>(reference_data);
if (message == WM_DESTROY) {
// Window property stores require explicit clearing before destruction.
ClearTaskbarProperties(properties);
}
if (message == WM_NCDESTROY) {
RemoveWindowSubclass(window, TaskbarSubclassProc, subclass_id);
properties->Release();
}
return DefSubclassProc(window, message, wparam, lparam);
}
} // namespace
bool ConfigureWindowsStreamTaskbar(HWND window) {
if (!window || !IsWindow(window)) return false;
DWORD_PTR existing = 0;
if (GetWindowSubclass(window, TaskbarSubclassProc, kTaskbarSubclassId,
&existing)) {
return true;
}
Microsoft::WRL::ComPtr<IPropertyStore> properties;
if (FAILED(SHGetPropertyStoreForWindow(window, IID_PPV_ARGS(&properties)))) {
return false;
}
if (!SetWindowSubclass(window, TaskbarSubclassProc, kTaskbarSubclassId,
reinterpret_cast<DWORD_PTR>(properties.Get()))) {
return false;
}
// A live session cannot be recreated by launching the executable. Keep
// pinning on the main application's button instead of creating a shortcut
// that claims to reopen a remote session but only opens the connection page.
PROPVARIANT prevent_pinning{};
InitPropVariantFromBoolean(TRUE, &prevent_pinning);
HRESULT result = properties->SetValue(PKEY_AppUserModel_PreventPinning,
prevent_pinning);
PROPVARIANT app_id{};
if (SUCCEEDED(result)) result = InitPropVariantFromString(kStreamAppId, &app_id);
if (SUCCEEDED(result)) result = properties->SetValue(PKEY_AppUserModel_ID, app_id);
PropVariantClear(&app_id);
if (FAILED(result)) {
ClearTaskbarProperties(properties.Get());
RemoveWindowSubclass(window, TaskbarSubclassProc, kTaskbarSubclassId);
return false;
}
// SetValue applies immediately; this store does not require Commit(). The
// subclass retains the store until this HWND is destroyed, even if Slint
// later creates a new native window for the same component.
properties.Detach();
return true;
}
} // namespace crossdesk
@@ -0,0 +1,15 @@
#ifndef CROSSDESK_WINDOWS_WINDOW_TASKBAR_H_
#define CROSSDESK_WINDOWS_WINDOW_TASKBAR_H_
#include <windows.h>
namespace crossdesk {
// Give the remote control window its own taskbar group, leaving the main
// window's shortcut grouping intact. Call on the UI thread before showing a
// native window; repeated calls are harmless, including after hide/show.
bool ConfigureWindowsStreamTaskbar(HWND window);
} // namespace crossdesk
#endif // CROSSDESK_WINDOWS_WINDOW_TASKBAR_H_
+1
View File
@@ -686,6 +686,7 @@ function setup_targets()
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/window_taskbar.cpp",
"apps/desktop/src/platform/windows/gui/shutdown_session_guard.cpp",
"apps/desktop/src/platform/windows/gui/slint_renderer_probe.cpp",
"apps/desktop/src/platform/common/gui/opengl_video_renderer.cpp",