From 18ae58a3d2387de3b2d6bb344b6873cee26cfb20 Mon Sep 17 00:00:00 2001 From: evgeny Date: Tue, 3 Nov 2026 00:36:24 +0100 Subject: [PATCH] Move device linking into an eligible member context action with confirmation --- tools/chatgui/src/accountlist.cpp | 39 +++++++++++++++++++++++++ tools/chatgui/src/accountlist.h | 2 ++ tools/chatgui/src/memberpropsdialog.cpp | 12 ++++---- 3 files changed, 47 insertions(+), 6 deletions(-) diff --git a/tools/chatgui/src/accountlist.cpp b/tools/chatgui/src/accountlist.cpp index b90608a71..e7e5e8a90 100644 --- a/tools/chatgui/src/accountlist.cpp +++ b/tools/chatgui/src/accountlist.cpp @@ -613,6 +613,42 @@ void AccountList::onMemberDoubleClicked(const QModelIndex& index) { editMember(index.data(MemberListModel::NodeIdRole).toULongLong(), true); } +bool AccountList::canLinkMyDevice(quint64 nodeId) const { + const auto member = m_model->member(nodeId); + static const uint8_t noGroup[32] = {}; + return member && !member->is_self && member->online && (member->conn_up & NCONN_BGP) + && !(member->flags & CHAT_MEMBER_FLAG_DELETED) && !memcmp(member->devgroup, noGroup, sizeof(noGroup)); +} + +void AccountList::confirmMyDeviceLink(quint64 nodeId) { + const auto member = m_model->member(nodeId); + if (!canLinkMyDevice(nodeId) || !gui_bridge_is_uasync_ready()) { + DEBUG_WARN(DEBUG_CATEGORY_CHAT, "device link: target unavailable channel=%s node=%016llx", qPrintable(m_channelId), + (unsigned long long)nodeId); return; + } + const QString channel = m_channelId; QPointer alive(this); + QPointer dialog = new QMessageBox(QMessageBox::Information, + QCoreApplication::translate("ChatGui", "This is my device"), + QCoreApplication::translate("ChatGui", "Link %1 to your device subgroup. The device will receive the subgroup's shared keys.\n\n" + "After you start linking, a PIN will appear here. Enter it on the selected device within two minutes.") + .arg(QString::fromUtf8(member->name)), QMessageBox::Cancel, this); + const auto release = qScopeGuard([dialog] { if (dialog) delete dialog.data(); }); + dialog->setObjectName("linkMyDeviceConfirmation"); dialog->setTextFormat(Qt::PlainText); + auto* link = dialog->addButton(QCoreApplication::translate("ChatGui", "Start linking"), QMessageBox::AcceptRole); + dialog->setDefaultButton(QMessageBox::Cancel); + connect(m_model, &MemberListModel::snapshotChanged, dialog, [this, channel, nodeId, link] { + link->setEnabled(channel == m_channelId && canLinkMyDevice(nodeId) && gui_bridge_is_uasync_ready()); + }); + DEBUG_INFO(DEBUG_CATEGORY_CHAT, "device link: explanation opened channel=%s node=%016llx", qPrintable(channel), + (unsigned long long)nodeId); + dialog->exec(); + if (!alive || !dialog || dialog->clickedButton() != link) return; + if (channel != m_channelId || !canLinkMyDevice(nodeId) || !gui_bridge_is_uasync_ready()) { + DEBUG_WARN(DEBUG_CATEGORY_CHAT, "device link: cancelled after target changed node=%016llx", (unsigned long long)nodeId); return; + } + inviteMyDevice(this, channel, nodeId); +} + /* Вложенный event loop меню допускает смену канала, снимка и уничтожение панели. */ void AccountList::onMemberContextMenu(const QPoint& pos) { auto* view = qobject_cast(sender()); if (!view) view = m_listView; @@ -657,6 +693,8 @@ void AccountList::onMemberContextMenu(const QPoint& pos) { QAction* editAction = menu.addAction(QCoreApplication::translate("ChatGui", "Edit")); editAction->setEnabled(editable); QAction* filesAction = menu.addAction(QCoreApplication::translate("ChatGui", "Member files")); + QAction* myDeviceAction = canLinkMyDevice(nodeId) ? menu.addAction(QCoreApplication::translate("ChatGui", "This is my device")) : nullptr; + if (myDeviceAction) myDeviceAction->setEnabled(gui_bridge_is_uasync_ready()); QAction* chosen = menu.exec(view->viewport()->mapToGlobal(pos)); if (!alive || !chosen) return; const auto current = m_model->member(nodeId); @@ -674,6 +712,7 @@ void AccountList::onMemberContextMenu(const QPoint& pos) { return; } const QString name = QString::fromUtf8(current->name); + if (myDeviceAction && chosen == myDeviceAction) { confirmMyDeviceLink(nodeId); return; } if (chosen == inviteAction) { emit inviteRequested(channel, nodeId, name); return; } if (chosen == radioCallAction) { emit radioCallRequested(channel, nodeId); return; } if (chosen == filesAction) { emit filesRequested(channel, nodeId); return; } diff --git a/tools/chatgui/src/accountlist.h b/tools/chatgui/src/accountlist.h index 8c35c3159..a5322cfbd 100644 --- a/tools/chatgui/src/accountlist.h +++ b/tools/chatgui/src/accountlist.h @@ -103,6 +103,8 @@ private: void updateGroupDetail(); // актуализировать список после изменения общего снимка void renameGroup(QString group); // только своя devgroup; повторная проверка после диалога quint64 ownGroupNode(const QString& group) const; // self текущей группы, 0 для чужой/удалённой + bool canLinkMyDevice(quint64 nodeId) const; // Чужой неудалённый узел с живым BGP-маршрутом и без devgroup. + void confirmMyDeviceLink(quint64 nodeId); // Объяснение, затем явный старт с повторной проверкой выбранного канала/узла. static QString relativeTimeStr(qint64 timestamp); static QString addrStr(const QByteArray& addr, int family); diff --git a/tools/chatgui/src/memberpropsdialog.cpp b/tools/chatgui/src/memberpropsdialog.cpp index 30bc2c852..cda6ba0e2 100644 --- a/tools/chatgui/src/memberpropsdialog.cpp +++ b/tools/chatgui/src/memberpropsdialog.cpp @@ -124,12 +124,12 @@ MemberPropsDialog::MemberPropsDialog(DbManager* db, const QString& channelId, layout->addWidget(hint); } - auto* addDevice = new QPushButton(QCoreApplication::translate("ChatGui", isOwn ? "Disconnect from my devices" : "Add device"), this); - addDevice->setObjectName("memberAddMyDevice"); addDevice->setEnabled(loaded && !curDeleted && (!isOwn || inDeviceGroup)); - connect(addDevice, &QPushButton::clicked, this, [this, addDevice] { - if (m_isOwn) disconnectMyDevice(addDevice); else inviteMyDevice(this, m_channelId, m_nodeId); - }); - layout->addWidget(addDevice); + if (isOwn) { + auto* disconnect = new QPushButton(QCoreApplication::translate("ChatGui", "Disconnect from my devices"), this); + disconnect->setObjectName("memberAddMyDevice"); disconnect->setEnabled(loaded && !curDeleted && inDeviceGroup); + connect(disconnect, &QPushButton::clicked, this, [disconnect] { disconnectMyDevice(disconnect); }); + layout->addWidget(disconnect); + } m_transferBtn = new QPushButton(QCoreApplication::translate("ChatGui", "Transfer admin rights"), this); m_transferBtn->setEnabled(haveKey && !isOwn);