From 1aa23126d2b2d32acdd425f33e9ef1784c5b8764 Mon Sep 17 00:00:00 2001 From: dijunkun Date: Wed, 21 Oct 2026 00:21:59 +0800 Subject: [PATCH] [fix] use in-app confirmation for update installation --- .../src/gui/application/gui_application.cpp | 24 +++-- apps/desktop/src/gui/ui/main_window.slint | 96 +++++++++++++++++++ apps/desktop/src/gui/ui/ui_localization.h | 2 + 3 files changed, 116 insertions(+), 6 deletions(-) diff --git a/apps/desktop/src/gui/application/gui_application.cpp b/apps/desktop/src/gui/application/gui_application.cpp index 776b5241..8b91d70b 100644 --- a/apps/desktop/src/gui/application/gui_application.cpp +++ b/apps/desktop/src/gui/application/gui_application.cpp @@ -1746,22 +1746,34 @@ void GuiApplication::BindMainCallbacks(bool gallery_window) { if (updater_ && updater_->busy()) updater_->Cancel(); #endif }); - main->on_open_download([this] { + main->on_open_download([this, main] { const auto& latest = update_checker_->latest(); #if CROSSDESK_HAS_UPDATER if (updater_ && updater_->busy()) return; if (updater_ && latest && updater_->Available(latest->update_envelope)) { - const int language = localization::detail::ClampLanguageIndex(localization_language_index_); - if (tinyfd_messageBox("CrossDesk", localization::install_update_confirm[language].c_str(), - "okcancel", "question", 0)) { - updater_->Start(latest->update_envelope); - } + main->set_about_open(false); + main->set_update_open(true); + main->set_update_confirm_open(true); return; } #endif OpenUrl(latest && !latest->download_url.empty() ? latest->download_url : "https://crossdesk.cn"); }); + main->on_confirm_update([this, main] { + if (!main->get_update_confirm_open()) return; + for (const auto& window : {ui_->main, ui_->wall}) + window->set_update_confirm_open(false); +#if CROSSDESK_HAS_UPDATER + const auto& latest = update_checker_->latest(); + if (!updater_ || updater_->busy() || !latest || + !updater_->Available(latest->update_envelope)) return; + updater_->Start(latest->update_envelope); + // Disable both windows immediately, before the next polling tick. + for (const auto& window : {ui_->main, ui_->wall}) + window->set_update_install_busy(updater_->busy()); +#endif + }); main->on_open_website([this] { OpenUrl("https://crossdesk.cn"); }); main->on_open_afdian_sponsor([this] { OpenUrl("https://afdian.com/a/kunkundi"); diff --git a/apps/desktop/src/gui/ui/main_window.slint b/apps/desktop/src/gui/ui/main_window.slint index ddb49688..9b4edb31 100644 --- a/apps/desktop/src/gui/ui/main_window.slint +++ b/apps/desktop/src/gui/ui/main_window.slint @@ -100,6 +100,7 @@ export global UiStrings { in-out property later: "Later"; in-out property download: "Download"; in-out property install-update: "Install update"; + in-out property install-update-confirm; in-out property input-password: "Input password"; in-out property reinput-password: "Incorrect password, please try again"; in-out property remember-password: "Remember password"; @@ -411,6 +412,12 @@ export component MainWindow inherits Window { in property update-available: false; in property update-install-available: false; in property update-install-busy: false; + changed update-install-available => { + if !root.update-install-available { root.update-confirm-open = false; } + } + changed update-install-busy => { + if root.update-install-busy { root.update-confirm-open = false; } + } in property update-install-status: ""; private property update-action-text: root.update-install-available ? UiStrings.install-update : UiStrings.download; in property update-check-status: idle; @@ -497,6 +504,7 @@ export component MainWindow inherits Window { callback cancel-self-hosted-settings; callback browse-save-path; callback open-download; + callback confirm-update; callback cancel-update; callback open-website; callback open-afdian-sponsor; @@ -534,6 +542,7 @@ export component MainWindow inherits Window { && !root.menu-open && !root.settings-open && !root.self-host-settings-open && !root.about-open && !(root.update-open && root.update-available) && !root.messages-open + && !root.update-confirm-open && !root.reset-password-open && !root.alias-open && !root.delete-open && !root.device-group-open && !root.connection-dialog-open && !root.permission-dialog-open && !root.wayland-notice-open @@ -545,6 +554,10 @@ export component MainWindow inherits Window { // Follow the first available update. Dismissing the dialog replaces this // binding, so background checks do not reopen it during this launch. in-out property update-open: root.update-available; + changed update-open => { + if !root.update-open { root.update-confirm-open = false; } + } + in-out property update-confirm-open: false; in-out property reset-password-open: false; in-out property reset-password-invalid: false; in-out property new-password-input: ""; @@ -2644,6 +2657,89 @@ export component MainWindow inherits Window { } } + // Keep confirmation inside the app instead of relying on external native + // dialogs and their platform-specific button labels. + if root.update-confirm-open: Rectangle { + background: #14233b66; + z: 48; + TouchArea {} + FocusScope { + width: parent.width; + height: parent.height; + key-pressed(event) => { + if event.text == Key.Escape { + root.update-confirm-open = false; + return accept; + } + if event.text == Key.Tab { + if update-confirm-cancel.has-focus { + update-confirm-install.focus(); + } else { + update-confirm-cancel.focus(); + } + return accept; + } + return reject; + } + Timer { + interval: 1ms; + running: true; + triggered => { + self.running = false; + // Defer focus until after Slint's accessibility update. + // Return defaults to cancelling this disruptive action. + update-confirm-cancel.focus(); + } + } + DialogSurface { + x: (parent.width - self.width) / 2; + y: (parent.height - self.height) / 2; + width: min(440px, parent.width - 40px); + height: update-confirm-content.preferred-height; + border-radius: UiRadius.surface; + border-color: WorkbenchStyle.border; + TouchArea {} + update-confirm-content := VerticalLayout { + padding: 24px; + spacing: 20px; + Text { + text: UiStrings.install-update; + color: WorkbenchStyle.text; + font-size: 18px; + font-weight: 600; + wrap: word-wrap; + } + Text { + text: UiStrings.install-update-confirm; + color: WorkbenchStyle.text; + font-size: 13px; + wrap: word-wrap; + } + HorizontalLayout { + alignment: end; + spacing: 8px; + update-confirm-cancel := WorkbenchButton { + text: UiStrings.cancel; + clicked => { root.update-confirm-open = false; } + } + update-confirm-install := WorkbenchButton { + width: max(112px, confirm-label-metrics.preferred-width + 24px); + text: UiStrings.install-update; + primary: true; + enabled: root.update-install-available && !root.update-install-busy; + clicked => { root.confirm-update(); } + confirm-label-metrics := Text { + visible: false; + text: UiStrings.install-update; + font-size: 13px; + } + } + } + } + } + } + } + // Linux Wayland sessions show this advisory once per application launch. if root.wayland-notice-open: Rectangle { background: #14233b66; diff --git a/apps/desktop/src/gui/ui/ui_localization.h b/apps/desktop/src/gui/ui/ui_localization.h index cb5eb9fd..1898167c 100644 --- a/apps/desktop/src/gui/ui/ui_localization.h +++ b/apps/desktop/src/gui/ui/ui_localization.h @@ -133,6 +133,8 @@ inline int ApplyMainWindowStrings( strings.set_view_release_notes(Text(localization::view_release_notes[language])); strings.set_download(Text(localization::manual_download[language])); strings.set_install_update(Text(localization::install_update[language])); + strings.set_install_update_confirm( + Text(localization::install_update_confirm[language])); strings.set_input_password(Text(localization::input_password[language])); strings.set_reinput_password(Text(localization::reinput_password[language])); strings.set_remember_password(