diff --git a/README.md b/README.md index ca81cd9..7225635 100644 --- a/README.md +++ b/README.md @@ -76,8 +76,9 @@ speaks (validated live against a real instance). ## Status -Version 0.5.2 — protocol and C++ core validated against a real `hermes-webui` +Version 0.5.3 — protocol and C++ core validated against a real `hermes-webui` (login, sessions, streaming chat, TTS, STT, voice dialog, profile selection); UI in English, Italian, French and German; replies rendered as formatted -markdown and cleaned before read-aloud; every request has a watchdog -timeout; on-device testing still ongoing. +markdown and cleaned before read-aloud; settings persist correctly from the +launcher (SailJail-safe config path); every request has a watchdog timeout; +on-device testing still ongoing. diff --git a/docs/BUILD.md b/docs/BUILD.md index fd877a4..1207b9c 100644 --- a/docs/BUILD.md +++ b/docs/BUILD.md @@ -76,12 +76,12 @@ richiede il probe WebUI su :8899). Esito 12/09/2026: unit 9/9, live completo OK. ```bash cd ~/workspace -tar czf harbour-hermes-0.5.2.tar.gz \ - --transform 's,^harbour-hermes,harbour-hermes-0.5.2,' \ +tar czf harbour-hermes-0.5.3.tar.gz \ + --transform 's,^harbour-hermes,harbour-hermes-0.5.3,' \ --exclude='.git' harbour-hermes ``` -Lo spec si aspetta la directory `harbour-hermes-0.5.2/` (pattern degli altri +Lo spec si aspetta la directory `harbour-hermes-0.5.3/` (pattern degli altri progetti: cercato in modo robusto anche per i sorgenti live di sfdk). ## Traduzioni diff --git a/rpm/harbour-hermes.spec b/rpm/harbour-hermes.spec index d716089..bd88ead 100644 --- a/rpm/harbour-hermes.spec +++ b/rpm/harbour-hermes.spec @@ -1,6 +1,6 @@ Name: harbour-hermes Summary: Client for the Hermes Web UI with voice -Version: 0.5.2 +Version: 0.5.3 Release: 1 Group: Qt/Qt License: MIT @@ -25,6 +25,16 @@ Sessions list, chat with live streaming replies, voice dictation (server-side TTS), plus a hands-free conversation mode. %changelog +* Sun Sep 13 2026 Carlo Baratto - 0.5.3-1 +- Fix persistenza delle impostazioni quando l'app parte dal launcher: + QSettings "di default" scriveva in ~/.config/harbour/hermes.conf, FUORI + dalla directory che la sandbox SailJail rende persistente + (~/.config/harbour/hermes/): le modifiche si perdevano a ogni riavvio + (da terminale invece restavano). Ora QSettings punta esplicitamente a + ~/.config/harbour/hermes/hermes.conf (stesso pattern di + harbour-calibreweb e harbour-quill); ogni scrittura fa sync immediato; + migrazione automatica dei valori rimasti nel vecchio percorso; log + diagnostico del percorso impostazioni all'avvio * Sun Sep 13 2026 Carlo Baratto - 0.5.2-1 - Chat: le risposte di Hermes sono mostrate formattate (grassetti, corsivi, titoli, liste, citazioni, codice, link) con un convertitore markdown -> diff --git a/src/main.cpp b/src/main.cpp index ddb316a..b7d4096 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -21,9 +21,9 @@ Q_DECL_EXPORT int main(int argc, char *argv[]) // AppConfigLocation = ~/.config/harbour/hermes (zona persistente della sandbox). app->setOrganizationName(QStringLiteral("harbour")); app->setApplicationName(QStringLiteral("hermes")); - app->setApplicationVersion(QStringLiteral("0.5.2")); + app->setApplicationVersion(QStringLiteral("0.5.3")); - qDebug() << "harbour-hermes v0.5.2 build" << __DATE__ << __TIME__; + qDebug() << "harbour-hermes v0.5.3 build" << __DATE__ << __TIME__; // Traduzioni: it/fr/de da /usr/share/harbour-hermes/translations, in base // alla lingua di sistema (l'inglese e' la lingua sorgente: nessun .qm). diff --git a/src/settings.cpp b/src/settings.cpp index d6ace6e..c95b4c4 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -1,5 +1,11 @@ #include "settings.h" +#include +#include +#include +#include +#include + // Nessuna configurazione personale e' compilata nel binario: i default sono // neutri e TUTTO si imposta dalle Impostazioni dell'app (QSettings in // ~/.config/harbour/hermes.conf). Vuoto = "decide il server". @@ -7,9 +13,65 @@ static const char *kDefaultBaseUrl = ""; static const char *kDefaultTtsVoice = ""; static const char *kDefaultTtsEngine = ""; +QString Settings::configPath() +{ + // Path canonico per la sandbox SailJail di Sailfish 5.x: con org/app + // dichiarati (harbour/hermes, vedi main.cpp e X-Sailjail nel desktop + // file) AppConfigLocation = ~/.config/harbour/hermes e' l'unica zona + // config che la sandbox rende PERSISTENTE tra gli avvii. NB: il + // QSettings "di default" scriverebbe in ~/.config/harbour/hermes.conf, + // FUORI da questa directory: dal launcher le impostazioni andrebbero + // perse a ogni riavvio (era il bug del "non salva le impostazioni"). + const QString dir = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation); + QDir().mkpath(dir); + return dir + QStringLiteral("/hermes.conf"); +} + Settings::Settings(QObject *parent) : QObject(parent) + , m_settings(configPath(), QSettings::IniFormat) { + migrateIfNeeded(); + // Diagnostica visibile nel log del device: dove vivono le impostazioni. + qDebug() << "Settings file:" << m_settings.fileName(); + if (m_settings.status() != QSettings::NoError) + qDebug() << "Settings WARNING: file di configurazione non accessibile"; + qDebug() << "Settings read: baseUrl=[" << baseUrl() << "] voice=[" + << ttsVoice() << "] profile=[" << profile() << "]"; +} + +void Settings::migrateIfNeeded() +{ + if (QFile::exists(m_settings.fileName())) + return; // c'e' gia' il file canonico: nessuna migrazione da fare + // Importa i valori rimasti nella vecchia posizione "di default" + // (~/.config/harbour/hermes.conf), ad esempio lasciati dagli avvii da + // terminale prima del fix del percorso. La prima sorgente con valori vince. + const QStringList candidates = QStringList() + << QDir::homePath() + QStringLiteral("/.config/harbour/hermes.conf") + << QStringLiteral("/home/defaultuser/.config/harbour/hermes.conf") + << QStringLiteral("/home/nemo/.config/harbour/hermes.conf"); + for (const QString &cand : candidates) { + if (cand == m_settings.fileName() || !QFile::exists(cand)) + continue; + QSettings src(cand, QSettings::IniFormat); + const QStringList keys = src.allKeys(); + if (keys.isEmpty()) + continue; + for (const QString &k : keys) + m_settings.setValue(k, src.value(k)); + m_settings.sync(); + qDebug() << "Settings: importati" << keys.size() << "valori da" << cand; + return; + } +} + +void Settings::store(const QString &key, const QVariant &value) +{ + m_settings.setValue(key, value); + // Flush immediato: se l'app viene terminata dal sistema, le modifiche + // alle impostazioni restano comunque su disco. + m_settings.sync(); } QString Settings::baseUrl() const @@ -28,7 +90,7 @@ void Settings::setBaseUrl(const QString &url) v.chop(1); if (v == baseUrl()) return; - m_settings.setValue(QStringLiteral("baseUrl"), v); + store(QStringLiteral("baseUrl"), v); emit baseUrlChanged(); } @@ -41,7 +103,7 @@ void Settings::setTtsVoice(const QString &voice) { if (voice == ttsVoice()) return; - m_settings.setValue(QStringLiteral("ttsVoice"), voice); + store(QStringLiteral("ttsVoice"), voice); emit ttsVoiceChanged(); } @@ -54,7 +116,7 @@ void Settings::setTtsEngine(const QString &engine) { if (engine == ttsEngine()) return; - m_settings.setValue(QStringLiteral("ttsEngine"), engine); + store(QStringLiteral("ttsEngine"), engine); emit ttsEngineChanged(); } @@ -67,7 +129,7 @@ void Settings::setTtsAutoRead(bool on) { if (on == ttsAutoRead()) return; - m_settings.setValue(QStringLiteral("ttsAutoRead"), on); + store(QStringLiteral("ttsAutoRead"), on); emit ttsAutoReadChanged(); } @@ -80,7 +142,7 @@ void Settings::setDictationAutoSend(bool on) { if (on == dictationAutoSend()) return; - m_settings.setValue(QStringLiteral("dictationAutoSend"), on); + store(QStringLiteral("dictationAutoSend"), on); emit dictationAutoSendChanged(); } @@ -93,7 +155,7 @@ void Settings::setVadSilenceMs(int ms) { if (ms == vadSilenceMs()) return; - m_settings.setValue(QStringLiteral("vadSilenceMs"), ms); + store(QStringLiteral("vadSilenceMs"), ms); emit vadSilenceMsChanged(); } @@ -106,7 +168,7 @@ void Settings::setVadThreshold(int threshold) { if (threshold == vadThreshold()) return; - m_settings.setValue(QStringLiteral("vadThreshold"), threshold); + store(QStringLiteral("vadThreshold"), threshold); emit vadThresholdChanged(); } @@ -119,7 +181,7 @@ void Settings::setVadMaxMs(int ms) { if (ms == vadMaxMs()) return; - m_settings.setValue(QStringLiteral("vadMaxMs"), ms); + store(QStringLiteral("vadMaxMs"), ms); emit vadMaxMsChanged(); } @@ -135,7 +197,7 @@ void Settings::setProfile(const QString &name) const QString clean = name.trimmed(); if (clean == profile()) return; - m_settings.setValue(QStringLiteral("profile"), clean); + store(QStringLiteral("profile"), clean); emit profileChanged(); } @@ -154,7 +216,7 @@ void Settings::setTtsVoices(const QStringList &list) } if (clean == ttsVoices()) return; - m_settings.setValue(QStringLiteral("ttsVoices"), clean); + store(QStringLiteral("ttsVoices"), clean); emit ttsVoicesChanged(); } @@ -173,7 +235,7 @@ void Settings::setTtsEngines(const QStringList &list) } if (clean == ttsEngines()) return; - m_settings.setValue(QStringLiteral("ttsEngines"), clean); + store(QStringLiteral("ttsEngines"), clean); emit ttsEnginesChanged(); } @@ -186,6 +248,6 @@ void Settings::setDefaultWorkspace(const QString &ws) { if (ws == defaultWorkspace()) return; - m_settings.setValue(QStringLiteral("defaultWorkspace"), ws); + store(QStringLiteral("defaultWorkspace"), ws); emit defaultWorkspaceChanged(); } diff --git a/src/settings.h b/src/settings.h index fac5c16..1cc5a49 100644 --- a/src/settings.h +++ b/src/settings.h @@ -5,6 +5,7 @@ #include #include #include +#include // Impostazioni persistenti dell'app. // QSettings con OrganizationName "harbour" / ApplicationName "hermes" (impostati @@ -40,6 +41,10 @@ class Settings : public QObject public: explicit Settings(QObject *parent = nullptr); + // Percorso del file impostazioni, nella zona persistente della sandbox + // SailJail (~/.config/harbour/hermes/hermes.conf). Pubblico per i test. + static QString configPath(); + QString baseUrl() const; void setBaseUrl(const QString &url); @@ -93,6 +98,9 @@ signals: void defaultWorkspaceChanged(); private: + void migrateIfNeeded(); + void store(const QString &key, const QVariant &value); + QSettings m_settings; }; diff --git a/tests/core_test.cpp b/tests/core_test.cpp index e6dac50..8a9256c 100644 --- a/tests/core_test.cpp +++ b/tests/core_test.cpp @@ -124,6 +124,28 @@ static void testMarkdown() "speech: righe vuote compattate"); } +static void testSettingsPersistence() +{ + qDebug() << "== unit: persistenza impostazioni =="; + const QString cfg = Settings::configPath(); + qDebug() << "file di test:" << cfg; + QFile::remove(cfg); // stato pulito: nessun valore precedente + { + Settings s1; + s1.setBaseUrl(QStringLiteral("https://esempio.test")); + s1.setTtsVoice(QStringLiteral("it-IT-ElsaNeural")); + } + { + // Una SECONDA istanza rilegge dallo stesso file: prova che i valori + // sono finiti su disco (non solo in memoria) -> persistenza vera. + Settings s2; + CHECK(s2.baseUrl() == QLatin1String("https://esempio.test"), + "baseUrl riletto da una nuova istanza (passa dal file)"); + CHECK(s2.ttsVoice() == QLatin1String("it-IT-ElsaNeural"), + "voce riletta da una nuova istanza (passa dal file)"); + } +} + int main(int argc, char **argv) { QCoreApplication app(argc, argv); @@ -132,6 +154,7 @@ int main(int argc, char **argv) testParser(); testMarkdown(); + testSettingsPersistence(); if (!app.arguments().contains(QStringLiteral("--live"))) { qDebug() << (failures == 0 ? "UNIT: tutti i test passati" : "UNIT: FALLIMENTI");