From 1b6248b05bb394b0e2c829bfdee539172e304944 Mon Sep 17 00:00:00 2001 From: dijunkun Date: Thu, 5 Nov 2026 18:02:12 +0800 Subject: [PATCH] [fix] position windows before showing to prevent flicker --- .../src/gui/application/gui_application.cpp | 61 ++++++++----------- .../src/gui/application/window_presentation.h | 21 +++++++ .../file_transfer/file_browser_window.cpp | 45 +++++++------- .../file_transfer/file_browser_window.h | 6 +- 4 files changed, 69 insertions(+), 64 deletions(-) create mode 100644 apps/desktop/src/gui/application/window_presentation.h diff --git a/apps/desktop/src/gui/application/gui_application.cpp b/apps/desktop/src/gui/application/gui_application.cpp index 1162340d..74c4b79d 100644 --- a/apps/desktop/src/gui/application/gui_application.cpp +++ b/apps/desktop/src/gui/application/gui_application.cpp @@ -97,6 +97,7 @@ #include "updater/update_controller.h" #endif #include "window_geometry.h" +#include "window_presentation.h" namespace crossdesk { namespace { @@ -1116,9 +1117,6 @@ struct GuiApplication::SlintUi { bool server_file_dialog_open = false; bool server_suspended_for_dialog = false; server_window_state::EdgeLayout server_layout{}; -#if _WIN32 - int server_native_window_attempts = 0; -#endif int localized_language = -1; std::chrono::steady_clock::time_point last_clipboard_poll{}; slint::Timer video_timer; @@ -3764,9 +3762,10 @@ void GuiApplication::SyncStreamWindow() { (*ui_->stream)->set_displays(ui_->display_model); (*ui_->stream)->set_stats_rows(ui_->stats_model); BindStreamCallbacks(); - (*ui_->stream)->show(); - // Repeat after the next layout pass because the backend resolves the - // initial DPI and preferred size asynchronously on some platforms. + (*ui_->stream)->window().set_size(slint::LogicalSize({ + kStreamWindowLogicalWidth, + StreamWindowLogicalHeight((*ui_->stream)->get_custom_titlebar())})); + // Resolve placement and any display-DPI change while still hidden. ui_->stream_initial_position_attempts = 2; #if defined(__APPLE__) ui_->stream_live_resize_configuration_attempts = 30; @@ -3781,8 +3780,10 @@ void GuiApplication::SyncStreamWindow() { // 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_) { - (*ui_->stream)->window().set_minimized(false); - (*ui_->stream)->show(); + if (ui_->stream_initial_position_attempts == 0) { + (*ui_->stream)->window().set_minimized(false); + (*ui_->stream)->show(); + } need_to_create_stream_window_ = false; } #if _WIN32 @@ -3802,7 +3803,8 @@ void GuiApplication::SyncStreamWindow() { if (ui_->video_presenter && ui_->video_presenter->EnsureAttached()) { video_frame_dirty_.store(true, std::memory_order_release); } - if (ui_->stream_initial_position_attempts > 0) { + if (has_sessions && ui_->stream_initial_position_attempts > 0 && + NativeWindowReady((*ui_->stream)->window())) { if (PositionWindowAtCenter( (*ui_->stream)->window(), ui_->main->window(), kStreamWindowLogicalWidth, @@ -3811,6 +3813,7 @@ void GuiApplication::SyncStreamWindow() { } else { ui_->stream_initial_position_attempts = 0; } + if (ui_->stream_initial_position_attempts == 0) (*ui_->stream)->show(); } if (!has_sessions) { SetStreamKeyboardFocus(false); @@ -4211,11 +4214,7 @@ void GuiApplication::SetServerPanelExpanded(bool expanded) { server_window_collapsed_ = !expanded; (*ui_->server_bar)->set_expanded(expanded); if (expanded) { - PositionServerWindows(); - (*ui_->server)->show(); -#if _WIN32 - ConfigureWindowsServerWindow((*ui_->server)->window()); -#endif + // SyncServerWindow shows the panel after native creation and placement. PositionServerWindows(); } else { (*ui_->server)->set_confirming_disconnect(false); @@ -4447,10 +4446,6 @@ void GuiApplication::SyncServerWindow() { BindServerCallbacks(); ui_localization::ApplyServerStrings(*ui_->server, localization_language_index_); ui_localization::ApplyServerStrings(*ui_->server_bar, localization_language_index_); - (*ui_->server_bar)->show(); -#if _WIN32 - ui_->server_native_window_attempts = 30; -#endif server_window_created_ = true; server_window_inited_ = true; } @@ -4470,13 +4465,9 @@ void GuiApplication::SyncServerWindow() { ui_->server_drag.active = false; server_window_created_ = false; server_window_inited_ = false; -#if _WIN32 - ui_->server_native_window_attempts = 0; -#endif } if (!ui_->server) return; if (ui_->server_suspended_for_dialog) { - (*ui_->server_bar)->show(); ui_->server_suspended_for_dialog = false; } @@ -4517,16 +4508,17 @@ void GuiApplication::SyncServerWindow() { (*ui_->server_bar)->set_controller_count(static_cast(ui_->controller_ids.size())); ui_localization::ApplyServerStrings(*ui_->server, localization_language_index_); ui_localization::ApplyServerStrings(*ui_->server_bar, localization_language_index_); -#if _WIN32 - if (ui_->server_native_window_attempts > 0) { - if (ConfigureWindowsServerWindow((*ui_->server_bar)->window())) - ui_->server_native_window_attempts = 0; - else - --ui_->server_native_window_attempts; - } -#endif (*ui_->server)->set_sending_file(transfers_.global_state().file_sending_.load()); PositionServerWindows(); + const auto show_prepared = [](slint::Window& window) { + if (window.is_visible() || !NativeWindowReady(window)) return; +#if _WIN32 + if (!ConfigureWindowsServerWindow(window)) return; +#endif + window.show(); + }; + show_prepared((*ui_->server_bar)->window()); + if (!server_window_collapsed_) show_prepared((*ui_->server)->window()); } void GuiApplication::CancelFileTransfer(uint32_t file_id) { @@ -4759,13 +4751,8 @@ void GuiApplication::SyncFileTransferNotifications() { #endif } if (!window.is_visible()) { -#if defined(__APPLE__) - // Winit creates native windows on the next event-loop turn. Wait until - // it exists so show() and focus restoration happen synchronously. - if (!window.appkit_view()) continue; -#elif _WIN32 - if (!window.win32_hwnd()) continue; -#endif + // Wait until native placement and focus restoration can be synchronous. + if (!NativeWindowReady(window)) continue; #if _WIN32 // Tool windows stay out of Alt-Tab and must not interrupt remote input. const auto configure = [&] { diff --git a/apps/desktop/src/gui/application/window_presentation.h b/apps/desktop/src/gui/application/window_presentation.h new file mode 100644 index 00000000..2824c0de --- /dev/null +++ b/apps/desktop/src/gui/application/window_presentation.h @@ -0,0 +1,21 @@ +#pragma once + +#include + +namespace crossdesk { + +// Winit creates hidden native windows on a later event-loop turn. Wait for +// that window before applying geometry that depends on its frame or DPI. +inline bool NativeWindowReady([[maybe_unused]] const slint::Window& window) { +#if defined(_WIN32) + return window.win32_hwnd() != nullptr; +#elif defined(__APPLE__) + return window.appkit_view() != nullptr; +#else + // Other backends accept initial geometry before show(); Wayland placement + // is controlled by the compositor and must not wait for a mapped surface. + return true; +#endif +} + +} // namespace crossdesk diff --git a/apps/desktop/src/gui/features/file_transfer/file_browser_window.cpp b/apps/desktop/src/gui/features/file_transfer/file_browser_window.cpp index c7194fb6..8e115089 100644 --- a/apps/desktop/src/gui/features/file_transfer/file_browser_window.cpp +++ b/apps/desktop/src/gui/features/file_transfer/file_browser_window.cpp @@ -10,6 +10,7 @@ extern unsigned int fa_solid_900_ttf_len; #include "ui/model_update.h" #include "localization.h" #include "application/window_geometry.h" +#include "application/window_presentation.h" #if defined(__APPLE__) #include "platform/window_drag.h" #elif defined(_WIN32) @@ -54,6 +55,9 @@ FileBrowserWindow::FileBrowserWindow(FileTransferManager& transfers, std::string transfers_.browser().TakeEvents(remote_); window_->window().window_handle().register_font_from_data( fa_solid_900_ttf, fa_solid_900_ttf_len); + // Resolve the native frame at its final size while it is still hidden. + // Otherwise show() applies the preferred size after we have centered it. + window_->window().set_size(slint::LogicalSize({900, 620})); window_->set_chinese(chinese_); UpdateActionLabels(language); SetPeerName(""); @@ -177,6 +181,7 @@ FileBrowserWindow::FileBrowserWindow(FileTransferManager& transfers, std::string next_sync_ = {}; }); window_->window().on_close_requested([this] { + show_pending_ = false; CancelDrag(); window_->hide(); return slint::CloseRequestResponse::KeepWindowShown; }); @@ -246,19 +251,23 @@ bool FileBrowserWindow::CenterOnScreen() { } void FileBrowserWindow::Show() { if (!Tick()) return; - window_->window().set_minimized(false); - window_->show(); -#if defined(__APPLE__) - // Winit may create the NSWindow after show() returns. Retry on UI ticks, - // including when a hidden file window is opened again. - native_titlebar_attempts_ = - ConfigureWhiteWindowChrome(window_->window().appkit_view()) ? 0 : 30; -#endif - // Always recenter a reopened window. Only retry until the native frame is - // ready; subsequent UI ticks must leave user-driven window movement alone. - center_attempts_ = CenterOnScreen() ? 0 : 30; + if (Visible()) { + window_->window().set_minimized(false); + } else { + show_pending_ = true; + ShowWhenReady(); + } if (!panes_[1].busy && !panes_[1].error.empty()) Navigate(true, panes_[1].path); } +void FileBrowserWindow::ShowWhenReady() { + if (!show_pending_ || !NativeWindowReady(window_->window())) return; +#if defined(__APPLE__) + if (!ConfigureWhiteWindowChrome(window_->window().appkit_view())) return; +#endif + if (!CenterOnScreen()) return; + show_pending_ = false; + window_->show(); +} void FileBrowserWindow::OpenDirectory(bool remote, const std::string& path) { const auto& pane = panes_[remote]; if (!SessionConnected() || !pane.loaded || pane.busy || !pane.error.empty() || @@ -480,24 +489,14 @@ bool FileBrowserWindow::Tick() { // directory/progress refresh. All native window operations stay on this thread. if (!SessionConnected()) { disconnected_ = true; + show_pending_ = false; window_->set_connected(false); CancelDrag(); requests_.clear(); window_->hide(); return false; } -#if defined(__APPLE__) - if (native_titlebar_attempts_ > 0 && Visible()) { - if (ConfigureWhiteWindowChrome(window_->window().appkit_view())) - native_titlebar_attempts_ = 0; - else - --native_titlebar_attempts_; - } -#endif - if (center_attempts_ > 0 && Visible()) { - if (CenterOnScreen()) center_attempts_ = 0; - else --center_attempts_; - } + ShowWhenReady(); const auto now = std::chrono::steady_clock::now(); if (now < next_sync_) return true; next_sync_ = now + std::chrono::milliseconds(150); diff --git a/apps/desktop/src/gui/features/file_transfer/file_browser_window.h b/apps/desktop/src/gui/features/file_transfer/file_browser_window.h index bc67c762..4c9231ac 100644 --- a/apps/desktop/src/gui/features/file_transfer/file_browser_window.h +++ b/apps/desktop/src/gui/features/file_transfer/file_browser_window.h @@ -61,6 +61,7 @@ class FileBrowserWindow { void Navigate(bool remote, const std::string& path, bool tree = false); void OpenDirectory(bool remote, const std::string& path); bool CenterOnScreen(); + void ShowWhenReady(); void UpdatePane(bool remote); void SortEntries(Pane& pane); void UpdateTree(bool remote); @@ -82,10 +83,7 @@ class FileBrowserWindow { uint64_t connection_generation_; bool chinese_; bool disconnected_ = false; - int center_attempts_ = 0; -#if defined(__APPLE__) - int native_titlebar_attempts_ = 0; -#endif + bool show_pending_ = false; slint::ComponentHandle window_ = ui::FileBrowserWindow::create(); Pane panes_[2]; std::shared_ptr drag_owner_ = std::make_shared(0);