[fix] avoid blocking Windows shutdown and restore display modes

This commit is contained in:
dijunkun
2026-11-11 04:28:45 +08:00
parent e47a3ac65c
commit 3b60a32ba7
13 changed files with 273 additions and 221 deletions
@@ -62,7 +62,7 @@
#include <windows.h>
#include "platform/windows/gui/slint_backend.h"
#include "platform/windows/gui/shutdown_session_guard.h"
#include "platform/windows/gui/session_end_observer.h"
#include "platform/windows/gui/tray/win_tray.h"
#include "platform/windows/gui/window_icons.h"
#include "platform/windows/gui/window_taskbar.h"
@@ -989,8 +989,7 @@ void DragWindow(WindowHandle& component, int phase, float mouse_x,
struct GuiApplication::SlintUi {
#if _WIN32
std::unique_ptr<ShutdownSessionGuard> shutdown_guard;
std::wstring_view shutdown_reason;
SessionEndObserver session_end;
#endif
gallery::Workspace gallery;
std::filesystem::path gallery_path;
@@ -1397,8 +1396,7 @@ void GuiApplication::InitializeUi() {
ui_->main->set_lan_mode(config_center_->GetConnectionMode() ==
ConfigCenter::CONNECTION_MODE::DIRECT_IP);
#if _WIN32
ui_->shutdown_guard =
std::make_unique<ShutdownSessionGuard>(windows_rdp_handoff_connected_);
ui_->session_end.Start();
#endif
ui_->sponsorship_prompt_pending =
config_center_->GetSponsorshipPromptDismissedVersion() != CROSSDESK_VERSION;
@@ -2533,6 +2531,12 @@ void GuiApplication::BindServerCallbacks() {
}
void GuiApplication::Tick() {
#if _WIN32
if (ui_) {
ui_->session_end.Start(); // Retry a transient native-window creation failure.
if (ui_->session_end.Ending()) exit_ = true;
}
#endif
if (exit_) {
BeginShutdown();
if (ShutdownDrained()) slint::quit_event_loop();
@@ -2647,11 +2651,6 @@ void GuiApplication::Tick() {
? std::string("ConnectionFailed")
: lan_error_;
ui_->main->set_lan_message(UiText(lan_message));
#if _WIN32
if (ui_->shutdown_guard) {
ui_->shutdown_guard->Update(ui_->shutdown_reason);
}
#endif
PollUpdateCheck();
HandleSessionCleanup();
HandlePasswordChangeResult();
@@ -3280,9 +3279,6 @@ void GuiApplication::UpdateLocalization() {
return;
}
#if _WIN32
static std::vector<std::wstring> shutdown_reasons(localization::GetSupportedLanguages().size());
ui_->shutdown_reason = localization::GetWindowsLocalizedLabel(
language, "shutdown_reason", L"Remote session active.", shutdown_reasons);
const bool available = SupportsServiceAutostart();
for (const auto& main : {ui_->main, ui_->wall}) {
main->set_service_autostart_platform(true);
@@ -7081,9 +7077,6 @@ void GuiApplication::Cleanup() {
return;
}
ui_->file_dialog_lifetime.reset();
#if _WIN32
ui_->shutdown_guard.reset();
#endif
#if defined(__APPLE__)
CancelNativeFileDialog();
#endif
@@ -433,7 +433,6 @@ inline constexpr TranslationRow kTranslationRows[] = {
{"近期连接", {"近期连接", "Recent Connections", "Недавние подключения", "近期連線", "最近の接続", "최근 연결", "Aktuelle Verbindungen", "Connexions récentes", "Conexiones recientes", "Conexões recentes"}},
{"返回", {"返回", "Back", "Назад", "返回", "戻る", "뒤로", "Zurück", "Retour", "Volver", "Voltar"}},
{"还原窗口", {"还原窗口", "Restore window", "Восстановить окно", "還原視窗", "復元ウィンドウ", "복원 창", "Fenster „Wiederherstellen“.", "Fenêtre de restauration", "Restaurar ventana", "Janela de restauração"}},
{"远程控制连接中,保留连接以确认或取消关机。", {"远程控制连接中,保留连接以确认或取消关机。", "Remote session active. Keeping the connection available to confirm or cancel shutdown.", "Удалённый сеанс активен. Соединение сохранено для подтверждения или отмены выключения.", "遠端控制連線中,保留連線以確認或取消關機。", "リモート接続中です。シャットダウンの確認または取り消しのため、接続を維持しています。", "원격 세션이 활성화되어 있습니다. 종료 확인 또는 취소를 위해 연결을 유지합니다.", "Remotesitzung aktiv. Die Verbindung bleibt zum Bestätigen oder Abbrechen des Herunterfahrens bestehen.", "Session distante active. La connexion reste disponible pour confirmer ou annuler l’arrêt.", "Sesión remota activa. Se mantiene la conexión para confirmar o cancelar el apagado.", "Sessão remota ativa. A conexão é mantida para confirmar ou cancelar o desligamento."}},
{"远程桌面", {"远程桌面", "Remote Desktop", "Удаленный рабочий стол", "遠端桌面", "リモートデスクトップ", "원격 데스크톱", "Remotedesktop", "Bureau à distance", "Escritorio remoto", "Área de Trabalho Remota"}},
{"远端 IP / 端口", {"远端 IP / 端口", "Remote IP / Port", "Удалённый IP / Порт", "遠端 IP / 埠", "リモートIP/ポート", "원격 IP / 포트", "Remote-IP/Port", "IP/port distant", "IP/puerto remoto", "IP/Porta Remota"}},
{"远端 Windows 服务不可用", {"远端 Windows 服务不可用", "Remote Windows service unavailable", "Служба Windows на удаленной стороне недоступна", "遠端 Windows 服務不可用", "リモート Windows サービスが利用できない", "원격 Windows 서비스를 사용할 수 없습니다.", "Remote-Windows-Dienst nicht verfügbar", "Service Windows distant indisponible", "Servicio remoto de Windows no disponible", "Serviço remoto do Windows indisponível"}},
@@ -840,7 +839,6 @@ inline constexpr NamedTranslation kNamedTranslations[] = {
{"privacy_unlock_lock", "锁定"},
{"privacy_screen_unlock_hint", "按下快捷键,解除隐私屏"},
{"exit_program", "退出"},
{"shutdown_reason", "远程控制连接中,保留连接以确认或取消关机。"},
{"pause_video_when_minimized", "最小化时暂停视频"},
{"pause_video_when_minimized_hint", "控制窗口最小化时暂停接收视频,降低本机解码资源占用。"},
{"system_permissions", "系统权限"},
@@ -1352,7 +1350,6 @@ inline constexpr const char* kSlintLiterals[] = {
X(privacy_unlock_lock) \
X(privacy_screen_unlock_hint) \
X(exit_program) \
X(shutdown_reason) \
X(pause_video_when_minimized) \
X(pause_video_when_minimized_hint) \
X(system_permissions) \
@@ -0,0 +1,72 @@
#include "session_end_observer.h"
#include "rd_log.h"
namespace crossdesk {
namespace {
constexpr wchar_t kSessionWindowClass[] = L"CrossDesk.SessionEndObserver";
}
SessionEndObserver::~SessionEndObserver() {
if (window_) DestroyWindow(window_);
}
bool SessionEndObserver::ReportError(const char* operation) {
const DWORD error = GetLastError();
const ULONGLONG now = GetTickCount64();
if (last_error_tick_ == 0 || now - last_error_tick_ >= 5000) {
LOG_WARN("Windows session end observer {} failed, error={}", operation, error);
last_error_tick_ = now;
}
SetLastError(error);
return false;
}
bool SessionEndObserver::Start() {
if (window_) return true;
WNDCLASSW klass{};
klass.lpfnWndProc = WindowProc;
klass.hInstance = GetModuleHandleW(nullptr);
klass.lpszClassName = kSessionWindowClass;
if (!RegisterClassW(&klass) && GetLastError() != ERROR_CLASS_ALREADY_EXISTS)
return ReportError("register window");
// A hidden top-level window receives session-end broadcasts even when the
// Slint windows are in the tray. HWND_MESSAGE does not receive them.
window_ = CreateWindowExW(WS_EX_TOOLWINDOW, kSessionWindowClass, L"CrossDesk",
WS_OVERLAPPED, 0, 0, 0, 0, nullptr, nullptr, klass.hInstance, this);
if (!window_) return ReportError("create window");
return true;
}
LRESULT CALLBACK SessionEndObserver::WindowProc(
HWND window, UINT message, WPARAM wparam, LPARAM lparam) {
if (message == WM_NCCREATE) {
const auto* create = reinterpret_cast<CREATESTRUCTW*>(lparam);
auto* observer = static_cast<SessionEndObserver*>(create->lpCreateParams);
observer->window_ = window;
SetWindowLongPtrW(window, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(observer));
}
auto* observer = reinterpret_cast<SessionEndObserver*>(
GetWindowLongPtrW(window, GWLP_USERDATA));
if (observer && message == WM_QUERYENDSESSION) {
// Never stop work or veto shutdown here, including logoff/Restart Manager.
LOG_INFO("Windows shutdown query accepted: flags=0x{:x}",
static_cast<DWORD>(lparam));
return TRUE;
}
if (observer && message == WM_ENDSESSION) {
LOG_INFO("Windows session end: confirmed={}, flags=0x{:x}",
wparam != FALSE, static_cast<DWORD>(lparam));
// Let the GUI timer begin its normal cleanup only after confirmation.
// Do not wait for network/device threads inside a Windows message handler.
if (wparam != FALSE) observer->ending_ = true;
return 0;
}
if (observer && message == WM_NCDESTROY) {
observer->window_ = nullptr;
SetWindowLongPtrW(window, GWLP_USERDATA, 0);
}
return DefWindowProcW(window, message, wparam, lparam);
}
} // namespace crossdesk
@@ -0,0 +1,39 @@
/*
* @Author: DI JUNKUN
* @Date: 2026-11-11
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _SESSION_END_OBSERVER_H_
#define _SESSION_END_OBSERVER_H_
#include <Windows.h>
namespace crossdesk {
// Owned and pumped by the UI thread. A query is permission to end the session,
// not a request to tear down transport/capture: another application may still
// cancel shutdown. This observer never registers a shutdown-blocking reason.
class SessionEndObserver {
public:
SessionEndObserver() = default;
~SessionEndObserver();
SessionEndObserver(const SessionEndObserver&) = delete;
SessionEndObserver& operator=(const SessionEndObserver&) = delete;
bool Start();
bool Ending() const { return ending_; }
private:
bool ReportError(const char* operation);
static LRESULT CALLBACK WindowProc(HWND window, UINT message,
WPARAM wparam, LPARAM lparam);
HWND window_ = nullptr;
bool ending_ = false;
ULONGLONG last_error_tick_ = 0;
};
} // namespace crossdesk
#endif
@@ -1,121 +0,0 @@
#include "shutdown_session_guard.h"
#include <utility>
#include "rd_log.h"
namespace crossdesk {
namespace {
constexpr wchar_t kShutdownWindowClass[] = L"CrossDesk.ShutdownSessionGuard";
}
ShutdownSessionGuard::ShutdownSessionGuard(
std::shared_ptr<std::atomic<bool>> connected)
: connected_(std::move(connected)) {}
ShutdownSessionGuard::~ShutdownSessionGuard() {
ClearReason();
if (window_) DestroyWindow(window_);
}
bool ShutdownSessionGuard::ReportError(const char* operation) {
const DWORD error = GetLastError();
const ULONGLONG now = GetTickCount64();
if (last_error_tick_ == 0 || now - last_error_tick_ >= 5000) {
LOG_WARN("Windows shutdown session guard {} failed, error={}", operation, error);
last_error_tick_ = now;
}
SetLastError(error);
return false;
}
bool ShutdownSessionGuard::Start() {
if (window_) return true;
WNDCLASSW klass{};
klass.lpfnWndProc = WindowProc;
klass.hInstance = GetModuleHandleW(nullptr);
klass.lpszClassName = kShutdownWindowClass;
if (!RegisterClassW(&klass) && GetLastError() != ERROR_CLASS_ALREADY_EXISTS)
return ReportError("register window");
// A top-level window receives session-end broadcasts even when the Slint
// windows are hidden in the tray. HWND_MESSAGE does not receive them.
window_ = CreateWindowExW(WS_EX_TOOLWINDOW, kShutdownWindowClass, L"CrossDesk",
WS_OVERLAPPED, 0, 0, 0, 0, nullptr, nullptr, klass.hInstance, this);
if (!window_) return ReportError("create window");
return true;
}
bool ShutdownSessionGuard::ClearReason() {
if (!reason_registered_) return true;
if (window_ && !ShutdownBlockReasonDestroy(window_))
return ReportError("clear reason");
reason_registered_ = false;
LOG_INFO("Windows shutdown session guard released");
return true;
}
bool ShutdownSessionGuard::SyncReason() {
if (shutdown_allowed_ || !connected_ ||
!connected_->load(std::memory_order_relaxed)) {
return ClearReason();
}
if (reason_registered_) return true;
if (!ShutdownBlockReasonCreate(window_, reason_.c_str()))
return ReportError("register reason");
reason_registered_ = true;
LOG_INFO("Windows shutdown session guard armed for connected controller");
return true;
}
bool ShutdownSessionGuard::Update(std::wstring_view reason) {
if (!Start()) return false;
if (std::wstring_view(reason_) != reason) {
// Keep the old registration and text on failure so the next tick retries
// removal before registering the newly selected language.
if (!ClearReason()) return false;
reason_.assign(reason);
}
return SyncReason();
}
LRESULT CALLBACK ShutdownSessionGuard::WindowProc(
HWND window, UINT message, WPARAM wparam, LPARAM lparam) {
if (message == WM_NCCREATE) {
const auto* create = reinterpret_cast<CREATESTRUCTW*>(lparam);
auto* guard = static_cast<ShutdownSessionGuard*>(create->lpCreateParams);
guard->window_ = window;
SetWindowLongPtrW(window, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(guard));
}
auto* guard = reinterpret_cast<ShutdownSessionGuard*>(
GetWindowLongPtrW(window, GWLP_USERDATA));
if (guard && message == WM_QUERYENDSESSION) {
const auto flags = static_cast<DWORD>(lparam);
// "Shut down anyway", logoff and Restart Manager must remain available.
if ((flags & (ENDSESSION_CRITICAL | ENDSESSION_LOGOFF | ENDSESSION_CLOSEAPP)) != 0)
guard->shutdown_allowed_ = true;
guard->SyncReason();
const bool retain = !guard->shutdown_allowed_ && guard->reason_registered_ &&
guard->connected_ && guard->connected_->load(std::memory_order_relaxed);
LOG_INFO("Windows shutdown query: flags=0x{:x}, retain_remote_session={}", flags, retain);
return retain ? FALSE : TRUE;
}
if (guard && message == WM_ENDSESSION) {
LOG_INFO("Windows session end: confirmed={}, flags=0x{:x}",
wparam != FALSE, static_cast<DWORD>(lparam));
guard->shutdown_allowed_ = wparam != FALSE;
guard->SyncReason();
// A cancelled shutdown must leave transport, capture and input running.
return 0;
}
if (guard && message == WM_NCDESTROY) {
guard->ClearReason();
// Destruction removes the native registration even if explicit removal
// failed. A later window must not inherit the destroyed window's state.
guard->reason_registered_ = false;
guard->window_ = nullptr;
SetWindowLongPtrW(window, GWLP_USERDATA, 0);
}
return DefWindowProcW(window, message, wparam, lparam);
}
} // namespace crossdesk
@@ -1,48 +0,0 @@
/*
* @Author: DI JUNKUN
* @Date: 2026-10-22
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _SHUTDOWN_SESSION_GUARD_H_
#define _SHUTDOWN_SESSION_GUARD_H_
#include <Windows.h>
#include <atomic>
#include <memory>
#include <string>
#include <string_view>
namespace crossdesk {
// Owned and pumped by the UI thread. The transport may revoke the shared
// connection flag from another thread, including between UI ticks.
class ShutdownSessionGuard {
public:
explicit ShutdownSessionGuard(std::shared_ptr<std::atomic<bool>> connected);
~ShutdownSessionGuard();
ShutdownSessionGuard(const ShutdownSessionGuard&) = delete;
ShutdownSessionGuard& operator=(const ShutdownSessionGuard&) = delete;
bool Update(std::wstring_view reason);
private:
bool Start();
bool SyncReason();
bool ClearReason();
bool ReportError(const char* operation);
static LRESULT CALLBACK WindowProc(HWND window, UINT message,
WPARAM wparam, LPARAM lparam);
std::shared_ptr<std::atomic<bool>> connected_;
HWND window_ = nullptr;
std::wstring reason_;
bool reason_registered_ = false;
bool shutdown_allowed_ = false;
ULONGLONG last_error_tick_ = 0;
};
} // namespace crossdesk
#endif
@@ -425,17 +425,22 @@ void ScreenCapturerWin::UpdateNativeCursor(ScreenCapturer* backend) {
}
bool ScreenCapturerWin::PrepareUnattendedDisplay() {
if (virtual_display_.active() || DetectHeadlessDesktop()) {
physical_mode_recovery_pending_ = true;
physical_mode_restore_attempts_ = 0;
}
if (!ForceHeadlessEnabled() && unattended_host_ && virtual_display_.active() &&
QueryAttachedPhysicalDisplayMode().width > 0) {
// The monitor may have returned between controller connections.
virtual_display_.Release();
RestorePhysicalDisplayMode();
return false;
}
if (!unattended_host_ ||
(!virtual_display_.active() && !DetectHeadlessDesktop())) return false;
virtual_display_.PrepareAcquire();
std::string error;
if (!virtual_display_.Acquire(ResolveVirtualDisplayMode(), &error)) {
if (!virtual_display_.Acquire(ResolveVirtualDisplayMode(false), &error)) {
LOG_WARN("Cannot prepare unattended capture display: {}", error);
return false;
}
@@ -778,7 +783,7 @@ int ScreenCapturerWin::Start(bool show_cursor) {
if (running_.load(std::memory_order_relaxed)) {
return 0;
}
ResolveVirtualDisplayMode();
ResolveVirtualDisplayMode(!physical_mode_recovery_pending_ && physical_mode_restore_attempts_ == 0);
native_cursor_enabled_.store(native_cursor_default_.load());
NotifyPrivacyCapture(false);
@@ -870,7 +875,7 @@ void ScreenCapturerWin::BeginVirtualDisplayProvisioning() {
// A usbmmidd virtual monitor turns the phantom desktop into a real
// display target, so the GPU backends and a chosen resolution apply.
std::string error;
if (virtual_display_.Acquire(ResolveVirtualDisplayMode(), &error)) {
if (virtual_display_.Acquire(ResolveVirtualDisplayMode(false), &error)) {
virtual_display_ready_.store(true, std::memory_order_release);
} else if (error != "cancelled") {
LOG_WARN("Windows capturer: no usbmmidd virtual display ({}); "
@@ -1169,6 +1174,10 @@ void ScreenCapturerWin::CheckDisplayPresence(ULONGLONG now) {
const bool headless = DetectHeadlessDesktop(stalled,
topology_changed);
const bool owns_display = virtual_display_.active();
if (headless || owns_display) {
physical_mode_recovery_pending_ = true;
physical_mode_restore_attempts_ = 0;
}
const bool provisioning =
virtual_display_busy_.load(std::memory_order_acquire);
if (!provisioning) {
@@ -1181,7 +1190,8 @@ void ScreenCapturerWin::CheckDisplayPresence(ULONGLONG now) {
physical_binding_changed = !replacement_display_name_.empty() &&
replacement_display_name_ != GetDisplayLabel(physical_device);
}
if (physical_present && (owns_display || physical_binding_changed)) {
if (physical_present && (owns_display || physical_binding_changed ||
physical_mode_recovery_pending_)) {
// Debounce a hotplug before removing the fallback that is still carrying
// the stream. This runs on the management thread, never a frame callback.
if (physical_display_since_ == 0) physical_display_since_ = now;
@@ -1194,7 +1204,7 @@ void ScreenCapturerWin::CheckDisplayPresence(ULONGLONG now) {
physical_display_since_ = 0;
// Track user resolution changes during capture, not only process startup.
// Resolve ignores phantom, RDP and virtual-display clone modes.
ResolveVirtualDisplayMode();
ResolveVirtualDisplayMode(!physical_mode_recovery_pending_ && physical_mode_restore_attempts_ == 0);
}
bool target_ready = false;
if (owns_display) {
@@ -1222,6 +1232,23 @@ void ScreenCapturerWin::CheckDisplayPresence(ULONGLONG now) {
BeginVirtualDisplayProvisioning();
}
void ScreenCapturerWin::RestorePhysicalDisplayMode() {
if (!physical_mode_recovery_pending_ || ForceHeadlessEnabled()) return;
std::string error;
if (RestoreAttachedPhysicalDisplayModes(&error)) {
physical_mode_recovery_pending_ = false;
physical_mode_restore_attempts_ = 0;
} else {
++physical_mode_restore_attempts_;
LOG_WARN("Physical display mode recovery attempt {} failed: {}",
physical_mode_restore_attempts_, error);
// Retry transient EDID/topology failures, but do not continually interrupt
// capture or fight a user setting if a driver cannot apply its native mode.
if (physical_mode_restore_attempts_ >= 3)
physical_mode_recovery_pending_ = false;
}
}
void ScreenCapturerWin::RestorePhysicalDisplay() {
JoinVirtualDisplayThread();
if (!running_.load(std::memory_order_relaxed)) return;
@@ -1231,6 +1258,7 @@ void ScreenCapturerWin::RestorePhysicalDisplay() {
StopSecureDesktopSharedCapture(secure_shared_session_id_);
virtual_display_ready_.store(false, std::memory_order_release);
virtual_display_.Release();
RestorePhysicalDisplayMode();
// Windows may renumber outputs after the unplug. Requery and bind the real
// screen to the same registered wire slot, including multi-monitor hotplugs.
std::wstring device;
@@ -117,6 +117,10 @@ class ScreenCapturerWin : public ScreenCapturer {
std::atomic<bool> virtual_display_busy_{false};
ULONGLONG last_virtual_adoption_tick_ = 0;
ULONGLONG physical_display_since_ = 0;
// A phantom desktop can return on the same DISPLAYn without ever acquiring
// usbmmidd. Remember that transition independently of virtual ownership.
bool physical_mode_recovery_pending_ = false;
unsigned physical_mode_restore_attempts_ = 0;
// Protected by alias_mutex_. The virtual screen and then the returning
// physical screen replace the same active wire slot explicitly.
std::string replacement_display_name_;
@@ -154,6 +158,7 @@ class ScreenCapturerWin : public ScreenCapturer {
void CheckCaptureProgress(ULONGLONG now);
void CheckDisplayPresence(ULONGLONG now);
void RestorePhysicalDisplay();
void RestorePhysicalDisplayMode();
// Virtual display provisioning runs off the caller's thread.
void JoinVirtualDisplayThread();
void BeginVirtualDisplayProvisioning();
@@ -22,8 +22,9 @@ inline constexpr DWORD WindowsShutdownLevel(WindowsShutdownRole role) {
}
inline DWORD ConfigureWindowsShutdownOrder(WindowsShutdownRole role) {
// Change only the order. The GUI's session guard separately defers shutdown
// while a controller is connected; a confirmed shutdown still ends CrossDesk.
// Change only the order, without vetoing shutdown or extending its timeout.
// SCM shutdown notifications still govern the service-owned host/helpers;
// these levels do not override an explicit stop from their owner.
if (SetProcessShutdownParameters(WindowsShutdownLevel(role), 0))
return ERROR_SUCCESS;
const DWORD error = GetLastError();
@@ -237,7 +237,14 @@ void SaveRememberedMode(HKEY root, const RememberedDisplayMode& record) {
RegCloseKey(key);
}
std::vector<std::wstring> ActivePhysicalDisplaySources(bool* queried) {
struct PhysicalDisplayTarget {
std::wstring device;
std::wstring monitor_path;
DISPLAYCONFIG_PATH_TARGET_INFO target;
bool cloned = false;
};
std::vector<PhysicalDisplayTarget> ActivePhysicalDisplays(bool* queried) {
*queried = false;
for (int attempt = 0; attempt < 4; ++attempt) {
UINT32 path_count = 0, mode_count = 0;
@@ -252,7 +259,7 @@ std::vector<std::wstring> ActivePhysicalDisplaySources(bool* queried) {
paths.data(), &mode_count, modes.data(), nullptr);
if (result == ERROR_INSUFFICIENT_BUFFER) continue;
if (result != ERROR_SUCCESS) break;
std::vector<std::wstring> sources;
std::vector<PhysicalDisplayTarget> displays;
for (UINT32 i = 0; i < path_count; ++i) {
const auto& path = paths[i];
if (!(path.flags & DISPLAYCONFIG_PATH_ACTIVE) ||
@@ -268,10 +275,18 @@ std::vector<std::wstring> ActivePhysicalDisplaySources(bool* queried) {
DISPLAYCONFIG_SOURCE_DEVICE_NAME source{};
source.header = {DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME, sizeof(source),
path.sourceInfo.adapterId, path.sourceInfo.id};
if (DisplayConfigGetDeviceInfo(&source.header) == ERROR_SUCCESS)
sources.emplace_back(source.viewGdiDeviceName);
if (DisplayConfigGetDeviceInfo(&source.header) != ERROR_SUCCESS) continue;
const bool cloned = std::count_if(paths.begin(), paths.begin() + path_count,
[&](const DISPLAYCONFIG_PATH_INFO& other) {
return (other.flags & DISPLAYCONFIG_PATH_ACTIVE) &&
other.sourceInfo.id == path.sourceInfo.id &&
other.sourceInfo.adapterId.LowPart == path.sourceInfo.adapterId.LowPart &&
other.sourceInfo.adapterId.HighPart == path.sourceInfo.adapterId.HighPart;
}) > 1;
displays.push_back({source.viewGdiDeviceName, target.monitorDevicePath,
path.targetInfo, cloned});
}
return sources;
return displays;
}
return {};
}
@@ -284,7 +299,7 @@ VirtualDisplayMode QueryAttachedPhysicalDisplayMode(std::wstring* device_name) {
// RDP creates a display mode for that client, not the console's next boot.
if (!GetSystemMetrics(SM_REMOTESESSION)) {
bool queried = false;
const auto sources = ActivePhysicalDisplaySources(&queried);
const auto sources = ActivePhysicalDisplays(&queried);
for (DWORD index = 0;; ++index) {
DISPLAY_DEVICEW adapter{sizeof(adapter)};
if (!EnumDisplayDevicesW(nullptr, index, &adapter, 0)) break;
@@ -296,8 +311,8 @@ VirtualDisplayMode QueryAttachedPhysicalDisplayMode(std::wstring* device_name) {
// real target disappears. Require the corresponding available CCD path
// when CCD is accessible; an IDD path must not validate a phantom GPU.
if (queried && std::none_of(sources.begin(), sources.end(),
[&](const std::wstring& source) {
return _wcsicmp(source.c_str(), adapter.DeviceName) == 0;
[&](const PhysicalDisplayTarget& source) {
return _wcsicmp(source.device.c_str(), adapter.DeviceName) == 0;
})) continue;
bool present = false;
for (DWORD child = 0; !present; ++child) {
@@ -325,7 +340,88 @@ VirtualDisplayMode QueryAttachedPhysicalDisplayMode(std::wstring* device_name) {
return attached;
}
VirtualDisplayMode ResolveVirtualDisplayMode() {
bool RestoreAttachedPhysicalDisplayModes(std::string* error) {
if (GetSystemMetrics(SM_REMOTESESSION) || CountUsbmmiddMonitors() != 0) {
SetError(error, "physical_display_topology_not_ready");
return false;
}
bool queried = false;
const auto displays = ActivePhysicalDisplays(&queried);
if (displays.empty()) {
SetError(error, "physical_display_not_ready");
return false;
}
bool restored = true;
for (const auto& display : displays) {
// One source mode affects every cloned target. Do not force a native 4K
// mode onto another real/third-party screen sharing a 1080p clone source.
if (display.cloned) {
LOG_INFO("Physical display '{}': keeping the shared clone mode", Narrow(display.device));
continue;
}
DISPLAYCONFIG_TARGET_PREFERRED_MODE preferred{};
preferred.header = {DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_PREFERRED_MODE,
sizeof(preferred), display.target.adapterId, display.target.id};
const LONG preferred_error = DisplayConfigGetDeviceInfo(&preferred.header);
DEVMODEW mode{};
mode.dmSize = sizeof(mode);
if (preferred_error != ERROR_SUCCESS ||
!ValidDisplayMode({static_cast<int>(preferred.width), static_cast<int>(preferred.height)}) ||
!EnumDisplaySettingsExW(display.device.c_str(), ENUM_CURRENT_SETTINGS, &mode, 0)) {
SetError(error, "physical_display_preferred_mode_unavailable:" + std::to_string(preferred_error));
restored = false;
continue;
}
DWORD width = preferred.width, height = preferred.height;
if (mode.dmDisplayOrientation == DMDO_90 || mode.dmDisplayOrientation == DMDO_270)
std::swap(width, height);
if (mode.dmPelsWidth == width && mode.dmPelsHeight == height) continue;
const DWORD old_width = mode.dmPelsWidth, old_height = mode.dmPelsHeight;
mode.dmPelsWidth = width;
mode.dmPelsHeight = height;
// Do not reuse the phantom's timing, change the primary screen/position,
// or write this temporary recovery into the user's persistent settings.
mode.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_BITSPERPEL | DM_DISPLAYORIENTATION;
LONG result = ChangeDisplaySettingsExW(display.device.c_str(), &mode, nullptr, CDS_TEST, nullptr);
if (result == DISP_CHANGE_SUCCESSFUL) {
// Display numbers may be recycled during hotplug. Validate the target
// identity again after CDS_TEST before changing this source's mode.
const auto current = ActivePhysicalDisplays(&queried);
const bool same_target = std::any_of(current.begin(), current.end(),
[&](const PhysicalDisplayTarget& target) {
return !target.cloned && target.device == display.device &&
target.monitor_path == display.monitor_path &&
target.target.id == display.target.id &&
target.target.adapterId.LowPart == display.target.adapterId.LowPart &&
target.target.adapterId.HighPart == display.target.adapterId.HighPart;
});
if (!same_target || CountUsbmmiddMonitors() != 0) {
SetError(error, "physical_display_changed_during_restore");
restored = false;
continue;
}
result = ChangeDisplaySettingsExW(display.device.c_str(), &mode, nullptr, 0, nullptr);
}
if (result != DISP_CHANGE_SUCCESSFUL) {
SetError(error, "physical_display_mode_change_failed:" + std::to_string(result));
restored = false;
continue;
}
DEVMODEW applied{};
applied.dmSize = sizeof(applied);
if (!EnumDisplaySettingsExW(display.device.c_str(), ENUM_CURRENT_SETTINGS, &applied, 0) ||
applied.dmPelsWidth != width || applied.dmPelsHeight != height) {
SetError(error, "physical_display_mode_not_applied");
restored = false;
continue;
}
LOG_INFO("Physical display '{}' restored from {}x{} to preferred {}x{}",
Narrow(display.device), old_width, old_height, width, height);
}
return restored;
}
VirtualDisplayMode ResolveVirtualDisplayMode(bool remember_attached) {
std::lock_guard lock(remembered_mode_mutex);
RememberedDisplayMode saved = last_observed_mode;
for (HKEY root : {HKEY_LOCAL_MACHINE, HKEY_CURRENT_USER}) {
@@ -336,7 +432,7 @@ VirtualDisplayMode ResolveVirtualDisplayMode() {
}
// A reattached physical screen may temporarily clone the fallback monitor.
// Do not let that temporary desktop mode replace the original observation.
const auto attached = CountUsbmmiddMonitors() == 0
const auto attached = remember_attached && CountUsbmmiddMonitors() == 0
? QueryAttachedPhysicalDisplayMode()
: VirtualDisplayMode{};
if (ValidDisplayMode(attached)) {
@@ -41,11 +41,15 @@ UsbmmiddStatus QueryUsbmmiddStatus();
// Remember an attached display's current pixel mode for GUI/SYSTEM startup.
// Phantom targets and our own fallback monitor must not overwrite that mode.
VirtualDisplayMode ResolveVirtualDisplayMode();
VirtualDisplayMode ResolveVirtualDisplayMode(bool remember_attached = true);
// A real active monitor's current mode, or {0, 0} for headless/RDP desktops.
// This probe does not update the remembered mode or change display settings.
VirtualDisplayMode QueryAttachedPhysicalDisplayMode(
std::wstring* device_name = nullptr);
// After a headless/virtual-display handoff, recover each real screen's native
// preferred pixel mode. Keep rotation and layout; never persist fallback modes.
// Returns false while topology/EDID is settling, so the owner can retry.
bool RestoreAttachedPhysicalDisplayModes(std::string* error = nullptr);
// Adapters attached to the desktop, i.e. monitors currently plugged in.
int CountUsbmmiddMonitors();
+1 -1
View File
@@ -688,7 +688,7 @@ function setup_targets()
"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/session_end_observer.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",
-14
View File
@@ -10248,18 +10248,6 @@
"pt-BR": "{0}, recebendo {1}, enviando {2}, taxa de perda de pacotes {3}",
"ru": "{0}, прием {1}, отправка {2}, скорость потери пакетов {3}"
},
"远程控制连接中,保留连接以确认或取消关机。": {
"zh-Hans": "远程控制连接中,保留连接以确认或取消关机。",
"en": "Remote session active. Keeping the connection available to confirm or cancel shutdown.",
"ru": "Удалённый сеанс активен. Соединение сохранено для подтверждения или отмены выключения.",
"zh-Hant": "遠端控制連線中,保留連線以確認或取消關機。",
"ja": "リモート接続中です。シャットダウンの確認または取り消しのため、接続を維持しています。",
"ko": "원격 세션이 활성화되어 있습니다. 종료 확인 또는 취소를 위해 연결을 유지합니다.",
"de": "Remotesitzung aktiv. Die Verbindung bleibt zum Bestätigen oder Abbrechen des Herunterfahrens bestehen.",
"fr": "Session distante active. La connexion reste disponible pour confirmer ou annuler l’arrêt.",
"es": "Sesión remota activa. Se mantiene la conexión para confirmar o cancelar el apagado.",
"pt-BR": "Sessão remota ativa. A conexão é mantida para confirmar ou cancelar o desligamento."
},
"监听所有 IP 接口": {
"zh-Hans": "监听所有 IP 接口",
"en": "Listen on all IP interfaces",
@@ -11335,7 +11323,6 @@
"privacy_unlock_lock": "锁定",
"privacy_screen_unlock_hint": "按下快捷键,解除隐私屏",
"exit_program": "退出",
"shutdown_reason": "远程控制连接中,保留连接以确认或取消关机。",
"pause_video_when_minimized": "最小化时暂停视频",
"pause_video_when_minimized_hint": "控制窗口最小化时暂停接收视频,降低本机解码资源占用。",
"system_permissions": "系统权限",
@@ -11985,7 +11972,6 @@
"近期连接",
"返回",
"还原窗口",
"远程控制连接中,保留连接以确认或取消关机。",
"远程桌面",
"远端 IP / 端口",
"远端 Windows 服务不可用",