From 54ad03185baff319c6acf49c448aa41f57c63fac Mon Sep 17 00:00:00 2001 From: evgeny Date: Wed, 12 Aug 2026 18:05:36 +0300 Subject: [PATCH] topo_group_connect: infinite auto-connect cycle with pause + connecting nodes notification MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - client_activity: desktop/server now ACTIVE by default, only mobile starts STANDBY - topo_group_connect: endless Phase1→2→3→1 cycle until connected - Pause between failed Phase2/3 attempts: 1s active, 30s standby - on_down always triggers cycle_restart when active_conn_count=0 - re-init destroys previous state and starts fresh - CHAT_EVT_CONNECTING_NODES event notifies GUI of current connecting nodes - Android: ConfigProvider default mode='auto', PTT fix, UDP log timer fix --- src/chat/chat_event.c | 3 +- src/chat/chat_event.h | 1 + src/routing_layer/topo_group_connect.c | 130 +++++++++++++++--- src/routing_layer/topo_group_connect.h | 19 ++- src/utun_instance.c | 2 +- .../java/com/utun/chat/data/ConfigProvider.kt | 2 +- .../utun/chat/ui/components/MessageBubble.kt | 57 ++++---- .../utun/chat/ui/screens/SettingsScreen.kt | 4 +- .../jni_bridge/android_udp_log.c | 1 + .../libutun_lite/instance_lite.c | 1 - 10 files changed, 158 insertions(+), 62 deletions(-) diff --git a/src/chat/chat_event.c b/src/chat/chat_event.c index b246eaab..1dbe7825 100644 --- a/src/chat/chat_event.c +++ b/src/chat/chat_event.c @@ -37,8 +37,9 @@ void chat_event_post(int type, const uint8_t* data, int len) { [20]="CONN_LIST", [21]="CONN_METRICS", [22]="MEMBER_DETAIL", [23]="MEMBER_LIST", [24]="MEMBER_UPDATED", [25]="MEMBER_REMOVED", + [26]="BGP_INFO", [27]="CONNECTING_NODES", }; - const char* n = (type >= 1 && type <= 25) ? names[type] : "?"; + const char* n = (type >= 1 && type <= 27) ? names[type] : "?"; DEBUG_DEBUG(DEBUG_CATEGORY_CHAT, "chat_event: %s(%d) data=%d bytes", n, type, len); if (data && len > 0) log_dump(DEBUG_LEVEL_DEBUG, DEBUG_CATEGORY_CHAT, " ", data, len); } diff --git a/src/chat/chat_event.h b/src/chat/chat_event.h index 7b27a1d3..84b15740 100644 --- a/src/chat/chat_event.h +++ b/src/chat/chat_event.h @@ -45,6 +45,7 @@ extern "C" { #define CHAT_EVT_MEMBER_UPDATED 24 /* [ch_id_len:1][ch_id:var][count:1][member:79B] — обновление одного мембера */ #define CHAT_EVT_MEMBER_REMOVED 25 /* [ch_id_len:1][ch_id:var][node_id:8] — мембер удалён из канала */ #define CHAT_EVT_BGP_INFO 26 /* текст: "rtt:XXms hop:X via bgp: [XXXX] name1 [XXXX] name2" */ +#define CHAT_EVT_CONNECTING_NODES 27 /* [ch_id_len:1][ch_id:var][count:2][node_id:8]* */ typedef void (*chat_event_handler_fn)(int type, const uint8_t* data, int len); diff --git a/src/routing_layer/topo_group_connect.c b/src/routing_layer/topo_group_connect.c index be6bd328..9ed3446b 100644 --- a/src/routing_layer/topo_group_connect.c +++ b/src/routing_layer/topo_group_connect.c @@ -1,19 +1,24 @@ /* - * topo_group_connect.c — авто-подключение к узлам группы при старте + * topo_group_connect.c — авто-подключение к узлам группы (бесконечный цикл до успеха) * * Phase 1: одновременный запуск conn_mgr_open_invite для пиров с connected=1. - * Таймаут = TGC_DIRECT_TIMEOUT_MS. По таймауту закрываем незавершённые handle'ы - * и помечаем connected=0 в БД. Если хоть один подключился → done. + * Таймаут = TGC_DIRECT_TIMEOUT_MS. Если хоть один подключился → done. * - * Phase 2: последовательный перебор пиров с прямыми/EIM адресами. + * Phase 2: последовательный перебор (supernode, затем public/EIM адреса). + * После каждого TIMEOUT — пауза перед следующей попыткой. * - * Phase 3: последовательный перебор пиров с локальными/strict NAT. + * Phase 3: последовательный перебор локальных/strict NAT адресов. + * После каждого TIMEOUT — пауза перед следующей попыткой. + * + * После исчерпания Phase 3 → пауза → cycle_restart → Phase 1 (бесконечно). + * + * Пауза: 1с когда client_activity == ACTIVE, 30с когда STANDBY (Android фон). + * При DOWN (active_conn_count → 0) — немедленный cycle_restart. + * При повторном topo_group_connect_init — destroy + fresh init. * * Счётчик active_conn_count — число уникальных peer-узлов с реальными соединениями. * UP: инкремент только для первого соединения к peer_node_id. - * DOWN: декремент только если нет других conn к peer_node_id И нет indirect-путей в nodeinfo. - * - * restart: перезапуск авто-подключения при phase=done и active_conn_count==0. + * DOWN: декремент только если нет других conn к peer_node_id И нет indirect-путей. */ #include "topo_group_connect.h" @@ -26,6 +31,7 @@ #include "../lib/u_async.h" #include "../lib/ll_queue.h" #include "etcp.h" +#include "../chat/chat_event.h" /* ─── внутренние константы ─── */ #define TGC_ID "topo_group_connect" @@ -34,13 +40,16 @@ #define TGC_PHASE_TWO 1 #define TGC_PHASE_THREE 2 #define TGC_PHASE_DONE 3 +#define TGC_PHASE_PAUSE 4 #define TGC_MAX_HANDLES 128 +#define TGC_PAUSE_ACTIVE_TB 10000 /* 1s пауза когда client_activity == ACTIVE */ +#define TGC_PAUSE_IDLE_TB 300000 /* 30s пауза когда STANDBY (Android фон) */ struct TOPO_GROUP_CONNECT { struct TOPO_GROUP* group; void* phase_timer; + void* pause_timer; uint8_t phase; - uint8_t active; int pending; int connected_count; int active_conn_count; @@ -55,20 +64,39 @@ struct TOPO_GROUP_CONNECT { static void tgc_phase2_try_next(struct TOPO_GROUP_CONNECT* gc); static void tgc_phase3_try_next(struct TOPO_GROUP_CONNECT* gc); static void tgc_phase1_timeout(void* arg); +static void tgc_cycle_restart(struct TOPO_GROUP_CONNECT* gc); +static void tgc_start_pause(struct TOPO_GROUP_CONNECT* gc, void (*cb)(void*), const char* label); static void tgc_callback(struct CONN_MGR_HANDLE* h, uint64_t node_id, uint64_t group_id, enum conn_mgr_event event, void* arg); +/* ─── нотификация GUI о списке узлов в процессе подключения ─── */ + +static void tgc_notify_connecting(struct TOPO_GROUP_CONNECT* gc, const uint64_t* ids, int count) { + size_t cl = strlen(gc->group->channel_id); if (cl > 255) cl = 255; + size_t sz = 1 + cl + 2 + (size_t)count * 8; + uint8_t* buf = u_malloc(sz); if (!buf) return; + uint8_t* p = buf; + *p++ = (uint8_t)cl; memcpy(p, gc->group->channel_id, cl); p += cl; + uint16_t c = (uint16_t)count; memcpy(p, &c, 2); p += 2; + for (int i = 0; i < count; i++) { memcpy(p, &ids[i], 8); p += 8; } + chat_event_post(CHAT_EVT_CONNECTING_NODES, buf, (int)sz); + u_free(buf); + DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "%s: notify_connecting ch=%s count=%d", + TGC_ID, gc->group->channel_id, count); +} + /* ═══════════════════════════════════════════════════════════════════════ * Жизненный цикл * ══════════════════════════════════════════════════════════════════════ */ int topo_group_connect_init(struct TOPO_GROUP* group) { - if (!group || group->group_type != TOPO_GROUP_TYPE_CHAT || !group->channel_id[0] || group->connect) + if (!group || group->group_type != TOPO_GROUP_TYPE_CHAT || !group->channel_id[0]) return -1; + if (group->connect) topo_group_connect_destroy(group); struct TOPO_GROUP_CONNECT* gc = u_calloc(1, sizeof(*gc)); if (!gc) return -1; - gc->group = group; gc->active = 1; gc->tried_super = 0; + gc->group = group; gc->tried_super = 0; group->connect = gc; uint64_t* ids = NULL; int count = 0; @@ -89,6 +117,7 @@ int topo_group_connect_init(struct TOPO_GROUP* group) { conn_mgr_open_invite(group->instance, group->group_id, NULL, ids[i], tgc_callback, gc, &gc->handles[gc->handle_count++]); } + tgc_notify_connecting(gc, ids, gc->handle_count); u_free(ids); gc->phase_timer = uasync_set_timeout(group->instance->ua, @@ -101,6 +130,7 @@ void topo_group_connect_destroy(struct TOPO_GROUP* group) { if (!gc) return; group->connect = NULL; if (gc->phase_timer) { uasync_cancel_timeout(group->instance->ua, gc->phase_timer); gc->phase_timer = NULL; } + if (gc->pause_timer) { uasync_cancel_timeout(group->instance->ua, gc->pause_timer); gc->pause_timer = NULL; } for (int i = 0; i < gc->handle_count; i++) conn_mgr_close(gc->handles[i]); u_free(gc->candidate_ids); u_free(gc); @@ -184,9 +214,9 @@ void topo_group_connect_on_down(struct TOPO_GROUP* group, struct ETCP_CONN* conn if (db) topo_node_sqlite_set_connected(db, group->channel_id, peer, 0); DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: DOWN peer=0x%016llx ch=%s active=%d", TGC_ID, (unsigned long long)peer, group->channel_id, gc->active_conn_count); - if (gc->phase == TGC_PHASE_DONE && gc->active_conn_count == 0) { - DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "%s: DOWN restart eligible ch=%s → restarting", TGC_ID, group->channel_id); - topo_group_connect_restart(group); + if (gc->active_conn_count == 0) { + DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: DOWN no connections — cycle restart ch=%s", TGC_ID, group->channel_id); + tgc_cycle_restart(gc); } } @@ -220,23 +250,39 @@ static void tgc_callback(struct CONN_MGR_HANDLE* h, uint64_t node_id, uint64_t g if (ok) { DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase2 connected to 0x%016llx — done", TGC_ID, (unsigned long long)node_id); gc->phase = TGC_PHASE_DONE; + tgc_notify_connecting(gc, NULL, 0); } else { - DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase2 0x%016llx FAIL — try next", TGC_ID, (unsigned long long)node_id); - tgc_phase2_try_next(gc); + DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase2 0x%016llx FAIL — pause then try next", TGC_ID, (unsigned long long)node_id); + tgc_start_pause(gc, (void(*)(void*))tgc_phase2_try_next, "tgc_p2_try"); } break; case TGC_PHASE_THREE: if (ok) { DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase3 connected to 0x%016llx — done", TGC_ID, (unsigned long long)node_id); gc->phase = TGC_PHASE_DONE; + tgc_notify_connecting(gc, NULL, 0); } else { - DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase3 0x%016llx FAIL — try next", TGC_ID, (unsigned long long)node_id); - tgc_phase3_try_next(gc); + DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase3 0x%016llx FAIL — pause then try next", TGC_ID, (unsigned long long)node_id); + tgc_start_pause(gc, (void(*)(void*))tgc_phase3_try_next, "tgc_p3_try"); } break; } } +/* ═══════════════════════════════════════════════════════════════════════ + * Пауза: 1s ACTIVE / 30s STANDBY + * ══════════════════════════════════════════════════════════════════════ */ + +static void tgc_start_pause(struct TOPO_GROUP_CONNECT* gc, void (*cb)(void*), const char* label) { + gc->phase = TGC_PHASE_PAUSE; + int active = (gc->group->instance->client_activity == CLIENT_ACTIVITY_ACTIVE); + int delay_tb = active ? TGC_PAUSE_ACTIVE_TB : TGC_PAUSE_IDLE_TB; + gc->pause_timer = uasync_set_timeout(gc->group->instance->ua, delay_tb, gc, cb, label); + tgc_notify_connecting(gc, NULL, 0); + DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "%s: pause %dms (activity=%s) for ch=%s", + TGC_ID, delay_tb / 10, active ? "ACTIVE" : "STANDBY", gc->group->channel_id); +} + /* ═══════════════════════════════════════════════════════════════════════ * Phase 1 timeout * ══════════════════════════════════════════════════════════════════════ */ @@ -273,6 +319,7 @@ static void tgc_phase1_timeout(void* arg) { if (gc->connected_count > 0) { DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase1 done — %d connected, skipping Phase 2", TGC_ID, gc->connected_count); gc->phase = TGC_PHASE_DONE; + tgc_notify_connecting(gc, NULL, 0); return; } gc->phase = TGC_PHASE_TWO; @@ -284,6 +331,7 @@ static void tgc_phase1_timeout(void* arg) { * ══════════════════════════════════════════════════════════════════════ */ static void tgc_phase2_try_next(struct TOPO_GROUP_CONNECT* gc) { + if (gc->phase == TGC_PHASE_PAUSE) gc->phase = TGC_PHASE_TWO; if (gc->phase != TGC_PHASE_TWO) return; while (1) { if (gc->candidate_count == 0) { @@ -311,6 +359,7 @@ static void tgc_phase2_try_next(struct TOPO_GROUP_CONNECT* gc) { DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase2 trying 0x%016llx (%d/%d) super_round=%d", TGC_ID, (unsigned long long)nid, gc->cursor, gc->candidate_count, gc->tried_super); if (conn_mgr_open_invite(gc->group->instance, gc->group->group_id, NULL, nid, tgc_callback, gc, NULL) < 0) continue; + tgc_notify_connecting(gc, &nid, 1); return; } if (gc->candidate_ids) { u_free(gc->candidate_ids); gc->candidate_ids = NULL; } @@ -323,6 +372,7 @@ static void tgc_phase2_try_next(struct TOPO_GROUP_CONNECT* gc) { * ══════════════════════════════════════════════════════════════════════ */ static void tgc_phase3_try_next(struct TOPO_GROUP_CONNECT* gc) { + if (gc->phase == TGC_PHASE_PAUSE) gc->phase = TGC_PHASE_THREE; if (gc->phase != TGC_PHASE_THREE) return; if (gc->candidate_count == 0) { sqlite3* db = gc->group->instance->topo_sqlite_db; @@ -337,8 +387,48 @@ static void tgc_phase3_try_next(struct TOPO_GROUP_CONNECT* gc) { DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase3 trying 0x%016llx (%d/%d)", TGC_ID, (unsigned long long)nid, gc->cursor, gc->candidate_count); if (conn_mgr_open_invite(gc->group->instance, gc->group->group_id, NULL, nid, tgc_callback, gc, NULL) < 0) continue; + tgc_notify_connecting(gc, &nid, 1); return; } - DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase3 exhausted — no connections, ch=%s", TGC_ID, gc->group->channel_id); - gc->phase = TGC_PHASE_DONE; + DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: Phase3 exhausted — pause then cycle restart, ch=%s", TGC_ID, gc->group->channel_id); + tgc_start_pause(gc, (void(*)(void*))tgc_cycle_restart, "tgc_p3_restart"); +} + +/* ═══════════════════════════════════════════════════════════════════════ + * Полный перезапуск цикла с Phase 1 (без destroy/init) + * ══════════════════════════════════════════════════════════════════════ */ + +static void tgc_cycle_restart(struct TOPO_GROUP_CONNECT* gc) { + gc->pause_timer = NULL; + if (gc->phase_timer) { uasync_cancel_timeout(gc->group->instance->ua, gc->phase_timer); gc->phase_timer = NULL; } + for (int i = 0; i < gc->handle_count; i++) conn_mgr_close(gc->handles[i]); + gc->handle_count = 0; gc->pending = 0; gc->connected_count = 0; + gc->cursor = 0; gc->tried_super = 0; + u_free(gc->candidate_ids); gc->candidate_ids = NULL; gc->candidate_count = 0; + + uint64_t* ids = NULL; int count = 0; + sqlite3* db = gc->group->instance->topo_sqlite_db; + DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "%s: cycle_restart ch=%s active=%d", + TGC_ID, gc->group->channel_id, gc->active_conn_count); + if (!db || topo_node_sqlite_get_connected_peers(db, gc->group->channel_id, &ids, &count) != 0 || count == 0) { + if (ids) { u_free(ids); ids = NULL; } + gc->phase = TGC_PHASE_TWO; + DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: cycle_restart ch=%s no connected peers → Phase 2", TGC_ID, gc->group->channel_id); + tgc_phase2_try_next(gc); + return; + } + + gc->phase = TGC_PHASE_ONE; gc->pending = count; + DEBUG_INFO(DEBUG_CATEGORY_BGP, "%s: cycle_restart Phase 1 launching %d connects ch=%s", + TGC_ID, count, gc->group->channel_id); + for (int i = 0; i < count && gc->handle_count < TGC_MAX_HANDLES; i++) { + DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "%s: Phase1 connect to 0x%016llx (%d/%d)", TGC_ID, (unsigned long long)ids[i], i + 1, count); + conn_mgr_open_invite(gc->group->instance, gc->group->group_id, NULL, ids[i], tgc_callback, gc, + &gc->handles[gc->handle_count++]); + } + tgc_notify_connecting(gc, ids, gc->handle_count); + u_free(ids); + + gc->phase_timer = uasync_set_timeout(gc->group->instance->ua, + TGC_DIRECT_TIMEOUT_MS * 10, gc, tgc_phase1_timeout, "tgc_phase1"); } diff --git a/src/routing_layer/topo_group_connect.h b/src/routing_layer/topo_group_connect.h index 86f88503..66c784cc 100644 --- a/src/routing_layer/topo_group_connect.h +++ b/src/routing_layer/topo_group_connect.h @@ -1,16 +1,21 @@ /** * @file topo_group_connect.h - * @brief Авто-подключение к узлам группы при старте и отслеживание состояния connected в БД. + * @brief Авто-подключение к узлам группы — бесконечный цикл до успеха. * - * При старте CHAT-группы: - * Phase 1 — одновременный запуск подключений ко всем пирам с connected=1. + * При старте CHAT-группы (topo_group_connect_init): + * Phase 1 — параллельный запуск ко всем пирам с connected=1 (таймаут 2с). * Если хоть один подключился → done. - * Phase 2 — последовательный перебор по пирам с публичными адресами. - * До первого успешного подключения. + * Phase 2 — последовательный перебор (supernode, затем public/EIM). + * После каждого TIMEOUT — пауза перед следующей попыткой. + * Phase 3 — последовательный перебор локальных/strict NAT. + * После каждого TIMEOUT — пауза перед следующей попыткой. + * После исчерпания Phase 3 → пауза → cycle_restart → Phase 1 (бесконечно). + * + * Пауза: 1с когда client_activity == ACTIVE, 30с когда STANDBY (Android фон). + * При DOWN (active_conn_count → 0) — немедленный cycle_restart из любой фазы. + * При повторном вызове topo_group_connect_init — destroy + fresh init. * - * Отслеживание: на UP → peers_.connected=1, на DOWN (последний conn) → 0. * active_conn_count — количество уникальных peer-узлов с живыми соединениями. - * restart — перезапуск авто-подключения (если phase=done и нет подключений). */ #ifndef TOPO_GROUP_CONNECT_H #define TOPO_GROUP_CONNECT_H diff --git a/src/utun_instance.c b/src/utun_instance.c index 48f034ee..0d8159bb 100644 --- a/src/utun_instance.c +++ b/src/utun_instance.c @@ -102,7 +102,7 @@ static int instance_init_common(struct UTUN_INSTANCE* instance, struct UASYNC* u // Set client type instance->client_type = config->global.client_type; instance->keepalive_interval = (uint16_t)(config->global.keepalive_interval > 0 ? config->global.keepalive_interval : 200); - instance->client_activity = (instance->client_type == CLIENT_TYPE_SERVER) ? CLIENT_ACTIVITY_ACTIVE : CLIENT_ACTIVITY_STANDBY; + instance->client_activity = (instance->client_type == CLIENT_TYPE_MOBILE) ? CLIENT_ACTIVITY_STANDBY : CLIENT_ACTIVITY_ACTIVE; instance->client_activity_timer = NULL; // Set my keys diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/ConfigProvider.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/ConfigProvider.kt index 1e002f67..63d8230f 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/ConfigProvider.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/ConfigProvider.kt @@ -191,7 +191,7 @@ class ConfigProvider(private val context: Context) { /* ── Connection mode ── */ fun getConnectionMode(): String = runBlocking { - context.dataStore.data.first()[connectionModeKey] ?: "manual" + context.dataStore.data.first()[connectionModeKey] ?: "auto" } suspend fun setConnectionMode(mode: String) { diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/components/MessageBubble.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/components/MessageBubble.kt index 2da23a15..a800a100 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/components/MessageBubble.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/components/MessageBubble.kt @@ -378,41 +378,40 @@ fun InputBar( modifier = Modifier .size(48.dp) .background(pttBg, RoundedCornerShape(4.dp)) - .pointerInput(hasMicPerm) { - awaitPointerEventScope { - while (true) { - val down = awaitPointerEvent() - val ch = down.changes.firstOrNull() ?: continue - if (!ch.pressed) continue - ch.consume() - - if (!hasMicPerm) { - permLauncher.launch(Manifest.permission.RECORD_AUDIO) - continue - } + .then(if (!hasMicPerm) { + Modifier.clickable { permLauncher.launch(Manifest.permission.RECORD_AUDIO) } + } else { + Modifier.pointerInput(Unit) { + awaitPointerEventScope { + while (true) { + val down = awaitPointerEvent() + val ch = down.changes.firstOrNull() ?: continue + if (!ch.pressed) continue + ch.consume() - onPttPressed() - var locked = false - var totalDragY = 0f - var lastY = ch.position.y + onPttPressed() + var locked = false + var totalDragY = 0f + var lastY = ch.position.y - while (true) { - val event = awaitPointerEvent() - val change = event.changes.firstOrNull() ?: break - if (change.pressed) { - val dy = change.position.y - lastY - lastY = change.position.y - totalDragY += dy - if (totalDragY < -lockThresholdPx && !locked) { locked = true; onPttLock() } - change.consume() - } else { - change.consume(); break + while (true) { + val event = awaitPointerEvent() + val change = event.changes.firstOrNull() ?: break + if (change.pressed) { + val dy = change.position.y - lastY + lastY = change.position.y + totalDragY += dy + if (totalDragY < -lockThresholdPx && !locked) { locked = true; onPttLock() } + change.consume() + } else { + change.consume(); break + } } + onPttReleased() } - onPttReleased() } } - }, + }), contentAlignment = Alignment.Center ) { Text("\uD83C\uDFA4", fontSize = 20.sp, color = if (isRecording) Color.White else Color.Unspecified) diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/SettingsScreen.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/SettingsScreen.kt index 38964858..ce760e7f 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/SettingsScreen.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/SettingsScreen.kt @@ -209,10 +209,10 @@ private fun GeneralSettingsTab(vm: ChatViewModel, dirty: Boolean, onDirty: (Bool /* ── Connection Mode ── */ Text("Network Interfaces", style = MaterialTheme.typography.titleMedium) Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) { - FilterChip(selected = connectionMode == "manual", onClick = { if (connectionMode != "manual") switchMode("manual") }, - label = { Text("Manual") }) FilterChip(selected = connectionMode == "auto", onClick = { if (connectionMode != "auto") switchMode("auto") }, label = { Text("Auto") }) + FilterChip(selected = connectionMode == "manual", onClick = { if (connectionMode != "manual") switchMode("manual") }, + label = { Text("Manual") }) } if (connectionMode == "auto") { diff --git a/tools/chatgui-android/jni_bridge/android_udp_log.c b/tools/chatgui-android/jni_bridge/android_udp_log.c index 6665ed11..b8eabe6c 100644 --- a/tools/chatgui-android/jni_bridge/android_udp_log.c +++ b/tools/chatgui-android/jni_bridge/android_udp_log.c @@ -37,6 +37,7 @@ static int g_udp_send_errors = 0; static void udp_reschedule_timer(void) { if (g_udp_stopping) return; + if (g_udp_timer) return; struct UASYNC* ua = instance_lite_get_uasync(); if (ua) g_udp_timer = uasync_set_timeout(ua, UDP_FLUSH_TB, NULL, udp_trampoline, "udp_log_flush"); } diff --git a/tools/chatgui-android/libutun_lite/instance_lite.c b/tools/chatgui-android/libutun_lite/instance_lite.c index ca9782fb..ea24e404 100644 --- a/tools/chatgui-android/libutun_lite/instance_lite.c +++ b/tools/chatgui-android/libutun_lite/instance_lite.c @@ -236,7 +236,6 @@ static void* instance_thread(void* arg) { if (config->global.log_udp_ip[0] && config->global.log_udp_port > 0) { udp_log_set_target(config->global.log_udp_ip, config->global.log_udp_port); - uasync_set_timeout(g_ua, 2000, NULL, udp_trampoline, "udp_log_flush"); } IL_LOGI("[EVT_DIAG] thread: about to set chat_event handler, g_event_handler=%p", (void*)g_event_handler);