From 707938d503df8ce3ea22d70dc450aafa9cf9ac9c Mon Sep 17 00:00:00 2001 From: evgeny Date: Tue, 27 Oct 2026 23:37:44 +0100 Subject: [PATCH] Bind remaining desktop commands atomically and cancel owned attachments --- tools/chatgui/transport/gui_bridge.h | 9 +++++ tools/chatgui/transport/gui_bridge_impl.cpp | 39 +++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/tools/chatgui/transport/gui_bridge.h b/tools/chatgui/transport/gui_bridge.h index f11aa4f5..6672ff6f 100644 --- a/tools/chatgui/transport/gui_bridge.h +++ b/tools/chatgui/transport/gui_bridge.h @@ -93,6 +93,11 @@ void gui_bridge_post_uasync_fn(void (*fn)(void*), void* arg); /* Передать отдельный u_malloc/u_calloc-блок arg: 0 — мост принял владение, callback обязан освободить arg; * отмена до callback освобождает его мостом. -1 — arg остаётся вызывающему. Вложенные ресурсы не освобождаются. */ int gui_bridge_post_uasync_owned_fn(void (*fn)(void*), void* arg); +/* Один u_malloc-блок команды ядру; binding — адрес её поля inst внутри arg. + * Проверка running, запись inst и enqueue выполняются под mutex. GUI не читает instance. + * 0 передаёт владение: fn освобождает arg после исполнения, мост — при отмене. + * -1 оставляет блок вызывающему для u_free; вложенные ресурсы этим API не поддерживаются. */ +int gui_bridge_post_bound_fn(void (*fn)(void*), void* arg, struct UTUN_INSTANCE** binding); /* Команды обычного звонка: GUI не передаёт instance или heap-аргументы. * Под mutex моста копируют данные, привязываются к живому running-ядру и передают владение очереди. @@ -130,6 +135,10 @@ int gui_bridge_update_name(const char* name); * При -1 результата нет: вызывающий освобождает req и сам сообщает об отказе своему composer. */ struct chat_msg_submit; int gui_bridge_submit_message(struct chat_msg_submit* req); +/* Подготовка вложения: тот же контракт результата, но отмена также освобождает PCM/compressor/временный source + * и сообщает MSG_SUBMITTED(false). При -1 caller вызывает attachment_send_discard(req). */ +struct attachment_send_req; +int gui_bridge_submit_attachment(struct attachment_send_req* req); /* Проверить наличие опубликованных uasync и instance. Только подсказка готовности, не резервация их lifetime. */ int gui_bridge_is_uasync_ready(void); diff --git a/tools/chatgui/transport/gui_bridge_impl.cpp b/tools/chatgui/transport/gui_bridge_impl.cpp index 4b47e4cf..18698e69 100644 --- a/tools/chatgui/transport/gui_bridge_impl.cpp +++ b/tools/chatgui/transport/gui_bridge_impl.cpp @@ -27,6 +27,7 @@ extern "C" { #include "../../../src/dm/dm_core.h" #include "../../../src/call/call.h" #include "../../../src/radio/radio_call.h" +#include "../../../src/media_async/attachment_send.h" } /* ── Внутренний объект-приёмник в GUI-потоке ── */ @@ -773,6 +774,21 @@ int gui_bridge_post_uasync_owned_fn(void (*fn)(void*), void* arg) { return post_command(fn, arg, true); } +/* binding указывает на поле inst самой команды; отдельного чтения указателя из GUI нет. */ +int gui_bridge_post_bound_fn(void (*fn)(void*), void* arg, struct UTUN_INSTANCE** binding) { + std::lock_guard guard(bridge_mutex); + if (!fn || !arg || !binding || !g_inst || !g_ua || !uasync_is_running(g_ua)) { + DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "GUI core command rejected: fn=%p arg=%p binding=%p inst=%p ua=%p running=%d", + (void*)fn, arg, (void*)binding, (void*)g_inst.load(), (void*)g_ua, uasync_is_running(g_ua)); return -1; + } + *binding = g_inst; + if (post_command(fn, arg, true) < 0) { + *binding = nullptr; DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "GUI core command queue failed: fn=%p arg=%p", (void*)fn, arg); return -1; + } + DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "GUI core command queued: fn=%p inst=%p ua=%p", (void*)fn, (void*)g_inst.load(), (void*)g_ua); + return 0; +} + /* Создание payload и enqueue атомарны относительно detach; GUI не читает заимствованный instance. */ int gui_bridge_call_start(const char* channel, uint64_t peer, uint64_t call_id) { const size_t length = channel ? strnlen(channel, sizeof(call_start_arg::ch_id)) : 0; @@ -1022,6 +1038,29 @@ int gui_bridge_submit_message(struct chat_msg_submit* req) { return post_command(chat_core_submit_trampoline, req, true, cancel_message_submit); } +/* Отмена до native trampoline не требует живого media owner; callback освобождает весь запрос сам. */ +static void cancel_attachment_submit(void* arg) { + auto* request = static_cast(arg); + if (request->request_id) { + uint8_t result[9] = {0}; + memcpy(result, &request->request_id, 8); gui_bridge_post(CHAT_EVT_MSG_SUBMITTED, result, sizeof(result)); + } + attachment_send_discard(request); +} + +int gui_bridge_submit_attachment(struct attachment_send_req* req) { + std::lock_guard guard(bridge_mutex); + if (!req || !g_inst || !g_ua || !uasync_is_running(g_ua)) { + DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "attachment submission: core/request unavailable"); return -1; + } + req->inst = g_inst; + if (post_command(attachment_send_trampoline, req, false, cancel_attachment_submit) < 0) { + req->inst = nullptr; DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "attachment submission: queue allocation failed"); return -1; + } + DEBUG_DEBUG(DEBUG_CATEGORY_MEDIA, "attachment queued: target=%s request=%llu", req->target, (unsigned long long)req->request_id); + return 0; +} + int gui_bridge_is_uasync_ready(void) { std::lock_guard guard(bridge_mutex); return g_ua && g_inst ? 1 : 0;