FS#13978 - rbutil: Improve GUI responsiveness during voice/talk file generation (Alessio Lenzi)

rockbox-gerrit-noreply--- via rockbox-cvs <[email protected]>
Newsgroups gmane.comp.systems.archos.rockbox.cvs
Message-ID <[email protected]>
commit 0726ec93517a61f602679ab052b083217ec9c96d
Author: Solomon Peachy <[email protected]>
Date:   Sun Aug 9 15:54:58 2026 -0400

     FS#13978 - rbutil: Improve GUI responsiveness during voice/talk file generation (Alessio Lenzi)
    
    Voice file and Talk clip generation currently run synchronously in the
    GUI thread. During long TTS operations this prevents the progress window
    from processing input and accessibility events. On Windows this makes
    the progress list unavailable to screen readers such as NVDA and can
    make Rockbox Utility appear to be hung.
    
    The attached patch moves VoiceFileCreator and TalkFileCreator to
    low-priority QThreads while keeping ProgressLoggerGui in the GUI thread.
    Progress and log signals are delivered through queued Qt connections.
    
    Cancellation remains available while a synchronous TTS request is
    running. The abort flags used across threads are atomic and the abort
    signal is sent through a direct connection. Worker objects and threads
    are deleted through the standard finished / deleteLater lifecycle.
    
    Talk generation now passes all selected folders to one worker and
    processes them sequentially, emitting the final done signal only once.
    This preserves multiple-folder selection without starting work in the
    GUI thread.
    
    Change-Id: I58aa66333d43e1be249c84e0cf46c17c119a8360

diff --git a/utils/rbutilqt/base/talkfile.cpp b/utils/rbutilqt/base/talkfile.cpp
index 50afb1f323..3cd265b7f1 100644
--- a/utils/rbutilqt/base/talkfile.cpp
+++ b/utils/rbutilqt/base/talkfile.cpp
@@ -31,6 +31,34 @@ TalkFileCreator::TalkFileCreator(QObject* parent): QObject(parent)
 bool TalkFileCreator::createTalkFiles()
 {
     m_abort = false;
+    QStringList dirs = m_dirs;
+    if(dirs.isEmpty())
+        dirs.append(m_dir);
+
+    for(const QString& dir : dirs) {
+        if(m_abort)
+            break;
+
+        m_dir = dir;
+        LOG_INFO() << "creating talk files for folder" << m_dir;
+        if(!createTalkFilesForDir()) {
+            emit done(true);
+            return false;
+        }
+    }
+
+    if(m_abort) {
+        doAbort();
+        emit done(true);
+        return false;
+    }
+
+    emit done(false);
+    return true;
+}
+
+bool TalkFileCreator::createTalkFilesForDir()
+{
     QString errStr;
 
     emit logItem(tr("Starting Talk file generation for folder %1")
@@ -51,10 +79,10 @@ bool TalkFileCreator::createTalkFiles()
     // generate entries
     TalkGenerator generator(this);
     // no string corrections yet: do not set language for TalkGenerator.
-    connect(&generator, &TalkGenerator::done, this, &TalkFileCreator::done);
     connect(&generator, &TalkGenerator::logItem, this, &TalkFileCreator::logItem);
     connect(&generator, &TalkGenerator::logProgress, this, &TalkFileCreator::logProgress);
-    connect(this, &TalkFileCreator::aborted, &generator, &TalkGenerator::abort);
+    connect(this, &TalkFileCreator::aborted,
+            &generator, &TalkGenerator::abort, Qt::DirectConnection);
 
     if(generator.process(&m_talkList) == TalkGenerator::eERROR)
     {
@@ -77,7 +105,6 @@ bool TalkFileCreator::createTalkFiles()
 
     emit logItem(tr("Finished creating Talk files"),LOGOK);
     emit logProgress(1,1);
-    emit done(false);
 
     return true;
 }
@@ -101,7 +128,6 @@ void TalkFileCreator::doAbort()
 {
     cleanup();
     emit logProgress(0,1);
-    emit done(true);
 }
 //! \brief creates a list of what to generate
 //!
diff --git a/utils/rbutilqt/base/talkfile.h b/utils/rbutilqt/base/talkfile.h
index 014753da2a..3b57964aaf 100644
--- a/utils/rbutilqt/base/talkfile.h
+++ b/utils/rbutilqt/base/talkfile.h
@@ -22,6 +22,7 @@
 #ifndef TALKFILE_H
 #define TALKFILE_H
 
+#include <atomic>
 #include <QtCore>
 #include "progressloglevels.h"
 
@@ -37,6 +38,7 @@ public:
     bool createTalkFiles();
 
     void setDir(QString dir) {m_dir = dir;}
+    void setDirs(QStringList dirs) {m_dirs = dirs;}
     void setMountPoint(QString mountpoint) {m_mountpoint = mountpoint;}
 
     void setGenerateOnlyNew(bool ov) {m_generateOnlyNew = ov;}
@@ -55,6 +57,7 @@ signals:
     void logProgress(int, int); //! set progress bar.
 
 private:
+    bool createTalkFilesForDir();
     bool cleanup();
     QString stripExtension(QString filename);
     void doAbort();
@@ -64,6 +67,7 @@ private:
     bool createTalkList(QDir startDir);
 
     QString m_dir;
+    QStringList m_dirs;
     QString m_mountpoint;
 
     bool m_generateOnlyNew;
@@ -73,7 +77,7 @@ private:
     bool m_talkFiles;
     QStringList m_ignoreFiles;
 
-    bool m_abort;
+    std::atomic_bool m_abort{false};
 
     QList<TalkGenerator::TalkEntry> m_talkList;
 };
diff --git a/utils/rbutilqt/base/talkgenerator.h b/utils/rbutilqt/base/talkgenerator.h
index 6bdb22745e..95da967d4d 100644
--- a/utils/rbutilqt/base/talkgenerator.h
+++ b/utils/rbutilqt/base/talkgenerator.h
@@ -22,6 +22,7 @@
 #ifndef TALKGENERATOR_H
 #define TALKGENERATOR_H
 
+#include <atomic>
 #include <QtCore>
 #include "progressloglevels.h"
 
@@ -81,7 +82,7 @@ private:
     };
     QList<struct CorrectionItems> m_corrections;
 
-    bool m_abort;
+    std::atomic_bool m_abort{false};
 
 
 };
diff --git a/utils/rbutilqt/base/voicefile.cpp b/utils/rbutilqt/base/voicefile.cpp
index aaafd64cc8..32845edcec 100644
--- a/utils/rbutilqt/base/voicefile.cpp
+++ b/utils/rbutilqt/base/voicefile.cpp
@@ -33,15 +33,12 @@ VoiceFileCreator::VoiceFileCreator(QObject* parent) :QObject(parent)
 
 void VoiceFileCreator::abort()
 {
-    m_abort = true;
     emit aborted();
 }
 
 bool VoiceFileCreator::createVoiceFile()
 {
     m_talkList.clear();
-    m_abort = false;
-
     delete corrFile;
     corrFile = new QFile(":/builtin/voice-corrections.txt", this);
     corrFile->open(QIODevice::ReadOnly);
@@ -335,7 +332,8 @@ void VoiceFileCreator::create(void)
         connect(&generator, &TalkGenerator::done, this, &VoiceFileCreator::done);
         connect(&generator, &TalkGenerator::logItem, this, &VoiceFileCreator::logItem);
         connect(&generator, &TalkGenerator::logProgress, this, &VoiceFileCreator::logProgress);
-        connect(this, &VoiceFileCreator::aborted, &generator, &TalkGenerator::abort);
+        connect(this, &VoiceFileCreator::aborted,
+                &generator, &TalkGenerator::abort, Qt::DirectConnection);
 
         if(generator.process(&m_talkList, m_wavtrimThreshold) == TalkGenerator::eERROR)
         {
diff --git a/utils/rbutilqt/base/voicefile.h b/utils/rbutilqt/base/voicefile.h
index 2c63ae4c80..35abe2ec60 100644
--- a/utils/rbutilqt/base/voicefile.h
+++ b/utils/rbutilqt/base/voicefile.h
@@ -70,7 +70,6 @@ private:
     int m_voiceformat;
     QFile *corrFile; // the voice-corrections file
 
-    bool m_abort;
     QList<TalkGenerator::TalkEntry> m_talkList;
 };
 
diff --git a/utils/rbutilqt/createvoicewindow.cpp b/utils/rbutilqt/createvoicewindow.cpp
index f4679a2418..2d44ab0d32 100644
--- a/utils/rbutilqt/createvoicewindow.cpp
+++ b/utils/rbutilqt/createvoicewindow.cpp
@@ -28,7 +28,6 @@
 CreateVoiceWindow::CreateVoiceWindow(QWidget *parent) : QDialog(parent)
 {
     ui.setupUi(this);
-    voicecreator = new VoiceFileCreator(this);
     updateSettings();
     connect(ui.change,&QAbstractButton::clicked,this,&CreateVoiceWindow::change);
 }
@@ -46,12 +45,22 @@ void CreateVoiceWindow::change()
 
 void CreateVoiceWindow::accept()
 {
+    // Do not start a second job while voice creation is still running.
+    if(workerThread)
+        return;
+
     logger = new ProgressLoggerGui(this);
     connect(logger,&ProgressLoggerGui::closed,this,&QWidget::close);
     logger->show();    
 
     saveSettings();
     
+    // Voice generation invokes the TTS engine and encoder synchronously. Run
+    // it in a worker thread so the GUI (including accessibility support) stays
+    // responsive while a voice file is being created.
+    workerThread = new QThread(this);
+    voicecreator = new VoiceFileCreator(nullptr);
+
     //configure voicecreator
     QString suffix = RbSettings::value(RbSettings::Suffix).toString();
     QString mountpoint = RbSettings::value(RbSettings::Mountpoint).toString();
@@ -70,8 +79,25 @@ void CreateVoiceWindow::accept()
     connect(voicecreator, &VoiceFileCreator::done, logger, &ProgressLoggerGui::setFinished);
     connect(voicecreator, &VoiceFileCreator::logItem, logger, &ProgressLoggerGui::addItem);
     connect(voicecreator, &VoiceFileCreator::logProgress, logger, &ProgressLoggerGui::setProgress);
-    connect(logger,&ProgressLoggerGui::aborted,voicecreator,&VoiceFileCreator::abort);
-    voicecreator->createVoiceFile();
+    // abort() only emits a signal connected to the generator's atomic abort
+    // flag, so it is safe to call while the worker is inside a TTS request.
+    connect(logger, &ProgressLoggerGui::aborted,
+            voicecreator, &VoiceFileCreator::abort, Qt::DirectConnection);
+
+    voicecreator->moveToThread(workerThread);
+    connect(workerThread, &QThread::started,
+            voicecreator, &VoiceFileCreator::createVoiceFile);
+    connect(voicecreator, &VoiceFileCreator::done,
+            workerThread, &QThread::quit);
+    connect(voicecreator, &VoiceFileCreator::done,
+            voicecreator, &QObject::deleteLater);
+    connect(workerThread, &QThread::finished,
+            workerThread, &QObject::deleteLater);
+    connect(workerThread, &QThread::finished, this, [this]() {
+        voicecreator = nullptr;
+        workerThread = nullptr;
+    });
+    workerThread->start(QThread::LowPriority);
 }
 
 
diff --git a/utils/rbutilqt/createvoicewindow.h b/utils/rbutilqt/createvoicewindow.h
index 87127267c5..5e5c07caf6 100644
--- a/utils/rbutilqt/createvoicewindow.h
+++ b/utils/rbutilqt/createvoicewindow.h
@@ -22,6 +22,8 @@
 #define CREATEVOICEWINDOW_H
 
 #include <QDialog>
+#include <QPointer>
+#include <QThread>
 #include <QUrl>
 
 #include "ui_createvoicefrm.h"
@@ -47,7 +49,8 @@ class CreateVoiceWindow : public QDialog
 
     private:
         void changeEvent(QEvent *event);
-        VoiceFileCreator* voicecreator;
+        QPointer<VoiceFileCreator> voicecreator;
+        QPointer<QThread> workerThread;
         Ui::CreateVoiceFrm ui;
         ProgressLoggerGui* logger;
         QUrl m_proxy;
diff --git a/utils/rbutilqt/installtalkwindow.cpp b/utils/rbutilqt/installtalkwindow.cpp
index c8bcdcec2e..d5cd8c76f3 100644
--- a/utils/rbutilqt/installtalkwindow.cpp
+++ b/utils/rbutilqt/installtalkwindow.cpp
@@ -27,7 +27,6 @@
 InstallTalkWindow::InstallTalkWindow(QWidget *parent) : QDialog(parent)
 {
     ui.setupUi(this);
-    talkcreator = new TalkFileCreator(this);
 
     connect(ui.change,&QAbstractButton::clicked,this,&InstallTalkWindow::change);
 
@@ -88,6 +87,9 @@ void InstallTalkWindow::change()
 
 void InstallTalkWindow::accept()
 {
+    if(workerThread)
+        return;
+
     saveSettings();
     QStringList foldersToTalk
         = RbSettings::value(RbSettings::TalkFolders).toStringList();
@@ -103,7 +105,11 @@ void InstallTalkWindow::accept()
     connect(logger,&ProgressLoggerGui::closed,this,&QWidget::close);
     logger->show();
 
+    workerThread = new QThread(this);
+    talkcreator = new TalkFileCreator(nullptr);
+
     talkcreator->setMountPoint(RbSettings::value(RbSettings::Mountpoint).toString());
+    talkcreator->setDirs(foldersToTalk);
 
     talkcreator->setGenerateOnlyNew(ui.GenerateOnlyNew->isChecked());
     talkcreator->setRecursive(ui.recursive->isChecked());
@@ -115,14 +121,23 @@ void InstallTalkWindow::accept()
     connect(talkcreator, &TalkFileCreator::done, logger, &ProgressLoggerGui::setFinished);
     connect(talkcreator, &TalkFileCreator::logItem, logger, &ProgressLoggerGui::addItem);
     connect(talkcreator, &TalkFileCreator::logProgress, logger, &ProgressLoggerGui::setProgress);
-    connect(logger,&ProgressLoggerGui::aborted,talkcreator,&TalkFileCreator::abort);
-
-    for(int i = 0; i < foldersToTalk.size(); i++) {
-        LOG_INFO() << "creating talk files for folder"
-                   << foldersToTalk.at(i);
-        talkcreator->setDir(foldersToTalk.at(i));
-        talkcreator->createTalkFiles();
-    }
+    connect(logger, &ProgressLoggerGui::aborted,
+            talkcreator, &TalkFileCreator::abort, Qt::DirectConnection);
+
+    talkcreator->moveToThread(workerThread);
+    connect(workerThread, &QThread::started,
+            talkcreator, &TalkFileCreator::createTalkFiles);
+    connect(talkcreator, &TalkFileCreator::done,
+            workerThread, &QThread::quit);
+    connect(talkcreator, &TalkFileCreator::done,
+            talkcreator, &QObject::deleteLater);
+    connect(workerThread, &QThread::finished,
+            workerThread, &QObject::deleteLater);
+    connect(workerThread, &QThread::finished, this, [this]() {
+        talkcreator = nullptr;
+        workerThread = nullptr;
+    });
+    workerThread->start(QThread::LowPriority);
 }
 
 
diff --git a/utils/rbutilqt/installtalkwindow.h b/utils/rbutilqt/installtalkwindow.h
index f965e4cb09..3b2c34f04d 100644
--- a/utils/rbutilqt/installtalkwindow.h
+++ b/utils/rbutilqt/installtalkwindow.h
@@ -23,6 +23,8 @@
 
 #include <QDialog>
 #include <QFileSystemModel>
+#include <QPointer>
+#include <QThread>
 
 #include "ui_installtalkfrm.h"
 #include "progressloggergui.h"
@@ -47,7 +49,8 @@ class InstallTalkWindow : public QDialog
 
     private:
         void changeEvent(QEvent *event);
-        TalkFileCreator* talkcreator;
+        QPointer<TalkFileCreator> talkcreator;
+        QPointer<QThread> workerThread;
         Ui::InstallTalkFrm ui;
         ProgressLoggerGui* logger;
         QFileSystemModel *fsm;
-- 
rockbox-cvs mailing list
[email protected]
https://lists.haxx.se/mailman/listinfo/rockbox-cvs
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.