diff --git a/tools/chatgui/tests/test_settings_save.cpp b/tools/chatgui/tests/test_settings_save.cpp index 1df29b75..f0bc289b 100644 --- a/tools/chatgui/tests/test_settings_save.cpp +++ b/tools/chatgui/tests/test_settings_save.cpp @@ -143,7 +143,7 @@ class SettingsSaveTest : public QObject { QCOMPARE(VoiceEncoder::preset(), 2); } if (kind == Dialog) { - QCOMPARE(QString::fromUtf8(core.inst->config->global.my_node_name), QString("After")); + QCOMPARE(QString::fromUtf8(core.inst->name), QString("After")); QCOMPARE(chat_setting_get_int(core.inst, "stream_subblocks", -1), 12); QCOMPARE(chat_setting_get_int(core.inst, "stream_rs_redundant", -1), 3); QCOMPARE(core.db.getUiStateInt("join_policy", -1), 1); diff --git a/tools/chatgui/transport/gui_bridge.h b/tools/chatgui/transport/gui_bridge.h index c09256f1..49d2aa67 100644 --- a/tools/chatgui/transport/gui_bridge.h +++ b/tools/chatgui/transport/gui_bridge.h @@ -92,6 +92,18 @@ 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); + +/* Назначение пары key/value: локальный ui_state SQLite либо live chat_setting ядра. */ +enum gui_setting_target { GUI_SETTING_UI_STATE, GUI_SETTING_CHAT }; +/* Скопировать NUL-строки key/value в собственный запрос и поставить его текущему ядру под mutex моста. + * key непустой; key+NUL+value+NUL должны помещаться в data соответствующего запроса ядра. + * 0 — принято (не подтверждение записи/валидации ядром), -1 — отказ с логом, память за вызывающим не остаётся. + * При отмене запрос освобождает мост, после исполнения — trampoline. INI-файл эта функция не меняет. */ +int gui_bridge_post_setting(enum gui_setting_target target, const char* key, const char* value); +/* Скопировать name (включая пустое, в пределах update_my_name_arg::name) и поставить обновление текущему ядру. + * Владение и результат — как у post_setting; строка/форма вызывающего после возврата больше не нужны. */ +int gui_bridge_update_name(const char* name); + /* Composer-запрос: 0 — владение req передано, callback/отмена освободят блок; -1 — req остаётся вызывающему. */ struct chat_msg_submit; int gui_bridge_submit_message(struct chat_msg_submit* req); diff --git a/tools/chatgui/transport/gui_bridge_impl.cpp b/tools/chatgui/transport/gui_bridge_impl.cpp index fabc2f58..43574fa3 100644 --- a/tools/chatgui/transport/gui_bridge_impl.cpp +++ b/tools/chatgui/transport/gui_bridge_impl.cpp @@ -625,6 +625,67 @@ int gui_bridge_post_uasync_owned_fn(void (*fn)(void*), void* arg) { return post_command(fn, arg, true); } +/* Сериализация и публикация образуют одну границу владения с detach ядра. */ +int gui_bridge_post_setting(enum gui_setting_target target, const char* key, const char* value) { + if ((target != GUI_SETTING_UI_STATE && target != GUI_SETTING_CHAT) || !key || !key[0] || !value) { + DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "GUI setting rejected: target=%d missing/invalid key or value", target); return -1; + } + const size_t capacity = target == GUI_SETTING_UI_STATE ? sizeof(save_ui_state_arg::data) : sizeof(chat_setting_arg::data); + size_t keyLength = 0, valueLength = 0; + while (keyLength < capacity && key[keyLength]) ++keyLength; + while (valueLength < capacity && value[valueLength]) ++valueLength; + if (keyLength + valueLength + 2 > capacity) { + DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "GUI setting rejected: target=%d key_bytes=%zu value_bytes=%zu capacity=%zu", + target, keyLength, valueLength, capacity); + return -1; + } + std::lock_guard guard(bridge_mutex); + if (!g_inst || !g_ua) { + DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "GUI setting rejected: target=%d key=%s core unavailable", target, key); return -1; + } + const size_t bytes = target == GUI_SETTING_UI_STATE ? sizeof(save_ui_state_arg) : sizeof(chat_setting_arg); + void* request = u_malloc(bytes); + if (!request) { + DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "GUI setting allocation failed: target=%d key=%s bytes=%zu", target, key, bytes); return -1; + } + char* data; + void (*callback)(void*); + if (target == GUI_SETTING_UI_STATE) { + auto* arg = static_cast(request); + arg->inst = g_inst; data = arg->data; callback = chat_core_save_ui_state_trampoline; + } else { + auto* arg = static_cast(request); + arg->inst = g_inst; data = arg->data; callback = chat_core_set_setting_trampoline; + } + memcpy(data, key, keyLength + 1); memcpy(data + keyLength + 1, value, valueLength + 1); + if (post_command(callback, request, true) < 0) { + u_free(request); + DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "GUI setting queue rejected: target=%d key=%s", target, key); return -1; + } + DEBUG_DEBUG(DEBUG_CATEGORY_CONFIG, "GUI setting queued: target=%d key=%s value_bytes=%zu", target, key, valueLength); + return 0; +} + +/* Смена имени использует тот же owned lifecycle без передачи instance из GUI. */ +int gui_bridge_update_name(const char* name) { + if (!name) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "GUI name rejected: missing name"); return -1; } + size_t length = 0; + while (length < sizeof(update_my_name_arg::name) && name[length]) ++length; + if (length == sizeof(update_my_name_arg::name)) { + DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "GUI name rejected: name exceeds request capacity"); return -1; + } + std::lock_guard guard(bridge_mutex); + if (!g_inst || !g_ua) { DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "GUI name rejected: core unavailable"); return -1; } + auto* request = static_cast(u_malloc(sizeof(update_my_name_arg))); + if (!request) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "GUI name allocation failed"); return -1; } + request->inst = g_inst; memcpy(request->name, name, length + 1); + if (post_command(chat_core_update_my_name_trampoline, request, true) < 0) { + u_free(request); DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "GUI name queue rejected"); return -1; + } + DEBUG_DEBUG(DEBUG_CATEGORY_CONFIG, "GUI name queued: bytes=%zu", length); + return 0; +} + int gui_bridge_submit_message(struct chat_msg_submit* req) { std::lock_guard guard(bridge_mutex); if (!req || !g_inst || !g_ua || !uasync_is_running(g_ua)) {