From 3b29b8dc48e1f3251dcdd206c1d3e2127cfa3644 Mon Sep 17 00:00:00 2001 From: fpfcmsr Date: Fri, 21 Nov 2025 20:19:50 +0000 Subject: [PATCH 1/3] update flatpak filesystem permissions tested on a clean install - Pied looks in .config/speech-dispatcher, creates a backup if it exists, and overwrites it. This makes it clear what permissions pied needs as opposed to the broad filesystem=home. --- flatpak/com.mikeasoft.pied.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flatpak/com.mikeasoft.pied.yaml b/flatpak/com.mikeasoft.pied.yaml index 7ac91c6..e2444e6 100644 --- a/flatpak/com.mikeasoft.pied.yaml +++ b/flatpak/com.mikeasoft.pied.yaml @@ -11,7 +11,7 @@ finish-args: - --device=dri - --socket=pulseaudio - --share=network - - --filesystem=home + - --filesystem=xdg-config - --talk-name=org.freedesktop.Flatpak modules: - name: Pied From ed23bf153ff8c3e0ab73dd159362785da07b21c5 Mon Sep 17 00:00:00 2001 From: fpfcmsr Date: Tue, 23 Dec 2025 09:28:49 +0000 Subject: [PATCH 2/3] update filesystem permission to .config/speech-dispatcher Allows pied to create speech-dispatcher directory if not present, and gives write options to said directory. --- flatpak/com.mikeasoft.pied.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flatpak/com.mikeasoft.pied.yaml b/flatpak/com.mikeasoft.pied.yaml index e2444e6..b2395a6 100644 --- a/flatpak/com.mikeasoft.pied.yaml +++ b/flatpak/com.mikeasoft.pied.yaml @@ -11,7 +11,7 @@ finish-args: - --device=dri - --socket=pulseaudio - --share=network - - --filesystem=xdg-config + - --filesystem=xdg-config/speech-dispathcer:create - --talk-name=org.freedesktop.Flatpak modules: - name: Pied From 0ac308392aa7fe6014e180f71e017be0dbbfb07a Mon Sep 17 00:00:00 2001 From: fpfcmsr Date: Tue, 23 Dec 2025 09:30:07 +0000 Subject: [PATCH 3/3] file level-backup of speech dispatcher files Creates backup of speechd.conf and piper.conf individually within speech-dispatcher directory. If either is changed / removed, then recreates them / backs them up as needed. --- lib/voice_buttons.dart | 137 +++++++++++++++++++++++++++-------------- 1 file changed, 91 insertions(+), 46 deletions(-) diff --git a/lib/voice_buttons.dart b/lib/voice_buttons.dart index 0862a20..a116514 100644 --- a/lib/voice_buttons.dart +++ b/lib/voice_buttons.dart @@ -48,60 +48,95 @@ class _VoiceButtonsState extends State { } } - void setSDConfig() async { + Future setSDConfig() async { Directory confDir = Directory(path.join(getHome()!, ".config/speech-dispatcher")); - Directory? originalConfigDir; - if (confDir.existsSync()) { - File sdConfig = File(path.join(confDir.path, "speechd.conf")); - if (sdConfig.existsSync()) { - if (sdConfig - .readAsStringSync() - .contains("THIS CONFIG WAS GENERATED BY PIED")) { - return; - } - } - DateTime now = DateTime.now(); - Directory newDir; - if (isSnap()) { - Directory origDir = - Directory(path.join(getHome()!, ".config/speech-dispatcher.orig")); - if (!origDir.existsSync()) { - origDir.createSync(); - } - newDir = Directory( - "${confDir.path}.orig/${now.year}-${now.month}-${now.day}"); - } else { - newDir = Directory( - "${confDir.path}.orig.${now.year}-${now.month}-${now.day}"); + + // Ensure the main config directory and subdirectories exist + if (!confDir.existsSync()) { + confDir.createSync(recursive: true); + } + Directory modulesDir = Directory(path.join(confDir.path, "modules")); + if (!modulesDir.existsSync()) { + modulesDir.createSync(recursive: true); + } + Directory clientsDir = Directory(path.join(confDir.path, "clients")); + if (!clientsDir.existsSync()) { + clientsDir.createSync(recursive: true); + } + + File sdConfig = File(path.join(confDir.path, "speechd.conf")); + File piperConfig = File(path.join(modulesDir.path, "piper.conf")); + + DateTime now = DateTime.now(); + String timestamp = + "${now.year}-${now.month}-${now.day}-${now.hour}-${now.minute}-${now.second}"; + + File? sdBackup; + File? piperBackup; + bool sdChanged = false; + + // Check/Backup speechd.conf + // If it exists and DOES NOT contain our signature, back it up. + if (sdConfig.existsSync()) { + if (!sdConfig.readAsStringSync().contains("THIS CONFIG WAS GENERATED BY PIED")) { + sdBackup = File("${sdConfig.path}.bak-$timestamp"); + sdConfig.renameSync(sdBackup.path); + + // We only create new config if we moved the old one or it didn't exist + sdConfig.createSync(); + sdConfig.writeAsString(sdConfigTemplate); + sdChanged = true; } - if (newDir.existsSync()) { - newDir.deleteSync(recursive: true); + } else { + // Create fresh if didn't exist + sdConfig.createSync(); + sdConfig.writeAsString(sdConfigTemplate); + sdChanged = true; + } + + // if not generated by pied -> back up + if (piperConfig.existsSync()) { + if (!piperConfig.readAsStringSync().contains("THIS CONFIG WAS GENERATED BY PIED")) { + piperBackup = File("${piperConfig.path}.bak-$timestamp"); + piperConfig.renameSync(piperBackup.path); } - confDir.renameSync(newDir.path); - originalConfigDir = newDir; } - confDir.createSync(); - Directory(path.join(confDir.path, "clients")).create(); - Directory(path.join(confDir.path, "modules")).create(); - File sdConfig = File(path.join(confDir.path, "speechd.conf")); - sdConfig.createSync(); - sdConfig.writeAsString(sdConfigTemplate); - restartSD(); - Timer(const Duration(seconds: 2), () { - confirmConfigChange(originalConfigDir); - }); + + // if changed config -> ask for confirmation + if (sdChanged) { + restartSD(); + Timer(const Duration(seconds: 2), () { + confirmConfigChange(sdBackup, piperBackup); + }); + } } - Future revertConfig(Directory? originalConfigDir) async { + Future revertConfig(File? sdBackup, File? piperBackup) async { Directory confDir = Directory(path.join(getHome()!, ".config/speech-dispatcher")); - confDir.deleteSync(recursive: true); - if (originalConfigDir != null) { - originalConfigDir.rename(confDir.path); + File sdConfig = File(path.join(confDir.path, "speechd.conf")); + File piperConfig = File(path.join(confDir.path, "modules/piper.conf")); + + // undo the Pied configuration + if (sdConfig.existsSync()) { + sdConfig.deleteSync(); + } + if (piperConfig.existsSync()) { + piperConfig.deleteSync(); + } + + // restore backups + if (sdBackup != null && sdBackup.existsSync()) { + sdBackup.renameSync(sdConfig.path); + } + if (piperBackup != null && piperBackup.existsSync()) { + piperBackup.renameSync(piperConfig.path); } + widget.onCurrentVoiceChanged(""); restartSD(); + Timer(const Duration(seconds: 2), () { showDialog( context: context, @@ -136,13 +171,13 @@ class _VoiceButtonsState extends State { }); } - Future confirmConfigChange(Directory? originalConfigDir) async { + Future confirmConfigChange(File? sdBackup, File? piperBackup) async { return showDialog( context: context, barrierDismissible: false, builder: (BuildContext context) { return ConfirmationDialog(onRevert: () { - revertConfig(originalConfigDir); + revertConfig(sdBackup, piperBackup); }); }); } @@ -161,12 +196,14 @@ class _VoiceButtonsState extends State { enabled: true, child: ElevatedButton( onPressed: () async { - setSDConfig(); + await setSDConfig(); + final Directory appDir = await getDataDir(); Directory modelDir = Directory(path.join(appDir.path, "models")); File modelConf = File(path.join(getHome()!, ".config/speech-dispatcher/modules/piper.conf")); + String configString = modelTemplate; configString = configString.replaceAll("PIPER_PATH", path.join(appDir.path, "piper", "piper")); @@ -178,7 +215,15 @@ class _VoiceButtonsState extends State { "LANGUAGE", widget.voiceData[1]); configString = configString.replaceAll( "SUBVOICE", widget.selectedSubVoice.toString()); - modelConf.writeAsString(configString); + + String header = "# THIS CONFIG WAS GENERATED BY PIED\n"; + + if (!modelConf.parent.existsSync()) { + modelConf.parent.createSync(recursive: true); + } + + // add pied signature to piper.conf + modelConf.writeAsString(header + configString); restartSD(); widget.onCurrentVoiceChanged(widget.voiceData[4]); widget.onCurrentSubVoiceChanged(