From 57212227243a6a2fdce84d0bbf0b47994d6beee2 Mon Sep 17 00:00:00 2001 From: dijunkun Date: Wed, 14 Oct 2026 10:20:20 +0800 Subject: [PATCH] [fix] disable already added devices in DeskHub recent picker --- .../src/gui/application/gui_application.cpp | 25 +++++++++++++++--- .../gui/features/gallery/gallery_workspace.h | 3 +++ apps/desktop/src/gui/ui/gallery_view.slint | 26 ++++++++++--------- apps/desktop/src/gui/ui/main_window.slint | 4 ++- docs/deskhub.md | 2 +- 5 files changed, 43 insertions(+), 17 deletions(-) diff --git a/apps/desktop/src/gui/application/gui_application.cpp b/apps/desktop/src/gui/application/gui_application.cpp index 127b7cea..07ef490e 100644 --- a/apps/desktop/src/gui/application/gui_application.cpp +++ b/apps/desktop/src/gui/application/gui_application.cpp @@ -4628,7 +4628,9 @@ bool GuiApplication::ImportGalleryRecent(const std::string& input_group) { std::vector ids; for (size_t i = 0; i < ui_->gallery_recent_model->row_count(); ++i) { const auto device = ui_->gallery_recent_model->row_data(i); - if (device && device->selected) ids.emplace_back(device->id); + if (device && device->selected && !device->added && + !ui_->gallery.Contains(std::string(device->id))) + ids.emplace_back(device->id); } if (ids.empty()) return false; const auto group = Trim(input_group); @@ -4773,6 +4775,7 @@ void GuiApplication::GalleryAction(const std::string& action, ? wall.groups[group_index - 1] : "")); std::vector devices; std::set seen; + int available = 0; for (const auto& [_, recent] : recent_connections_) { if (!gallery::Workspace::ValidId(recent.remote_id) || !seen.insert(recent.remote_id).second) @@ -4781,25 +4784,31 @@ void GuiApplication::GalleryAction(const std::string& action, device.id = UiText(recent.remote_id); device.name = UiText(settings_.RecentConnectionDisplayName(recent)); device.online = device_presence_cache_.IsOnline(recent.remote_id); + device.added = wall.Contains(recent.remote_id); + device.selected = device.added; + if (!device.added) ++available; devices.push_back(std::move(device)); } ui_->gallery_recent_model->set_vector(std::move(devices)); ui_->wall->set_gallery_recent_devices(ui_->gallery_recent_model); ui_->wall->set_gallery_recent_selected(0); + ui_->wall->set_gallery_recent_available(available); return; } if (action == "recent-close") { ui_->gallery_recent_model->set_vector({}); ui_->wall->set_gallery_recent_selected(0); + ui_->wall->set_gallery_recent_available(0); ui_->wall->set_gallery_notice(""); return; } if (action == "recent-select" || action == "recent-all") { const bool select_all = ui_->wall->get_gallery_recent_selected() < - int(ui_->gallery_recent_model->row_count()); + ui_->wall->get_gallery_recent_available(); int count = 0; for (size_t i = 0; i < ui_->gallery_recent_model->row_count(); ++i) { auto device = *ui_->gallery_recent_model->row_data(i); + if (device.added) continue; if (action == "recent-all" || std::string(device.id) == value) { device.selected = action == "recent-all" ? select_all : !device.selected; ui_->gallery_recent_model->set_row_data(i, device); @@ -5179,14 +5188,24 @@ void GuiApplication::SyncGallery() { } if (ui_->wall->get_gallery_importing()) { // Keep the picker selection and ordering stable when presence changes. + int available = 0, selected = 0; for (size_t i = 0; i < ui_->gallery_recent_model->row_count(); ++i) { auto device = *ui_->gallery_recent_model->row_data(i); const bool online = device_presence_cache_.IsOnline(std::string(device.id)); - if (device.online != online) { + const bool added = ui_->gallery.Contains(std::string(device.id)); + if (device.online != online || device.added != added) { device.online = online; + if (device.added != added) device.selected = added; + device.added = added; ui_->gallery_recent_model->set_row_data(i, device); } + if (!device.added) { + ++available; + if (device.selected) ++selected; + } } + ui_->wall->set_gallery_recent_available(available); + ui_->wall->set_gallery_recent_selected(selected); } const auto tr = [this](const char* zh, const char* en, const char* ru) -> std::string { diff --git a/apps/desktop/src/gui/features/gallery/gallery_workspace.h b/apps/desktop/src/gui/features/gallery/gallery_workspace.h index c003eb9b..f7c2167e 100644 --- a/apps/desktop/src/gui/features/gallery/gallery_workspace.h +++ b/apps/desktop/src/gui/features/gallery/gallery_workspace.h @@ -62,6 +62,9 @@ struct Workspace { std::all_of(id.begin(), id.end(), [](unsigned char c) { return c >= '0' && c <= '9'; }); } + bool Contains(const std::string &id) const { + return wall_ids_.count(id) != 0; + } bool Add(const std::string &id, const std::string &group = {}) { if (!ValidId(id)) return false; diff --git a/apps/desktop/src/gui/ui/gallery_view.slint b/apps/desktop/src/gui/ui/gallery_view.slint index 302ee410..2c4cb395 100644 --- a/apps/desktop/src/gui/ui/gallery_view.slint +++ b/apps/desktop/src/gui/ui/gallery_view.slint @@ -33,6 +33,7 @@ export struct GalleryRecentDevice { id: string, name: string, selected: bool, + added: bool, online: bool, } export struct GalleryActionDevice { @@ -159,6 +160,7 @@ export component GalleryView inherits Rectangle { in-out property importing: false; in property <[GalleryRecentDevice]> recent-devices; in property recent-selected: 0; + in property recent-available: 0; in property import-group; private property confirming: false; private property action-name; @@ -922,10 +924,10 @@ export component GalleryView inherits Rectangle { background: white; border-radius: 8px; border-width: 1px; border-color: #b2b2b2; Text { x: 18px; y: 16px; height: 24px; text: GalleryStrings.tr("从最近连接添加", "Add from recent connections", "Добавить из недавних"); font-size: 16px; } Text { x: 18px; y: 44px; width: 404px; height: 18px; text: GalleryStrings.tr("加入桌面墙后,可通过批量选择连接", "Add devices, then use Select to connect them.", "После добавления подключайтесь через выбор."); color: #62666d; font-size: 11px; } - ImGuiCheckBox { x: 18px; y: 74px; enabled: root.recent-devices.length > 0; checked: root.recent-devices.length > 0 && root.recent-selected == root.recent-devices.length; } - Text { x: 45px; y: 74px; height: 21px; text: GalleryStrings.tr("全选", "Select all", "Выбрать все"); font-size: 12px; vertical-alignment: center; } - TouchArea { x: 18px; y: 74px; width: 144px; height: 21px; enabled: root.recent-devices.length > 0; clicked => { root.action("recent-all", ""); } } - Text { x: 224px; y: 74px; width: 198px; height: 21px; text: GalleryStrings.tr("已选 ", "Selected ", "Выбрано ") + root.recent-selected + " / " + root.recent-devices.length; horizontal-alignment: right; vertical-alignment: center; font-size: 12px; color: #62666d; } + ImGuiCheckBox { x: 18px; y: 74px; enabled: root.recent-available > 0; checked: root.recent-devices.length > 0 && root.recent-selected == root.recent-available; } + Text { x: 45px; y: 74px; height: 21px; text: GalleryStrings.tr("全选", "Select all", "Выбрать все"); color: root.recent-available > 0 ? #24272d : #92969e; font-size: 12px; vertical-alignment: center; } + TouchArea { x: 18px; y: 74px; width: 144px; height: 21px; enabled: root.recent-available > 0; clicked => { root.action("recent-all", ""); } } + Text { x: 224px; y: 74px; width: 198px; height: 21px; text: GalleryStrings.tr("待添加 ", "To add ", "К добавлению ") + root.recent-selected + " / " + root.recent-available; horizontal-alignment: right; vertical-alignment: center; font-size: 12px; color: #62666d; } Rectangle { x: 18px; y: 102px; width: 404px; height: 160px; border-width: 1px; border-color: #d8dadd; border-radius: 4px; clip: true; @@ -935,17 +937,17 @@ export component GalleryView inherits Rectangle { viewport-height: max(self.height, root.recent-devices.length * 44px); for device[index] in root.recent-devices: Rectangle { y: index * 44px; width: recent-list.viewport-width; height: 44px; - background: device.selected ? #edf5fe : row-touch.has-hover ? #f4f5f6 : white; - ImGuiCheckBox { x: 8px; y: 11px; checked: device.selected; } - Text { x: 36px; y: 3px; width: parent.width - 126px; height: 20px; text: device.name; font-size: 12px; overflow: elide; vertical-alignment: center; } - Text { x: 36px; y: 23px; width: parent.width - 126px; height: 16px; text: device.id; font-size: 11px; color: #808080; } - Rectangle { x: parent.width - 74px; y: 19px; width: 6px; height: 6px; border-radius: 3px; background: device.online ? #21b567 : #92969e; } + background: device.added ? #f1f2f4 : device.selected ? #edf5fe : row-touch.has-hover ? #f4f5f6 : white; + ImGuiCheckBox { x: 8px; y: 11px; enabled: !device.added; checked: device.added || device.selected; } + Text { x: 36px; y: 3px; width: parent.width - 126px; height: 20px; text: device.name; color: device.added ? #92969e : #24272d; font-size: 12px; overflow: elide; vertical-alignment: center; } + Text { x: 36px; y: 23px; width: parent.width - 126px; height: 16px; text: device.id; font-size: 11px; color: device.added ? #92969e : #808080; } + Rectangle { x: parent.width - 74px; y: 19px; width: 6px; height: 6px; border-radius: 3px; background: !device.added && device.online ? #21b567 : #92969e; } Text { x: parent.width - 61px; y: 12px; width: 57px; height: 20px; - text: device.online ? GalleryStrings.tr("在线", "Online", "В сети") : GalleryStrings.tr("离线", "Offline", "Не в сети"); - color: device.online ? #17854c : #808080; font-size: 11px; vertical-alignment: center; + text: device.added ? GalleryStrings.tr("已添加", "Added", "Добавлено") : device.online ? GalleryStrings.tr("在线", "Online", "В сети") : GalleryStrings.tr("离线", "Offline", "Не в сети"); + color: device.added ? #92969e : device.online ? #17854c : #808080; font-size: 11px; vertical-alignment: center; } - row-touch := TouchArea { clicked => { root.action("recent-select", device.id); } } + row-touch := TouchArea { enabled: !device.added; clicked => { root.action("recent-select", device.id); } } } } if root.recent-devices.length == 0: Text { text: GalleryStrings.tr("暂无最近连接", "No recent connections", "Нет недавних подключений"); color: #808080; font-size: 12px; horizontal-alignment: center; vertical-alignment: center; } diff --git a/apps/desktop/src/gui/ui/main_window.slint b/apps/desktop/src/gui/ui/main_window.slint index bdc0ed0d..38c611cd 100644 --- a/apps/desktop/src/gui/ui/main_window.slint +++ b/apps/desktop/src/gui/ui/main_window.slint @@ -341,6 +341,7 @@ export component MainWindow inherits Window { in-out property gallery-importing: false; in property <[GalleryRecentDevice]> gallery-recent-devices; in property gallery-recent-selected: 0; + in property gallery-recent-available: 0; in property gallery-import-group; callback open-gallery(); callback toggle-gallery-fullscreen(); @@ -1337,7 +1338,8 @@ export component MainWindow inherits Window { selected-devices: root.gallery-selected-devices; focused: root.gallery-focused; batch <=> root.gallery-batch; adding <=> root.gallery-adding; importing <=> root.gallery-importing; recent-devices: root.gallery-recent-devices; - recent-selected: root.gallery-recent-selected; import-group: root.gallery-import-group; + recent-selected: root.gallery-recent-selected; + recent-available: root.gallery-recent-available; import-group: root.gallery-import-group; action(code, value) => { root.gallery-action(code, value); } swap-devices(source, target) => { root.gallery-swap(source, target) } add-device(id, password, group, remember) => { root.gallery-add-device(id, password, group, remember) } diff --git a/docs/deskhub.md b/docs/deskhub.md index a21bb5de..f16e2a10 100644 --- a/docs/deskhub.md +++ b/docs/deskhub.md @@ -11,7 +11,7 @@ ## 使用 - 在桌面墙中点“添加 → 通过设备 ID 添加”,填写设备 ID、密码和可选的分组名称,再点“添加并连接”。密码仅用于本次连接,勾选“记住密码”才会通过原有凭据机制保存;不会写入桌面墙配置。 -- “添加 → 从近期连接添加”支持多选、全选和统一指定分组。导入后只加入桌面墙,使用“选择 → 连接”再发起连接。同一分组内重复设备会跳过;不指定分组时,已在桌面墙中的设备不会重复加入。 +- “添加 → 从近期连接添加”支持多选、全选和统一指定分组。已加入桌面墙的设备显示为灰色、保持勾选并标注“已添加”,不可操作,不受当前分组筛选影响;全选和待添加数量只计算尚未加入的设备,全部已添加时禁用添加按钮。导入后只加入桌面墙,使用“选择 → 连接”再发起连接。 - 连接成功后立即记录在连接页的“最近连接”中,无需等到断开,也不要求记住密码。失败的连接不会新增最近记录。 - “全部设备”显示保存的成员及当前活动会话。分组成员与连接相互独立:断开后成员仍在,重启后不会自动连接。 - 卡片名称栏只显示设备名,长名称省略,不再显示状态圆点。未在线的设备(离线或在线状态未知)整张卡片使用灰色,包括预览背景、边框和名称;在线但尚未连接的设备保持正常样式。选中边框、复选框和操作入口仍可用。悬停画面可查看设备名称、ID 和在线状态,精简模式也支持。在线状态订阅包含全部分组中的设备;尚未收到结果、结果过期或服务器断连时显示未知,已有活动连接的设备仍显示在线。