android: member list screen + link status dots, ipv6 autoconnect fixes
Android chatgui: - New MemberListScreen with member list, detail card, edit dialog - Link status dots (green/red/grey) per address in detail card (1s poll) - Self-node name coloured green - ChatScreen: people icon in TopAppBar IPv6 autoconnect fixes (3 bugs in conn_mgr/topo): - topo_node_sqlite_node_load: load v6 addresses from DB - topo_node_registry_store: merge v6_addrs into existing node - cm_sock_v6_classify: wildcard [::] treated as CM_V6_DIR - cm_add_v6_link: htons(port) was missing Memory fixes: - strdup→u_strdup in bridge (7 instances) - u_realloc cap update before success check (3 instances) - char sql[256]→[512] for truncated SELECT Chat member hash now includes addresses for change detection Linux chatgui: - Link status dots in AccountList detail panel (1s poll) - Self-node name in green in member list
This commit is contained in:
1 parent
6719a1e23c
commit
50156e12af
19 files changed
+1022
-23
No files matched your search
@@ -35,6 +35,7 @@ void AccountDelegate::paint(QPainter *painter, const QStyleOptionViewItem &optio
|
||||
|
||||
QColor textColor = option.palette.color(QPalette::Text);
|
||||
if (option.state & QStyle::State_Selected) textColor = option.palette.color(QPalette::HighlightedText);
|
||||
if (index.data(Qt::UserRole + 2).toBool()) textColor = QColor("#4CAF50");
|
||||
painter->setPen(textColor);
|
||||
|
||||
QString name = index.data(Qt::DisplayRole).toString();
|
||||
|
||||
@@ -10,6 +10,13 @@
|
||||
#include <QFont>
|
||||
#include "../../../lib/platform_compat.h"
|
||||
|
||||
extern "C" {
|
||||
#include "utun_instance.h"
|
||||
#include "etcp.h"
|
||||
#include "etcp_connections.h"
|
||||
#include "../lib/socket_compat.h"
|
||||
}
|
||||
|
||||
QString AccountList::addrStr(const void* addr, int family) {
|
||||
if (family == 4) {
|
||||
const uint8_t* a = (const uint8_t*)addr;
|
||||
@@ -84,12 +91,19 @@ AccountList::AccountList(DbManager* db, QWidget *parent)
|
||||
m_detailTimer = new QTimer(this);
|
||||
m_detailTimer->setInterval(5000);
|
||||
connect(m_detailTimer, &QTimer::timeout, this, &AccountList::onDetailTimerTick);
|
||||
|
||||
m_linkTimer = new QTimer(this);
|
||||
m_linkTimer->setInterval(1000);
|
||||
connect(m_linkTimer, &QTimer::timeout, this, [this]{
|
||||
if (m_detailNodeId != 0) updateDetailDisplay();
|
||||
});
|
||||
}
|
||||
|
||||
void AccountList::setChannel(const QString& channelId) {
|
||||
m_channelId = channelId;
|
||||
m_model->setChannel(channelId);
|
||||
m_detailTimer->stop();
|
||||
m_linkTimer->stop();
|
||||
m_detailNodeId = 0;
|
||||
m_detailText->clear();
|
||||
}
|
||||
@@ -106,6 +120,7 @@ void AccountList::showMemberDetail(quint64 nodeId) {
|
||||
if (!m_db || !m_db->m_db) return;
|
||||
|
||||
m_detailTimer->stop();
|
||||
m_linkTimer->stop();
|
||||
m_detailNodeId = nodeId;
|
||||
m_detailName.clear();
|
||||
m_detailOnline = false;
|
||||
@@ -166,6 +181,7 @@ void AccountList::showMemberDetail(quint64 nodeId) {
|
||||
}
|
||||
|
||||
m_detailTimer->start();
|
||||
m_linkTimer->start();
|
||||
updateDetailDisplay();
|
||||
}
|
||||
|
||||
@@ -184,10 +200,35 @@ void AccountList::updateDetailDisplay() {
|
||||
if (m_detailCreated > 0)
|
||||
lines.append(QString("📅 %1").arg(relativeTimeStr(m_detailCreated)));
|
||||
|
||||
struct ETCP_CONN* conn = nullptr;
|
||||
if (m_inst) conn = instance_find_conn(m_inst, m_detailNodeId);
|
||||
|
||||
for (const auto& a : m_detailAddrs) {
|
||||
QString proto;
|
||||
switch (a.protocol) { case 1: proto = "UDP"; break; case 2: proto = "TCP"; break; default: proto = "UDP/TCP"; break; }
|
||||
lines.append(QString("%1:%2:%3").arg(proto, addrStr(a.addr.constData(), a.family)).arg(a.port));
|
||||
QString linkColor = "#9E9E9E";
|
||||
if (conn) {
|
||||
for (struct ETCP_LINK* lk = conn->links; lk; lk = lk->next) {
|
||||
const struct sockaddr_storage* sa = &lk->remote_addr;
|
||||
int lfm = 0; QString lip; uint16_t lpo = 0;
|
||||
char ipbuf[INET6_ADDRSTRLEN];
|
||||
if (sa->ss_family == AF_INET) {
|
||||
struct sockaddr_in* sin = (struct sockaddr_in*)sa;
|
||||
inet_ntop(AF_INET, &sin->sin_addr, ipbuf, sizeof(ipbuf));
|
||||
lfm = 4; lpo = ntohs(sin->sin_port); lip = QString::fromLocal8Bit(ipbuf);
|
||||
} else if (sa->ss_family == AF_INET6) {
|
||||
struct sockaddr_in6* sin6 = (struct sockaddr_in6*)sa;
|
||||
inet_ntop(AF_INET6, &sin6->sin6_addr, ipbuf, sizeof(ipbuf));
|
||||
lfm = 6; lpo = ntohs(sin6->sin6_port); lip = QString::fromLocal8Bit(ipbuf);
|
||||
}
|
||||
if (lfm == a.family && lpo == (uint16_t)a.port && lip == addrStr(a.addr.constData(), a.family)) {
|
||||
linkColor = (lk->link_state == 3 && lk->link_status == 1) ? "#4CAF50" : "#E53935";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
lines.append(QString("<span style='color:%1;'>●</span> %2:%3:%4")
|
||||
.arg(linkColor, proto, addrStr(a.addr.constData(), a.family)).arg(a.port));
|
||||
}
|
||||
|
||||
m_detailText->setHtml(lines.join("<br>"));
|
||||
|
||||
@@ -6,9 +6,11 @@
|
||||
#include <QTimer>
|
||||
#include <QMenu>
|
||||
#include <QVector>
|
||||
#include <QElapsedTimer>
|
||||
|
||||
class MemberListModel;
|
||||
class DbManager;
|
||||
struct UTUN_INSTANCE;
|
||||
|
||||
struct DetailAddress {
|
||||
int family;
|
||||
@@ -24,6 +26,7 @@ public:
|
||||
|
||||
void setChannel(const QString& channelId);
|
||||
void refresh();
|
||||
void setUtunInstance(struct UTUN_INSTANCE* inst) { m_inst = inst; }
|
||||
|
||||
signals:
|
||||
void memberRenamed();
|
||||
@@ -46,8 +49,11 @@ private:
|
||||
QTextEdit *m_detailText;
|
||||
QTimer *m_refreshTimer;
|
||||
QTimer *m_detailTimer;
|
||||
QTimer *m_linkTimer;
|
||||
QString m_channelId;
|
||||
|
||||
struct UTUN_INSTANCE* m_inst = nullptr;
|
||||
|
||||
quint64 m_detailNodeId = 0;
|
||||
QString m_detailName;
|
||||
bool m_detailOnline = false;
|
||||
|
||||
@@ -133,8 +133,9 @@ void MainWindow::setupNode() {
|
||||
connect(m_node, &UtunNode::error, this, [](const QString& msg) {
|
||||
qWarning("UtunNode error: %s", qPrintable(msg));
|
||||
});
|
||||
connect(m_node, &UtunNode::started, this, []() {
|
||||
connect(m_node, &UtunNode::started, this, [this]() {
|
||||
qDebug("UtunNode started");
|
||||
if (m_accountList && m_node) m_accountList->setUtunInstance(m_node->instance());
|
||||
});
|
||||
connect(m_node, &UtunNode::stopped, this, [this]() {
|
||||
qDebug("UtunNode stopped");
|
||||
|
||||
@@ -34,6 +34,7 @@ QVariant MemberListModel::data(const QModelIndex& index, int role) const {
|
||||
if (role == Qt::ToolTipRole)
|
||||
return QString(it->online ? "online" : "offline") + " | 0x" +
|
||||
QString("%1").arg(it->nodeId, 16, 16, QChar('0'));
|
||||
if (role == Qt::UserRole + 2) return it->isSelf;
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -90,6 +91,7 @@ void MemberListModel::loadBatch(int from, int count) {
|
||||
int row = from + i;
|
||||
MemberListItem& item = m_cache[row];
|
||||
item.nodeId = members[i].nodeId;
|
||||
item.isSelf = (item.nodeId == m_db->myNodeId());
|
||||
item.online = members[i].online;
|
||||
QString shortId = QString("%1").arg(members[i].nodeId, 8, 16, QChar('0')).right(4).toUpper();
|
||||
item.name = members[i].name.isEmpty() ? shortId : members[i].name;
|
||||
|
||||
@@ -13,11 +13,12 @@ class DbManager;
|
||||
struct MemberListItem {
|
||||
quint64 nodeId = 0;
|
||||
bool online = false;
|
||||
bool isSelf = false;
|
||||
QPixmap avatar;
|
||||
QString name;
|
||||
QString localNick;
|
||||
int rtt = -1; // ms (min из node_addresses, -1 если нет)
|
||||
int natType = -1; // addr_type: 0=NET, 1=DIR, 2=EIM, 3=STR
|
||||
int rtt = -1;
|
||||
int natType = -1;
|
||||
};
|
||||
|
||||
class MemberListModel : public QAbstractListModel {
|
||||
|
||||
@@ -31,6 +31,8 @@ public:
|
||||
void setDebugLevel(const QString& level);
|
||||
void setDebugCategories(const QString& categories);
|
||||
|
||||
struct UTUN_INSTANCE* instance() const { return m_instance; }
|
||||
|
||||
void send(uint64_t dstNodeId, const QByteArray& data);
|
||||
|
||||
QString nodeIdHex() const;
|
||||
|
||||
Reference in new issue
Block a user