diff --git a/tools/chatgui/db/db_manager.cpp b/tools/chatgui/db/db_manager.cpp
index 0a3c3a10..975d66cb 100644
--- a/tools/chatgui/db/db_manager.cpp
+++ b/tools/chatgui/db/db_manager.cpp
@@ -363,6 +363,14 @@ QString DbManager::getUiState(const QString& key) const {
return val;
}
+int DbManager::getUiStateInt(const QString& key, int defaultVal) const {
+ QString val = getUiState(key);
+ if (val.isEmpty()) return defaultVal;
+ bool ok = false;
+ int v = val.toInt(&ok);
+ return ok ? v : defaultVal;
+}
+
/* ── sync operations (read-only, for gui_bridge) ── */
QByteArray DbManager::syncListChannels() const {
diff --git a/tools/chatgui/db/db_manager.h b/tools/chatgui/db/db_manager.h
index 285c70ab..37a99d42 100644
--- a/tools/chatgui/db/db_manager.h
+++ b/tools/chatgui/db/db_manager.h
@@ -104,6 +104,7 @@ public:
/* ── UI state (read-only используем QSettings, оставлен для совместимости) ── */
QString getUiState(const QString& key) const;
+ int getUiStateInt(const QString& key, int defaultVal) const;
/* ── Sync-операции (read-only, для gui_bridge/inline) ── */
QByteArray syncListChannels() const;
diff --git a/tools/chatgui/resources/chatgui.qrc b/tools/chatgui/resources/chatgui.qrc
index ccb2e2c7..24f828e8 100644
--- a/tools/chatgui/resources/chatgui.qrc
+++ b/tools/chatgui/resources/chatgui.qrc
@@ -5,6 +5,5 @@
animations/greeting.tgs
animations/stop.tgs
animations/robot.tgs
- sounds/msg_incoming.mp3
diff --git a/tools/chatgui/src/mainwindow.cpp b/tools/chatgui/src/mainwindow.cpp
index 7d860090..5d2a49a9 100644
--- a/tools/chatgui/src/mainwindow.cpp
+++ b/tools/chatgui/src/mainwindow.cpp
@@ -21,6 +21,7 @@
#include
#include
#include
+#include
extern "C" {
#include "../transport/chat_core.h"
@@ -85,25 +86,65 @@ MainWindow::MainWindow(QWidget *parent, DbManager* db, const QString& cfgPath,
SoundManager* sm = SoundManager::instance();
if (sm->init()) {
+ QString soundDir;
+ QString msgInFile, msgOutFile, connectFile, disconnectFile;
+
QFile cfgFile(m_cfgPath);
if (cfgFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
- bool inGui = false; bool enabled = true; int volume = 80;
+ bool inGui = false;
QTextStream in(&cfgFile);
while (!in.atEnd()) {
QString line = in.readLine().trimmed();
if (line == "[gui]") { inGui = true; continue; }
if (line.startsWith("[") && line.endsWith("]")) inGui = false;
if (!inGui) continue;
- if (line.startsWith("sound_enabled="))
- enabled = line.mid(15).trimmed() != "0";
- else if (line.startsWith("sound_volume="))
- volume = line.mid(13).trimmed().toInt();
+ if (line.startsWith("sound_dir="))
+ soundDir = line.mid(10).trimmed();
+ else if (line.startsWith("sound_msg_incoming="))
+ msgInFile = line.mid(19).trimmed();
+ else if (line.startsWith("sound_msg_outgoing="))
+ msgOutFile = line.mid(19).trimmed();
+ else if (line.startsWith("sound_connect="))
+ connectFile = line.mid(14).trimmed();
+ else if (line.startsWith("sound_disconnect="))
+ disconnectFile = line.mid(17).trimmed();
}
cfgFile.close();
- sm->setEnabled(enabled);
- sm->setVolume(volume / 100.0f);
}
- sm->loadSound("msg_incoming", ":/sounds/msg_incoming.mp3");
+
+ if (soundDir.isEmpty()) soundDir = "sounds";
+ QDir binDir(QApplication::applicationDirPath());
+ QString baseDir = QDir::isAbsolutePath(soundDir) ? soundDir
+ : binDir.absoluteFilePath(soundDir);
+ auto resolvePath = [&baseDir](const QString& file) -> QString {
+ if (file.isEmpty()) return {};
+ return QDir::cleanPath(baseDir + "/" + file);
+ };
+
+ struct { const char* name; QString file; } events[] = {
+ {"msg_incoming", msgInFile},
+ {"msg_outgoing", msgOutFile},
+ {"connect", connectFile},
+ {"disconnect", disconnectFile},
+ };
+ for (auto& e : events) {
+ if (!e.file.isEmpty()) {
+ QString path = resolvePath(e.file);
+ sm->loadSound(e.name, path);
+ }
+ }
+
+ if (m_db && m_db->isOpen()) {
+ sm->setEnabled(m_db->getUiStateInt("sound_enabled", 1));
+ sm->setVolume(m_db->getUiStateInt("sound_volume", 80) / 100.0f);
+ QString soundEvents[] = {"msg_incoming", "msg_outgoing", "connect", "disconnect"};
+ for (int i = 0; i < 4; i++) {
+ const QString& ek = soundEvents[i];
+ bool on = m_db->getUiStateInt(QStringLiteral("sound_%1_on").arg(ek), i == 0 ? 1 : 0);
+ float ev = m_db->getUiStateInt(QStringLiteral("sound_%1_vol").arg(ek), 80) / 100.0f;
+ sm->setEvent(ek, on, ev);
+ }
+ }
}
if (m_db) {
diff --git a/tools/chatgui/src/settingsdialog.cpp b/tools/chatgui/src/settingsdialog.cpp
index cc5180ed..8ec92d49 100644
--- a/tools/chatgui/src/settingsdialog.cpp
+++ b/tools/chatgui/src/settingsdialog.cpp
@@ -86,7 +86,7 @@ SettingsDialog::SettingsDialog(const QString& configPath, DbManager* db, QWidget
m_nodesPage = new NodesPage(db, m_pages);
m_pages->addWidget(m_nodesPage);
- m_soundPage = new SoundSettingsPage(m_pages);
+ m_soundPage = new SoundSettingsPage(db, m_pages);
m_pages->addWidget(m_soundPage);
rightLayout->addWidget(m_pages, 1);
@@ -124,7 +124,7 @@ SettingsDialog::SettingsDialog(const QString& configPath, DbManager* db, QWidget
// Load current values
loadProfileFromConfig(m_configPath);
m_networkPage->loadFromConfig(m_configPath);
- m_soundPage->loadFromConfig(m_configPath);
+ m_soundPage->loadFromDb();
m_categoryList->setCurrentRow(0);
}
@@ -148,7 +148,7 @@ void SettingsDialog::onCategoryChanged(int row) {
if (row == 2) m_databasePage->refreshStatus();
if (row == 3) m_statusPage->refreshStatus();
if (row == 4) m_nodesPage->refreshNodes();
- if (row == 5) m_soundPage->loadFromConfig(m_configPath);
+ if (row == 5) m_soundPage->loadFromDb();
}
void SettingsDialog::onSave() {
@@ -188,6 +188,6 @@ void SettingsDialog::onSave() {
f.close();
m_networkPage->saveToConfig(m_configPath);
- m_soundPage->saveToConfig(m_configPath);
+ m_soundPage->applyAndSave();
accept();
}
diff --git a/tools/chatgui/src/sound_manager.cpp b/tools/chatgui/src/sound_manager.cpp
index 7b8813d3..da477ab6 100644
--- a/tools/chatgui/src/sound_manager.cpp
+++ b/tools/chatgui/src/sound_manager.cpp
@@ -31,30 +31,17 @@ void SoundManager::shutdown() {
if (!m_initialized) return;
for (auto it = m_sounds.begin(); it != m_sounds.end(); ++it) {
- if (it->sound) {
- ma_sound_uninit(it->sound);
- delete it->sound;
- }
- if (it->decoder) {
- ma_decoder_uninit(it->decoder);
- delete it->decoder;
- }
+ if (it->sound) { ma_sound_uninit(it->sound); delete it->sound; }
+ if (it->decoder) { ma_decoder_uninit(it->decoder); delete it->decoder; }
}
m_sounds.clear();
- if (m_engine) {
- ma_engine_uninit(m_engine);
- delete m_engine;
- m_engine = nullptr;
- }
+ if (m_engine) { ma_engine_uninit(m_engine); delete m_engine; m_engine = nullptr; }
m_initialized = false;
qDebug("SoundManager: shutdown");
}
-void SoundManager::setEnabled(bool enabled) {
- m_enabled = enabled;
- qDebug("SoundManager: enabled=%d", enabled);
-}
+void SoundManager::setEnabled(bool enabled) { m_enabled = enabled; }
bool SoundManager::isEnabled() const { return m_enabled; }
@@ -62,15 +49,25 @@ void SoundManager::setVolume(float volume) {
if (volume < 0.0f) volume = 0.0f;
if (volume > 1.0f) volume = 1.0f;
m_volume = volume;
- for (auto it = m_sounds.begin(); it != m_sounds.end(); ++it) {
- if (it->sound)
- ma_sound_set_volume(it->sound, m_volume);
- }
}
float SoundManager::volume() const { return m_volume; }
-bool SoundManager::loadSound(const QString& name, const QString& qrcPath) {
+void SoundManager::setEvent(const QString& name, bool enabled, float volume) {
+ if (volume < 0.0f) volume = 0.0f;
+ if (volume > 1.0f) volume = 1.0f;
+ EventSettings es;
+ es.enabled = enabled;
+ es.volume = volume;
+ m_eventSettings[name] = es;
+ qDebug("SoundManager: event '%s' enabled=%d vol=%.2f", qPrintable(name), enabled, volume);
+}
+
+SoundManager::EventSettings SoundManager::eventSettings(const QString& name) const {
+ return m_eventSettings.value(name);
+}
+
+bool SoundManager::loadSound(const QString& name, const QString& filePath) {
if (!m_initialized) {
qWarning("SoundManager: not initialized, cannot load %s", qPrintable(name));
return false;
@@ -80,9 +77,9 @@ bool SoundManager::loadSound(const QString& name, const QString& qrcPath) {
return false;
}
- QFile f(qrcPath);
+ QFile f(filePath);
if (!f.open(QIODevice::ReadOnly)) {
- qWarning("SoundManager: cannot open %s", qPrintable(qrcPath));
+ qWarning("SoundManager: cannot open %s", qPrintable(filePath));
return false;
}
@@ -105,26 +102,29 @@ bool SoundManager::loadSound(const QString& name, const QString& qrcPath) {
0, NULL, ls.sound);
if (result != MA_SUCCESS) {
qWarning("SoundManager: sound init failed for %s (%d)", qPrintable(name), result);
- ma_decoder_uninit(ls.decoder);
- delete ls.decoder;
+ ma_decoder_uninit(ls.decoder); delete ls.decoder;
delete ls.sound;
return false;
}
- ma_sound_set_volume(ls.sound, m_volume);
m_sounds.insert(name, ls);
qDebug("SoundManager: loaded '%s' from %s (%lld bytes)",
- qPrintable(name), qPrintable(qrcPath), (long long)ls.mp3Data.size());
+ qPrintable(name), qPrintable(filePath), (long long)ls.mp3Data.size());
return true;
}
void SoundManager::play(const QString& name) {
if (!m_enabled || !m_initialized) return;
+ EventSettings es = m_eventSettings.value(name);
+ if (!es.enabled) return;
+
auto it = m_sounds.find(name);
if (it == m_sounds.end()) return;
ma_sound* s = it->sound;
+ float effectiveVolume = m_volume * es.volume;
+ ma_sound_set_volume(s, effectiveVolume);
ma_sound_stop(s);
ma_sound_seek_to_pcm_frame(s, 0);
ma_sound_start(s);
diff --git a/tools/chatgui/src/sound_manager.h b/tools/chatgui/src/sound_manager.h
index 48e78bd2..a30172c1 100644
--- a/tools/chatgui/src/sound_manager.h
+++ b/tools/chatgui/src/sound_manager.h
@@ -12,6 +12,11 @@ struct ma_sound;
class SoundManager : public QObject {
Q_OBJECT
public:
+ struct EventSettings {
+ bool enabled = true;
+ float volume = 0.8f;
+ };
+
static SoundManager* instance();
bool init();
@@ -22,7 +27,10 @@ public:
void setVolume(float volume);
float volume() const;
- bool loadSound(const QString& name, const QString& qrcPath);
+ void setEvent(const QString& name, bool enabled, float volume);
+ EventSettings eventSettings(const QString& name) const;
+
+ bool loadSound(const QString& name, const QString& filePath);
void play(const QString& name);
private:
@@ -39,5 +47,6 @@ private:
bool m_initialized = false;
bool m_enabled = true;
float m_volume = 0.8f;
+ QHash m_eventSettings;
QHash m_sounds;
};
diff --git a/tools/chatgui/src/soundsettingspage.cpp b/tools/chatgui/src/soundsettingspage.cpp
index 0864a558..6e38e13f 100644
--- a/tools/chatgui/src/soundsettingspage.cpp
+++ b/tools/chatgui/src/soundsettingspage.cpp
@@ -1,16 +1,22 @@
#include "soundsettingspage.h"
#include "sound_manager.h"
+#include "../db/db_manager.h"
+#include "../transport/gui_bridge.h"
+#include "../../lib/mem.h"
+extern "C" {
+#include "../transport/chat_core.h"
+}
#include
#include
#include
#include
#include
-#include
-#include
+#include
#include
-SoundSettingsPage::SoundSettingsPage(QWidget* parent)
+SoundSettingsPage::SoundSettingsPage(DbManager* db, QWidget* parent)
: QWidget(parent)
+ , m_db(db)
{
auto* layout = new QVBoxLayout(this);
layout->setContentsMargins(0, 12, 0, 0);
@@ -28,8 +34,9 @@ SoundSettingsPage::SoundSettingsPage(QWidget* parent)
m_volumeSlider = new QSlider(Qt::Horizontal, this);
m_volumeSlider->setRange(0, 100);
m_volumeSlider->setFixedWidth(200);
- m_volumeSlider->setStyleSheet("QSlider::groove:horizontal { height: 6px; border-radius: 3px; background: palette(mid); }"
- "QSlider::handle:horizontal { width: 14px; height: 14px; margin: -4px 0; border-radius: 7px; background: #3390EC; }");
+ m_volumeSlider->setStyleSheet(
+ "QSlider::groove:horizontal { height: 6px; border-radius: 3px; background: palette(mid); }"
+ "QSlider::handle:horizontal { width: 14px; height: 14px; margin: -4px 0; border-radius: 7px; background: #3390EC; }");
volLayout->addWidget(m_volumeSlider);
m_volumeLabel = new QLabel("80%", this);
@@ -39,82 +46,136 @@ SoundSettingsPage::SoundSettingsPage(QWidget* parent)
volLayout->addStretch();
layout->addLayout(volLayout);
+ auto* sep = new QFrame(this);
+ sep->setFrameShape(QFrame::HLine);
+ sep->setStyleSheet("QFrame { color: palette(mid); }");
+ layout->addWidget(sep);
+
+ auto* header = new QLabel("Events", this);
+ header->setStyleSheet("font-size: 13px; font-weight: bold;");
+ layout->addWidget(header);
+
+ m_events[0] = {"msg_incoming", "Incoming message", nullptr, nullptr, nullptr};
+ m_events[1] = {"msg_outgoing", "Outgoing message", nullptr, nullptr, nullptr};
+ m_events[2] = {"connect", "Peer connected", nullptr, nullptr, nullptr};
+ m_events[3] = {"disconnect", "Peer disconnected", nullptr, nullptr, nullptr};
+
+ for (int i = 0; i < 4; i++) {
+ auto& e = m_events[i];
+ auto* row = new QHBoxLayout();
+ row->setSpacing(8);
+
+ auto* lbl = new QLabel(e.label, this);
+ lbl->setFixedWidth(140);
+ lbl->setStyleSheet("font-size: 13px;");
+ row->addWidget(lbl);
+
+ e.check = new QCheckBox(this);
+ row->addWidget(e.check);
+
+ e.slider = new QSlider(Qt::Horizontal, this);
+ e.slider->setRange(0, 100);
+ e.slider->setFixedWidth(100);
+ e.slider->setStyleSheet(
+ "QSlider::groove:horizontal { height: 4px; border-radius: 2px; background: palette(mid); }"
+ "QSlider::handle:horizontal { width: 10px; height: 10px; margin: -3px 0; border-radius: 5px; background: #3390EC; }");
+ row->addWidget(e.slider);
+
+ e.volLabel = new QLabel("80%", this);
+ e.volLabel->setFixedWidth(30);
+ e.volLabel->setStyleSheet("font-size: 12px;");
+ row->addWidget(e.volLabel);
+
+ row->addStretch();
+ layout->addLayout(row);
+
+ connect(e.slider, &QSlider::valueChanged, this, [this, i](int val) {
+ m_events[i].volLabel->setText(QString::number(val) + "%");
+ });
+ }
+
layout->addStretch();
- connect(m_enabledCheck, &QCheckBox::stateChanged, this, &SoundSettingsPage::onEnabledChanged);
+ connect(m_enabledCheck, &QCheckBox::stateChanged, this, [this](int state) {
+ bool on = (state == Qt::Checked);
+ m_volumeSlider->setEnabled(on);
+ m_volumeLabel->setEnabled(on);
+ });
connect(m_volumeSlider, &QSlider::valueChanged, this, [this](int val) {
m_volumeLabel->setText(QString::number(val) + "%");
});
}
-void SoundSettingsPage::onEnabledChanged(int state) {
- m_volumeSlider->setEnabled(state == Qt::Checked);
- m_volumeLabel->setEnabled(state == Qt::Checked);
-}
-
-void SoundSettingsPage::loadFromConfig(const QString& path) {
- QFile f(path);
- if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) return;
-
- bool enabled = true;
- int volume = 80;
- bool inGui = false;
- QTextStream in(&f);
- while (!in.atEnd()) {
- QString line = in.readLine().trimmed();
- if (line == "[gui]") { inGui = true; continue; }
- if (line.startsWith("[") && line.endsWith("]")) inGui = false;
- if (!inGui) continue;
- if (line.startsWith("sound_enabled="))
- enabled = line.mid(QString("sound_enabled=").size()).trimmed() != "0";
- else if (line.startsWith("sound_volume="))
- volume = line.mid(QString("sound_volume=").size()).trimmed().toInt();
- }
- f.close();
+void SoundSettingsPage::loadFromDb() {
+ if (!m_db) return;
+ bool enabled = m_db->getUiStateInt("sound_enabled", 1);
m_enabledCheck->setChecked(enabled);
- m_volumeSlider->setValue(volume);
+
+ int vol = m_db->getUiStateInt("sound_volume", 80);
+ m_volumeSlider->setValue(vol);
m_volumeSlider->setEnabled(enabled);
m_volumeLabel->setEnabled(enabled);
- m_volumeLabel->setText(QString::number(volume) + "%");
+ m_volumeLabel->setText(QString::number(vol) + "%");
+
+ for (int i = 0; i < 4; i++) {
+ auto& e = m_events[i];
+ QString onKey = QStringLiteral("sound_%1_on").arg(e.key);
+ QString volKey = QStringLiteral("sound_%1_vol").arg(e.key);
+ bool on = m_db->getUiStateInt(onKey, i == 0 ? 1 : 0); // only msg_incoming on by default
+ int ev = m_db->getUiStateInt(volKey, 80);
+ e.check->setChecked(on);
+ e.slider->setValue(ev);
+ e.volLabel->setText(QString::number(ev) + "%");
+ }
}
-void SoundSettingsPage::saveToConfig(const QString& path) const {
- QFile f(path);
- if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) return;
- QString content = f.readAll();
- f.close();
-
- QStringList lines = content.split('\n');
-
- bool inGui = false;
- bool foundEnabled = false, foundVolume = false;
- for (int i = 0; i < lines.size(); i++) {
- QString line = lines[i].trimmed();
- if (line == "[gui]") { inGui = true; continue; }
- if (line.startsWith("[") && line.endsWith("]")) inGui = false;
- if (!inGui) continue;
-
- if (line.startsWith("sound_enabled=")) {
- lines[i] = QString("sound_enabled=%1").arg(m_enabledCheck->isChecked() ? 1 : 0);
- foundEnabled = true;
- } else if (line.startsWith("sound_volume=")) {
- lines[i] = QString("sound_volume=%1").arg(m_volumeSlider->value());
- foundVolume = true;
+void SoundSettingsPage::applyAndSave() {
+ SoundManager* sm = SoundManager::instance();
+
+ bool enabled = m_enabledCheck->isChecked();
+ float masterVol = m_volumeSlider->value() / 100.0f;
+ sm->setEnabled(enabled);
+ sm->setVolume(masterVol);
+
+ for (int i = 0; i < 4; i++) {
+ auto& e = m_events[i];
+ bool on = e.check->isChecked();
+ float ev = e.slider->value() / 100.0f;
+ sm->setEvent(e.key, on, ev);
+
+ /* async DB persist — key\0value\0 */
+ QString onKey = QStringLiteral("sound_%1_on").arg(e.key);
+ QString volKey = QStringLiteral("sound_%1_vol").arg(e.key);
+ QByteArray onVal = on ? QByteArray("1") : QByteArray("0");
+ QByteArray volVal = QByteArray::number(e.slider->value());
+
+ for (int j = 0; j < 2; j++) {
+ const char* k = j == 0 ? onKey.toUtf8().constData() : volKey.toUtf8().constData();
+ const char* v = j == 0 ? onVal.constData() : volVal.constData();
+ size_t klen = strlen(k), vlen = strlen(v);
+ size_t total = klen + 1 + vlen + 1;
+ if (total > 256) continue;
+ void* arg = u_malloc(total);
+ memcpy(arg, k, klen + 1);
+ memcpy((char*)arg + klen + 1, v, vlen + 1);
+ gui_bridge_post_uasync_fn(chat_core_save_ui_state_trampoline, arg);
}
}
- if (!foundEnabled || !foundVolume) {
- for (int i = 0; i < lines.size(); i++) {
- if (lines[i].trimmed() == "[gui]") {
- if (!foundEnabled) lines.insert(i + 1, QString("sound_enabled=%1").arg(m_enabledCheck->isChecked() ? 1 : 0));
- if (!foundVolume) lines.insert(i + 1, QString("sound_volume=%1").arg(m_volumeSlider->value()));
- break;
- }
+ /* save global settings */
+ {
+ const char* keys[] = {"sound_enabled", "sound_volume"};
+ QByteArray vals[] = {enabled ? QByteArray("1") : QByteArray("0"),
+ QByteArray::number(m_volumeSlider->value())};
+ for (int j = 0; j < 2; j++) {
+ size_t klen = strlen(keys[j]), vlen = vals[j].size();
+ size_t total = klen + 1 + vlen + 1;
+ if (total > 256) continue;
+ void* arg = u_malloc(total);
+ memcpy(arg, keys[j], klen + 1);
+ memcpy((char*)arg + klen + 1, vals[j].constData(), vlen + 1);
+ gui_bridge_post_uasync_fn(chat_core_save_ui_state_trampoline, arg);
}
}
-
- f.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text);
- f.write(lines.join('\n').toUtf8());
- f.close();
}
diff --git a/tools/chatgui/src/soundsettingspage.h b/tools/chatgui/src/soundsettingspage.h
index e553b278..bf1e0fa1 100644
--- a/tools/chatgui/src/soundsettingspage.h
+++ b/tools/chatgui/src/soundsettingspage.h
@@ -2,6 +2,7 @@
#include
+class DbManager;
class QCheckBox;
class QSlider;
class QLabel;
@@ -9,16 +10,23 @@ class QLabel;
class SoundSettingsPage : public QWidget {
Q_OBJECT
public:
- explicit SoundSettingsPage(QWidget* parent = nullptr);
+ explicit SoundSettingsPage(DbManager* db, QWidget* parent = nullptr);
- void loadFromConfig(const QString& configPath);
- void saveToConfig(const QString& configPath) const;
-
-private slots:
- void onEnabledChanged(int state);
+ void loadFromDb();
+ void applyAndSave();
private:
- QCheckBox* m_enabledCheck;
- QSlider* m_volumeSlider;
- QLabel* m_volumeLabel;
+ struct EventRow {
+ const char* key;
+ const char* label;
+ QCheckBox* check = nullptr;
+ QSlider* slider = nullptr;
+ QLabel* volLabel = nullptr;
+ };
+
+ DbManager* m_db = nullptr;
+ QCheckBox* m_enabledCheck;
+ QSlider* m_volumeSlider;
+ QLabel* m_volumeLabel;
+ EventRow m_events[4];
};
diff --git a/tools/chatgui/transport/chat_core.c b/tools/chatgui/transport/chat_core.c
index 674311f5..708e1642 100644
--- a/tools/chatgui/transport/chat_core.c
+++ b/tools/chatgui/transport/chat_core.c
@@ -394,6 +394,27 @@ void chat_core_update_my_name_trampoline(void* arg) {
u_free(arg);
}
+void chat_core_save_ui_state(const char* key, const char* value) {
+ if (!g_cc.initialized || !key || !value) return;
+ sqlite3_stmt* st = NULL;
+ sqlite3_prepare_v2(g_cc.db,
+ "INSERT OR REPLACE INTO ui_state(key, value) VALUES(?, ?)", -1, &st, NULL);
+ if (st) {
+ sqlite3_bind_text(st, 1, key, -1, SQLITE_STATIC);
+ sqlite3_bind_text(st, 2, value, -1, SQLITE_STATIC);
+ sqlite3_step(st); sqlite3_finalize(st);
+ }
+}
+
+struct save_ui_state_arg { char data[256]; };
+void chat_core_save_ui_state_trampoline(void* arg) {
+ struct save_ui_state_arg* a = (struct save_ui_state_arg*)arg;
+ const char* key = a->data;
+ const char* value = key + strlen(key) + 1;
+ chat_core_save_ui_state(key, value);
+ u_free(arg);
+}
+
static void chat_core_collect_status(void);
void chat_core_collect_status_trampoline(void* arg) {
diff --git a/tools/chatgui/transport/chat_core.h b/tools/chatgui/transport/chat_core.h
index bdea89f3..3eda1ba4 100644
--- a/tools/chatgui/transport/chat_core.h
+++ b/tools/chatgui/transport/chat_core.h
@@ -98,6 +98,10 @@ void chat_core_update_my_name(const char* name);
void chat_core_update_my_name_trampoline(void* arg);
void chat_core_sync_my_addresses(void);
+/* Сохранение key-value в ui_state (вызывается из uasync-потока) */
+void chat_core_save_ui_state(const char* key, const char* value);
+void chat_core_save_ui_state_trampoline(void* arg);
+
/* Трамплин для gui_bridge_post_uasync (GUI → uasync) */
void chat_core_submit_trampoline(void* arg);
diff --git a/tools/chatgui/transport/node_config.cpp b/tools/chatgui/transport/node_config.cpp
index 2556e37f..5ea5dd14 100644
--- a/tools/chatgui/transport/node_config.cpp
+++ b/tools/chatgui/transport/node_config.cpp
@@ -37,7 +37,8 @@ static const QSet CONTROL_KEYS = {
static const QSet GUI_KEYS = {
"db_path", "debug_file", "debug_level", "debug_categories",
- "sound_enabled", "sound_volume"
+ "sound_dir", "sound_msg_incoming", "sound_msg_outgoing",
+ "sound_connect", "sound_disconnect"
};
static const QSet ALLOWED_KEYS_KEYS = {