Add local per-member radio mute controls on desktop and Android

This commit is contained in:
evgeny committed 2026-10-07 13:35:25 +02:00
1 parent a069d11ef9
commit 7e19cbaf3a
33 files changed
+735 -27

No files matched your search

+1
View File
@@ -64,6 +64,7 @@ void chat_core_ensure_channel_ready(struct UTUN_INSTANCE* inst, const char* ch_i
if (!cc || !cc->initialized || !ch_id || !ch_id[0]) return;
if (chat_feedback_ensure_channel(inst, ch_id) < 0) return;
if (si_find(inst, ch_id)) return;
if (topo_node_sqlite_member_local_init(cc->db, ch_id) < 0) return;
char tbl_msg[80]; msg_table_name(ch_id, tbl_msg, sizeof(tbl_msg));
+1
View File
@@ -50,6 +50,7 @@ void chat_event_post(struct UTUN_INSTANCE* inst, int type, const uint8_t* data,
[43]="RADIO_TALK",
[44]="CALL_CONNECTION",
[45]="DM_CONVERSATIONS", [46]="ATTACHMENT_PREPARATION", [47]="DM_CALL_ROUTE",
[55]="RADIO_MUTE_RESULT",
};
const char* n = (type >= 1 && type < (int)(sizeof(names) / sizeof(names[0]))) ? names[type] : "?";
DEBUG_DEBUG(DEBUG_CATEGORY_CHAT, "chat_event: %s(%d) data=%d bytes", n, type, len);
+1
View File
@@ -76,6 +76,7 @@ extern "C" {
#define CHAT_EVT_GROUP_AVATAR_RESULT 52 /* request_id/group:8LE, failed:1; результат публикации аватарки группы */
#define CHAT_EVT_FEEDBACK_CHANGED 53 /* [channel_len:1][channel][message_id:32][kind:1] — состояние после commit */
#define CHAT_EVT_FEEDBACK_RESULT 54 /* [request_id:8LE][success:1][JSON] — результат команды интерфейса */
#define CHAT_EVT_RADIO_MUTE_RESULT 55 /* [group_id:8LE][node_id:8LE][success:1] — локальный переключатель рации */
typedef void (*chat_event_handler_fn)(struct UTUN_INSTANCE* inst, int type, const uint8_t* data, int len);
+34 -2
View File
@@ -13,6 +13,7 @@
#include "../utun_instance.h"
#include "../routing_layer/topo_group.h"
#include "../radio/radio.h"
#include "../../lib/mem.h"
#include "../../lib/ll_queue.h"
#include "../../lib/json_flat.h"
@@ -148,7 +149,7 @@ int chat_core_get_member_list(struct UTUN_INSTANCE* inst, const char* ch_id, uin
snprintf(sql, sizeof(sql),
"SELECT p.node_id, COALESCE(n.name,''),"
" COALESCE(p.local_nick,''), COALESCE(p.adm_tags,''),"
" COALESCE(p.storage,0), COALESCE(p.deleted,0), COALESCE(p.userinfo,''),p.source,p.adm_tags_sig"
" COALESCE(p.storage,0), COALESCE(p.deleted,0), COALESCE(p.userinfo,''),p.source,p.adm_tags_sig,p.radio_muted"
" FROM \"%s\" p LEFT JOIN nodes n ON p.node_id=n.node_id"
" ORDER BY p.node_id ASC", peers_tbl);
@@ -179,6 +180,7 @@ int chat_core_get_member_list(struct UTUN_INSTANCE* inst, const char* ch_id, uin
m->storage_role = member_storage_role(adm_tags, st, 7, 8, sqlite3_column_int(st, 5));
m->is_self = (uint8_t)(m->node_id == my_id ? 1 : 0);
m->flags = parse_adm_tags_flags(adm_tags) | member_radio_flag(inst, ch_id, m->node_id);
if (sqlite3_column_int(st, 9)) m->flags |= CHAT_MEMBER_FLAG_RADIO_MUTED;
if (sqlite3_column_int(st, 5)) m->flags |= CHAT_MEMBER_FLAG_DELETED;
m->rtt = 0xFFFF;
m->nat_type = 0xFF;
@@ -277,7 +279,7 @@ int chat_core_get_single_member(struct UTUN_INSTANCE* inst, const char* ch_id, u
snprintf(sql, sizeof(sql),
"SELECT COALESCE(n.name,''),"
" COALESCE(p.local_nick,''), COALESCE(p.adm_tags,''),"
" COALESCE(p.storage,0), COALESCE(p.deleted,0), COALESCE(p.userinfo,''),p.source,p.adm_tags_sig"
" COALESCE(p.storage,0), COALESCE(p.deleted,0), COALESCE(p.userinfo,''),p.source,p.adm_tags_sig,p.radio_muted"
" FROM \"%s\" p LEFT JOIN nodes n ON p.node_id=n.node_id"
" WHERE p.node_id=?", peers_tbl);
@@ -296,6 +298,7 @@ int chat_core_get_single_member(struct UTUN_INSTANCE* inst, const char* ch_id, u
m.storage_role = member_storage_role(adm_tags, st, 6, 7, sqlite3_column_int(st, 4));
m.is_self = (uint8_t)(node_id == my_id ? 1 : 0);
m.flags = parse_adm_tags_flags(adm_tags) | member_radio_flag(inst, ch_id, node_id);
if (sqlite3_column_int(st, 8)) m.flags |= CHAT_MEMBER_FLAG_RADIO_MUTED;
if (sqlite3_column_int(st, 4)) m.flags |= CHAT_MEMBER_FLAG_DELETED;
m.rtt = 0xFFFF;
m.nat_type = 0xFF;
@@ -333,6 +336,35 @@ int chat_core_get_single_member(struct UTUN_INSTANCE* inst, const char* ch_id, u
return 0;
}
int chat_core_toggle_member_radio_mute(struct UTUN_INSTANCE* inst, const char* ch_id, uint64_t node_id) {
uint8_t member[CHAT_MEMBER_DISPLAY_SIZE];
if (!ch_id || !ch_id[0] || strlen(ch_id) >= 64 || chat_core_get_single_member(inst, ch_id, node_id, member) < 0) {
DEBUG_WARN(DEBUG_CATEGORY_RADIO, "radio: mute toggle member not found node=%016llx", (unsigned long long)node_id); return -1;
}
int muted = !(member[10] & CHAT_MEMBER_FLAG_RADIO_MUTED);
if (radio_set_member_muted(inst, strtoull(ch_id, NULL, 10), node_id, muted) < 0) return -1;
if (chat_core_get_single_member(inst, ch_id, node_id, member) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "radio: cannot publish local mute ch=%s node=%016llx",
ch_id, (unsigned long long)node_id); return -1;
}
size_t cl = strlen(ch_id);
uint8_t evt[1 + 64 + 1 + CHAT_MEMBER_DISPLAY_SIZE];
evt[0] = (uint8_t)cl; memcpy(evt + 1, ch_id, cl); evt[1 + cl] = 1;
memcpy(evt + 2 + cl, member, sizeof(member));
chat_event_post(inst, CHAT_EVT_MEMBER_UPDATED, evt, (int)(cl + 2 + sizeof(member)));
return 0;
}
void chat_core_toggle_member_radio_mute_trampoline(void* arg) {
struct chat_member_radio_mute_req* req = arg;
if (!req) { DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "radio: null member mute request"); return; }
int rc = chat_core_toggle_member_radio_mute(req->inst, req->ch_id, req->node_id);
uint8_t result[17];
mf_put64(result, strtoull(req->ch_id, NULL, 10)); mf_put64(result + 8, req->node_id); result[16] = rc == 0;
chat_event_post(req->inst, CHAT_EVT_RADIO_MUTE_RESULT, result, sizeof(result));
u_free(req);
}
/* ─── трамплины для gui_bridge ─── */
void chat_core_request_member_list_trampoline(void* arg) {
+7 -1
View File
@@ -9,7 +9,7 @@
* [0-7] node_id uint64_t LE
* [8] online uint8_t
* [9] is_self uint8_t
* [10] flags uint8_t 0x01=supernode 0x02=admin 0x04=moder 0x08=deleted 0x10=radio
* [10] flags uint8_t 0x01=supernode 0x02=admin 0x04=moder 0x08=deleted 0x10=radio 0x20=local radio mute
* [11-12] rtt uint16_t LE (ms, 0xFFFF=нет данных)
* [13] nat_type uint8_t ADDR_TYPE_*
* [14] storage uint8_t
@@ -34,6 +34,7 @@ extern "C" {
#define CHAT_MEMBER_FLAG_MODER 0x04
#define CHAT_MEMBER_FLAG_DELETED 0x08
#define CHAT_MEMBER_FLAG_RADIO 0x10
#define CHAT_MEMBER_FLAG_RADIO_MUTED 0x20 /* локальное отключение голоса участника */
#define CHAT_MEMBER_DISPLAY_SIZE 158
@@ -74,6 +75,11 @@ void chat_core_request_member_list_trampoline(void* arg);
* arg = struct chat_member_list_req*. Результат: CHAT_EVT_MEMBER_UPDATED с RTT-обновлениями. */
void chat_core_request_member_rtt_trampoline(void* arg);
/* Локальный переключатель рации; каждый запрос меняет актуальное состояние в uasync. */
int chat_core_toggle_member_radio_mute(struct UTUN_INSTANCE* inst, const char* ch_id, uint64_t node_id);
struct chat_member_radio_mute_req { struct UTUN_INSTANCE* inst; char ch_id[64]; uint64_t node_id; };
void chat_core_toggle_member_radio_mute_trampoline(void* arg);
/* Инициализация: регистрирует node_props_changed callback для рассылки MEMBER_UPDATED. */
void chat_member_init(struct UTUN_INSTANCE* inst);
+2
View File
@@ -3,6 +3,7 @@
#include "../routing_layer/topo_node_sqlite.h"
#include "../routing_layer/topo_group.h"
#include "../radio/radio.h"
#include "chat_core.h"
#include "chat_core_priv.h"
#include "../utun_instance.h"
@@ -884,6 +885,7 @@ int member_sync_verify_and_purge(struct UTUN_INSTANCE* inst, const char* ch_id)
DEBUG_ERROR(DEBUG_CATEGORY_MEMBER_SYNC, "%s: verify_and_purge — PURGE broken record nid=0x%016llx ns=%s",
MS_ID, (unsigned long long)nid, ch_id);
topo_node_sqlite_member_del(db, ch_id, nid);
radio_forget_member_mute(inst, strtoull(ch_id, NULL, 10), nid);
merkle_sync_recompute_path(inst, MT_DATA_MEMBERS, ch_id, nid);
/* пассивное удаление: если узел подключён напрямую — убрать из CHAT-группы */
if (inst->topo_groups) {