Requested feature added. It is now possible to customize speech used for echo settings including speech module, voice, rate, volume, pitch.
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
# Maintainer: Storm Dragon <storm_dragon@stormux.org>
|
# Maintainer: Storm Dragon <storm_dragon@stormux.org>
|
||||||
|
|
||||||
pkgname=cthulhu
|
pkgname=cthulhu
|
||||||
pkgver=2026.02.17
|
pkgver=2026.02.22
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="Desktop-agnostic screen reader with plugin system, forked from Orca"
|
pkgdesc="Desktop-agnostic screen reader with plugin system, forked from Orca"
|
||||||
url="https://git.stormux.org/storm/cthulhu"
|
url="https://git.stormux.org/storm/cthulhu"
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
project('cthulhu',
|
project('cthulhu',
|
||||||
version: '2026.02.17-master',
|
version: '2026.02.22-testing',
|
||||||
meson_version: '>= 1.0.0',
|
meson_version: '>= 1.0.0',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -222,6 +222,24 @@ class Backend:
|
|||||||
|
|
||||||
return settingsDict
|
return settingsDict
|
||||||
|
|
||||||
|
def _hasLikelyCustomEchoVoice(self, echoVoice, defaultVoice):
|
||||||
|
"""Return True when echoVoice appears intentionally customized."""
|
||||||
|
|
||||||
|
if not echoVoice:
|
||||||
|
return False
|
||||||
|
|
||||||
|
echoEstablished = bool(echoVoice.get('established', False))
|
||||||
|
if not defaultVoice:
|
||||||
|
return echoEstablished or bool(echoVoice.get(acss.ACSS.FAMILY))
|
||||||
|
|
||||||
|
for key in [acss.ACSS.RATE, acss.ACSS.AVERAGE_PITCH, acss.ACSS.GAIN, acss.ACSS.FAMILY]:
|
||||||
|
echoValue = echoVoice.get(key)
|
||||||
|
defaultValue = defaultVoice.get(key)
|
||||||
|
if echoValue is not None and echoValue != defaultValue:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return echoEstablished and bool(echoVoice.get(acss.ACSS.FAMILY))
|
||||||
|
|
||||||
def getGeneral(self, profile=None):
|
def getGeneral(self, profile=None):
|
||||||
""" Get general settings from default settings and
|
""" Get general settings from default settings and
|
||||||
override with profile values. """
|
override with profile values. """
|
||||||
@@ -241,8 +259,24 @@ class Backend:
|
|||||||
if key == 'voices':
|
if key == 'voices':
|
||||||
for voiceType, voiceDef in value.items():
|
for voiceType, voiceDef in value.items():
|
||||||
value[voiceType] = acss.ACSS(voiceDef)
|
value[voiceType] = acss.ACSS(voiceDef)
|
||||||
|
if key == 'echoVoice' and isinstance(value, dict):
|
||||||
|
value = acss.ACSS(value)
|
||||||
if key not in ['startingProfile', 'activeProfile']:
|
if key not in ['startingProfile', 'activeProfile']:
|
||||||
generalSettings[key] = value
|
generalSettings[key] = value
|
||||||
|
|
||||||
|
# Backward compatibility: recover custom echo behavior when legacy
|
||||||
|
# profiles have echoVoice but are missing custom-echo toggle keys.
|
||||||
|
if 'useCustomEchoVoice' not in profileSettings:
|
||||||
|
voices = generalSettings.get('voices') or {}
|
||||||
|
defaultVoice = voices.get(settings.DEFAULT_VOICE)
|
||||||
|
echoVoice = generalSettings.get('echoVoice')
|
||||||
|
if self._hasLikelyCustomEchoVoice(echoVoice, defaultVoice):
|
||||||
|
generalSettings['useCustomEchoVoice'] = True
|
||||||
|
if 'useCustomEchoForKey' not in profileSettings:
|
||||||
|
generalSettings['useCustomEchoForKey'] = True
|
||||||
|
if 'useCustomEchoForCharacter' not in profileSettings:
|
||||||
|
generalSettings['useCustomEchoForCharacter'] = True
|
||||||
|
|
||||||
try:
|
try:
|
||||||
generalSettings['activeProfile'] = profileSettings['profile']
|
generalSettings['activeProfile'] = profileSettings['profile']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
|||||||
@@ -136,6 +136,24 @@
|
|||||||
<property name="step_increment">0.10000000149</property>
|
<property name="step_increment">0.10000000149</property>
|
||||||
<property name="page_increment">1</property>
|
<property name="page_increment">1</property>
|
||||||
</object>
|
</object>
|
||||||
|
<object class="GtkAdjustment" id="echoPitchAdjustment">
|
||||||
|
<property name="upper">10</property>
|
||||||
|
<property name="value">5</property>
|
||||||
|
<property name="step_increment">0.10000000149</property>
|
||||||
|
<property name="page_increment">1</property>
|
||||||
|
</object>
|
||||||
|
<object class="GtkAdjustment" id="echoRateAdjustment">
|
||||||
|
<property name="upper">100</property>
|
||||||
|
<property name="value">50</property>
|
||||||
|
<property name="step_increment">1</property>
|
||||||
|
<property name="page_increment">10</property>
|
||||||
|
</object>
|
||||||
|
<object class="GtkAdjustment" id="echoVolumeAdjustment">
|
||||||
|
<property name="upper">10</property>
|
||||||
|
<property name="value">10</property>
|
||||||
|
<property name="step_increment">0.10000000149</property>
|
||||||
|
<property name="page_increment">1</property>
|
||||||
|
</object>
|
||||||
<object class="GtkDialog" id="cthulhuSetupWindow">
|
<object class="GtkDialog" id="cthulhuSetupWindow">
|
||||||
<property name="can_focus">False</property>
|
<property name="can_focus">False</property>
|
||||||
<property name="title" translatable="yes">Cthulhu Preferences</property>
|
<property name="title" translatable="yes">Cthulhu Preferences</property>
|
||||||
@@ -3037,6 +3055,295 @@
|
|||||||
<property name="top_attach">1</property>
|
<property name="top_attach">1</property>
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkFrame" id="echoVoiceFrame">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="label_xalign">0</property>
|
||||||
|
<property name="shadow_type">none</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkAlignment" id="echoVoiceAlignment">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="left_padding">12</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkGrid" id="echoVoiceGrid">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="row_spacing">10</property>
|
||||||
|
<property name="column_spacing">8</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkCheckButton" id="useCustomEchoVoiceCheckButton">
|
||||||
|
<property name="label" translatable="yes">Use c_ustom echo voice settings</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">False</property>
|
||||||
|
<property name="use_underline">True</property>
|
||||||
|
<property name="draw_indicator">True</property>
|
||||||
|
<signal name="toggled" handler="useCustomEchoVoiceToggled" swapped="no"/>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">0</property>
|
||||||
|
<property name="top_attach">0</property>
|
||||||
|
<property name="width">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkCheckButton" id="useCustomEchoSpeechServerCheckButton">
|
||||||
|
<property name="label" translatable="yes">Use custom _speech-dispatcher module for echo</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">False</property>
|
||||||
|
<property name="use_underline">True</property>
|
||||||
|
<property name="draw_indicator">True</property>
|
||||||
|
<signal name="toggled" handler="useCustomEchoSpeechServerToggled" swapped="no"/>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">0</property>
|
||||||
|
<property name="top_attach">1</property>
|
||||||
|
<property name="width">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="echoSpeechServersLabel">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="xalign">1</property>
|
||||||
|
<property name="label" translatable="yes">Echo _module:</property>
|
||||||
|
<property name="use_underline">True</property>
|
||||||
|
<property name="mnemonic_widget">echoSpeechServers</property>
|
||||||
|
<accessibility>
|
||||||
|
<relation type="label-for" target="echoSpeechServers"/>
|
||||||
|
</accessibility>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">0</property>
|
||||||
|
<property name="top_attach">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkComboBox" id="echoSpeechServers">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<signal name="changed" handler="echoSpeechServersChanged" swapped="no"/>
|
||||||
|
<accessibility>
|
||||||
|
<relation type="labelled-by" target="echoSpeechServersLabel"/>
|
||||||
|
</accessibility>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">1</property>
|
||||||
|
<property name="top_attach">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="echoSpeechFamiliesLabel">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="xalign">1</property>
|
||||||
|
<property name="label" translatable="yes">Echo _voice:</property>
|
||||||
|
<property name="use_underline">True</property>
|
||||||
|
<property name="mnemonic_widget">echoSpeechFamilies</property>
|
||||||
|
<accessibility>
|
||||||
|
<relation type="label-for" target="echoSpeechFamilies"/>
|
||||||
|
</accessibility>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">0</property>
|
||||||
|
<property name="top_attach">3</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkComboBox" id="echoSpeechFamilies">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<signal name="changed" handler="echoSpeechFamiliesChanged" swapped="no"/>
|
||||||
|
<accessibility>
|
||||||
|
<relation type="labelled-by" target="echoSpeechFamiliesLabel"/>
|
||||||
|
</accessibility>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">1</property>
|
||||||
|
<property name="top_attach">3</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="echoRateLabel">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="xalign">1</property>
|
||||||
|
<property name="label" translatable="yes">Echo ra_te:</property>
|
||||||
|
<property name="use_underline">True</property>
|
||||||
|
<property name="mnemonic_widget">echoRateScale</property>
|
||||||
|
<accessibility>
|
||||||
|
<relation type="label-for" target="echoRateScale"/>
|
||||||
|
</accessibility>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">0</property>
|
||||||
|
<property name="top_attach">4</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkScale" id="echoRateScale">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="adjustment">echoRateAdjustment</property>
|
||||||
|
<property name="round_digits">0</property>
|
||||||
|
<property name="digits">0</property>
|
||||||
|
<property name="value_pos">right</property>
|
||||||
|
<signal name="value-changed" handler="echoRateValueChanged" swapped="no"/>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">1</property>
|
||||||
|
<property name="top_attach">4</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="echoPitchLabel">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="xalign">1</property>
|
||||||
|
<property name="label" translatable="yes">Echo pi_tch:</property>
|
||||||
|
<property name="use_underline">True</property>
|
||||||
|
<property name="mnemonic_widget">echoPitchScale</property>
|
||||||
|
<accessibility>
|
||||||
|
<relation type="label-for" target="echoPitchScale"/>
|
||||||
|
</accessibility>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">0</property>
|
||||||
|
<property name="top_attach">5</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkScale" id="echoPitchScale">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="adjustment">echoPitchAdjustment</property>
|
||||||
|
<property name="round_digits">1</property>
|
||||||
|
<property name="value_pos">right</property>
|
||||||
|
<signal name="value-changed" handler="echoPitchValueChanged" swapped="no"/>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">1</property>
|
||||||
|
<property name="top_attach">5</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="echoVolumeLabel">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="xalign">1</property>
|
||||||
|
<property name="label" translatable="yes">Echo vo_lume:</property>
|
||||||
|
<property name="use_underline">True</property>
|
||||||
|
<property name="mnemonic_widget">echoVolumeScale</property>
|
||||||
|
<accessibility>
|
||||||
|
<relation type="label-for" target="echoVolumeScale"/>
|
||||||
|
</accessibility>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">0</property>
|
||||||
|
<property name="top_attach">6</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkScale" id="echoVolumeScale">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="adjustment">echoVolumeAdjustment</property>
|
||||||
|
<property name="round_digits">1</property>
|
||||||
|
<property name="value_pos">right</property>
|
||||||
|
<signal name="value-changed" handler="echoVolumeValueChanged" swapped="no"/>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">1</property>
|
||||||
|
<property name="top_attach">6</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkCheckButton" id="useCustomEchoForKeyCheckButton">
|
||||||
|
<property name="label" translatable="yes">Use custom voice for _key echo</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">False</property>
|
||||||
|
<property name="use_underline">True</property>
|
||||||
|
<property name="draw_indicator">True</property>
|
||||||
|
<signal name="toggled" handler="checkButtonToggled" swapped="no"/>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">0</property>
|
||||||
|
<property name="top_attach">7</property>
|
||||||
|
<property name="width">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkCheckButton" id="useCustomEchoForCharacterCheckButton">
|
||||||
|
<property name="label" translatable="yes">Use custom voice for _character echo</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">False</property>
|
||||||
|
<property name="use_underline">True</property>
|
||||||
|
<property name="draw_indicator">True</property>
|
||||||
|
<signal name="toggled" handler="checkButtonToggled" swapped="no"/>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">0</property>
|
||||||
|
<property name="top_attach">8</property>
|
||||||
|
<property name="width">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkCheckButton" id="useCustomEchoForWordCheckButton">
|
||||||
|
<property name="label" translatable="yes">Use custom voice for _word echo</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">False</property>
|
||||||
|
<property name="use_underline">True</property>
|
||||||
|
<property name="draw_indicator">True</property>
|
||||||
|
<signal name="toggled" handler="checkButtonToggled" swapped="no"/>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">0</property>
|
||||||
|
<property name="top_attach">9</property>
|
||||||
|
<property name="width">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkCheckButton" id="useCustomEchoForSentenceCheckButton">
|
||||||
|
<property name="label" translatable="yes">Use custom voice for _sentence echo</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">False</property>
|
||||||
|
<property name="use_underline">True</property>
|
||||||
|
<property name="draw_indicator">True</property>
|
||||||
|
<signal name="toggled" handler="checkButtonToggled" swapped="no"/>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">0</property>
|
||||||
|
<property name="top_attach">10</property>
|
||||||
|
<property name="width">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child type="label">
|
||||||
|
<object class="GtkLabel" id="echoVoiceLabel">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="label" translatable="yes">Echo Voice</property>
|
||||||
|
<attributes>
|
||||||
|
<attribute name="weight" value="bold"/>
|
||||||
|
</attributes>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left_attach">0</property>
|
||||||
|
<property name="top_attach">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkCheckButton" id="enableEchoByCharacterCheckButton">
|
<object class="GtkCheckButton" id="enableEchoByCharacterCheckButton">
|
||||||
<property name="label" translatable="yes" comments="Translators: When this option is enabled, inserted text of length 1 is spoken.">Enable echo by cha_racter</property>
|
<property name="label" translatable="yes" comments="Translators: When this option is enabled, inserted text of length 1 is spoken.">Enable echo by cha_racter</property>
|
||||||
@@ -3049,7 +3356,7 @@
|
|||||||
</object>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
<property name="left_attach">0</property>
|
<property name="left_attach">0</property>
|
||||||
<property name="top_attach">2</property>
|
<property name="top_attach">3</property>
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
@@ -3064,7 +3371,7 @@
|
|||||||
</object>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
<property name="left_attach">0</property>
|
<property name="left_attach">0</property>
|
||||||
<property name="top_attach">3</property>
|
<property name="top_attach">4</property>
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
@@ -3079,7 +3386,7 @@
|
|||||||
</object>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
<property name="left_attach">0</property>
|
<property name="left_attach">0</property>
|
||||||
<property name="top_attach">4</property>
|
<property name="top_attach">5</property>
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
|
|||||||
@@ -23,5 +23,5 @@
|
|||||||
# Forked from Orca screen reader.
|
# Forked from Orca screen reader.
|
||||||
# Cthulhu project: https://git.stormux.org/storm/cthulhu
|
# Cthulhu project: https://git.stormux.org/storm/cthulhu
|
||||||
|
|
||||||
version = "2026.02.17"
|
version = "2026.02.22"
|
||||||
codeName = "master"
|
codeName = "testing"
|
||||||
|
|||||||
@@ -38,6 +38,9 @@ gi.require_version("Gtk", "3.0")
|
|||||||
from gi.repository import Atspi
|
from gi.repository import Atspi
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import json
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
import threading
|
import threading
|
||||||
from gi.repository import Gdk
|
from gi.repository import Gdk
|
||||||
from gi.repository import GLib
|
from gi.repository import GLib
|
||||||
@@ -173,6 +176,16 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
|
|||||||
self.speechSystemsChoice = None
|
self.speechSystemsChoice = None
|
||||||
self.speechSystemsChoices = None
|
self.speechSystemsChoices = None
|
||||||
self.speechSystemsModel = None
|
self.speechSystemsModel = None
|
||||||
|
self.echoVoice = None
|
||||||
|
self.echoSpeechFamiliesChoice = None
|
||||||
|
self.echoSpeechFamiliesChoices = None
|
||||||
|
self.echoSpeechFamiliesModel = None
|
||||||
|
self.echoSpeechServersChoice = None
|
||||||
|
self.echoSpeechServersChoices = None
|
||||||
|
self.echoSpeechServersModel = None
|
||||||
|
self.initializingEchoSpeech = False
|
||||||
|
self._echoVoiceFetchToken = 0
|
||||||
|
self._updatingEchoSpeechFamilies = False
|
||||||
self.systemVoice = None
|
self.systemVoice = None
|
||||||
self.uppercaseVoice = None
|
self.uppercaseVoice = None
|
||||||
self.window = None
|
self.window = None
|
||||||
@@ -386,7 +399,12 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
|
|||||||
self._initComboBox(self.get_widget("speechLanguages"))
|
self._initComboBox(self.get_widget("speechLanguages"))
|
||||||
self.speechFamiliesModel = \
|
self.speechFamiliesModel = \
|
||||||
self._initComboBox(self.get_widget("speechFamilies"))
|
self._initComboBox(self.get_widget("speechFamilies"))
|
||||||
|
self.echoSpeechServersModel = \
|
||||||
|
self._initComboBox(self.get_widget("echoSpeechServers"))
|
||||||
|
self.echoSpeechFamiliesModel = \
|
||||||
|
self._initComboBox(self.get_widget("echoSpeechFamilies"))
|
||||||
self._initSpeechState()
|
self._initSpeechState()
|
||||||
|
self._initEchoSpeechState()
|
||||||
|
|
||||||
# TODO - JD: Will this ever be the case??
|
# TODO - JD: Will this ever be the case??
|
||||||
self._isInitialSetup = \
|
self._isInitialSetup = \
|
||||||
@@ -1671,6 +1689,261 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
|
|||||||
self._setupSpeechSystems(factories)
|
self._setupSpeechSystems(factories)
|
||||||
self.initializingSpeech = False
|
self.initializingSpeech = False
|
||||||
|
|
||||||
|
def _getSpeechDispatcherFactory(self):
|
||||||
|
"""Returns the Speech Dispatcher factory if available."""
|
||||||
|
|
||||||
|
factories = cthulhu.cthulhuApp.settingsManager.getSpeechServerFactories()
|
||||||
|
for factory in factories:
|
||||||
|
try:
|
||||||
|
if factory.SpeechServer.getFactoryName() == guilabels.SPEECH_DISPATCHER:
|
||||||
|
return factory
|
||||||
|
except Exception:
|
||||||
|
debug.printException(debug.LEVEL_FINEST)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _setEchoVoiceFamily(self, family):
|
||||||
|
"""Sets echo voice family from the selected server family object."""
|
||||||
|
|
||||||
|
if not family:
|
||||||
|
return
|
||||||
|
|
||||||
|
name = family.get(speechserver.VoiceFamily.NAME)
|
||||||
|
language = family.get(speechserver.VoiceFamily.LANG)
|
||||||
|
dialect = family.get(speechserver.VoiceFamily.DIALECT)
|
||||||
|
variant = family.get(speechserver.VoiceFamily.VARIANT)
|
||||||
|
self.echoVoice[acss.ACSS.FAMILY] = {
|
||||||
|
speechserver.VoiceFamily.NAME: name,
|
||||||
|
speechserver.VoiceFamily.LANG: language,
|
||||||
|
speechserver.VoiceFamily.DIALECT: dialect,
|
||||||
|
speechserver.VoiceFamily.VARIANT: variant,
|
||||||
|
}
|
||||||
|
self.echoVoice['established'] = True
|
||||||
|
|
||||||
|
def _populateEchoSpeechFamilies(self, families):
|
||||||
|
"""Populate the echo family combobox from the provided families list."""
|
||||||
|
|
||||||
|
combobox = self.get_widget("echoSpeechFamilies")
|
||||||
|
combobox.set_model(None)
|
||||||
|
self.echoSpeechFamiliesModel.clear()
|
||||||
|
self.echoSpeechFamiliesChoices = list(families or [])
|
||||||
|
self.echoSpeechFamiliesChoice = None
|
||||||
|
|
||||||
|
selectedIndex = 0
|
||||||
|
selectedMatchFound = False
|
||||||
|
selectedFamily = self.echoVoice.get(acss.ACSS.FAMILY)
|
||||||
|
selectedName = selectedLanguage = selectedDialect = selectedVariant = None
|
||||||
|
if selectedFamily:
|
||||||
|
selectedName = selectedFamily.get(speechserver.VoiceFamily.NAME)
|
||||||
|
selectedLanguage = selectedFamily.get(speechserver.VoiceFamily.LANG)
|
||||||
|
selectedDialect = selectedFamily.get(speechserver.VoiceFamily.DIALECT)
|
||||||
|
selectedVariant = selectedFamily.get(speechserver.VoiceFamily.VARIANT)
|
||||||
|
|
||||||
|
for i, family in enumerate(self.echoSpeechFamiliesChoices):
|
||||||
|
name = family.get(speechserver.VoiceFamily.NAME) or ""
|
||||||
|
language = family.get(speechserver.VoiceFamily.LANG) or ""
|
||||||
|
dialect = family.get(speechserver.VoiceFamily.DIALECT) or ""
|
||||||
|
variant = family.get(speechserver.VoiceFamily.VARIANT)
|
||||||
|
display = name
|
||||||
|
locale = language
|
||||||
|
if dialect:
|
||||||
|
locale = f"{language}-{dialect}" if language else dialect
|
||||||
|
if locale:
|
||||||
|
display = f"{name} ({locale})"
|
||||||
|
self.echoSpeechFamiliesModel.append((i, display))
|
||||||
|
if selectedName == name and selectedLanguage == language \
|
||||||
|
and selectedDialect == dialect and selectedVariant == variant:
|
||||||
|
selectedIndex = i
|
||||||
|
selectedMatchFound = True
|
||||||
|
|
||||||
|
combobox.set_model(self.echoSpeechFamiliesModel)
|
||||||
|
if self.echoSpeechFamiliesChoices:
|
||||||
|
self._updatingEchoSpeechFamilies = True
|
||||||
|
try:
|
||||||
|
combobox.set_active(selectedIndex)
|
||||||
|
finally:
|
||||||
|
self._updatingEchoSpeechFamilies = False
|
||||||
|
self.echoSpeechFamiliesChoice = self.echoSpeechFamiliesChoices[selectedIndex]
|
||||||
|
|
||||||
|
def _fetchEchoSpeechFamiliesWorker(self, serverInfo, requestToken):
|
||||||
|
"""Fetch module-specific voices in a subprocess and apply asynchronously."""
|
||||||
|
|
||||||
|
queryScript = """
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
from cthulhu import speechdispatcherfactory
|
||||||
|
from cthulhu import speechserver
|
||||||
|
|
||||||
|
serverInfo = json.loads(sys.argv[1])
|
||||||
|
if isinstance(serverInfo, list):
|
||||||
|
serverInfo = tuple(serverInfo)
|
||||||
|
|
||||||
|
server = speechdispatcherfactory.SpeechServer.getSpeechServer(serverInfo)
|
||||||
|
families = server.getVoiceFamilies() if server else []
|
||||||
|
|
||||||
|
result = []
|
||||||
|
for family in families:
|
||||||
|
result.append({
|
||||||
|
"name": family.get(speechserver.VoiceFamily.NAME),
|
||||||
|
"lang": family.get(speechserver.VoiceFamily.LANG),
|
||||||
|
"dialect": family.get(speechserver.VoiceFamily.DIALECT),
|
||||||
|
"variant": family.get(speechserver.VoiceFamily.VARIANT),
|
||||||
|
})
|
||||||
|
|
||||||
|
print(json.dumps(result))
|
||||||
|
"""
|
||||||
|
|
||||||
|
familyDefs = None
|
||||||
|
try:
|
||||||
|
completed = subprocess.run(
|
||||||
|
[sys.executable, "-c", queryScript, json.dumps(serverInfo)],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=4.0,
|
||||||
|
check=False,
|
||||||
|
)
|
||||||
|
if completed.returncode == 0 and completed.stdout.strip():
|
||||||
|
familyDefs = json.loads(completed.stdout.strip())
|
||||||
|
else:
|
||||||
|
tokens = [
|
||||||
|
"PREFERENCES DIALOG: Echo voice fetch failed for",
|
||||||
|
serverInfo,
|
||||||
|
"rc=",
|
||||||
|
completed.returncode,
|
||||||
|
]
|
||||||
|
debug.printTokens(debug.LEVEL_WARNING, tokens, True)
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
tokens = ["PREFERENCES DIALOG: Echo voice fetch timed out for", serverInfo]
|
||||||
|
debug.printTokens(debug.LEVEL_WARNING, tokens, True)
|
||||||
|
except Exception:
|
||||||
|
debug.printException(debug.LEVEL_WARNING)
|
||||||
|
|
||||||
|
GLib.idle_add(self._applyFetchedEchoSpeechFamilies, requestToken, familyDefs)
|
||||||
|
|
||||||
|
def _applyFetchedEchoSpeechFamilies(self, requestToken, familyDefs):
|
||||||
|
"""Apply fetched echo families if this is still the active request."""
|
||||||
|
|
||||||
|
if requestToken != self._echoVoiceFetchToken:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not isinstance(familyDefs, list):
|
||||||
|
return False
|
||||||
|
|
||||||
|
families = []
|
||||||
|
for familyDef in familyDefs:
|
||||||
|
if not isinstance(familyDef, dict):
|
||||||
|
continue
|
||||||
|
families.append(
|
||||||
|
speechserver.VoiceFamily({
|
||||||
|
speechserver.VoiceFamily.NAME: familyDef.get("name"),
|
||||||
|
speechserver.VoiceFamily.LANG: familyDef.get("lang"),
|
||||||
|
speechserver.VoiceFamily.DIALECT: familyDef.get("dialect"),
|
||||||
|
speechserver.VoiceFamily.VARIANT: familyDef.get("variant"),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
if not families:
|
||||||
|
return False
|
||||||
|
|
||||||
|
self._populateEchoSpeechFamilies(families)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _setupEchoSpeechFamilies(self):
|
||||||
|
"""Populate echo voice family list for the current echo server."""
|
||||||
|
self._echoVoiceFetchToken += 1
|
||||||
|
requestToken = self._echoVoiceFetchToken
|
||||||
|
useCustomModule = self.get_widget("useCustomEchoSpeechServerCheckButton").get_active()
|
||||||
|
fallbackFamilies = list(self.speechFamiliesChoices or [])
|
||||||
|
self._populateEchoSpeechFamilies(fallbackFamilies)
|
||||||
|
|
||||||
|
if useCustomModule and self.echoSpeechServersChoice:
|
||||||
|
serverInfo = self.echoSpeechServersChoice.getInfo()
|
||||||
|
thread = threading.Thread(
|
||||||
|
target=self._fetchEchoSpeechFamiliesWorker,
|
||||||
|
args=(serverInfo, requestToken),
|
||||||
|
daemon=True,
|
||||||
|
)
|
||||||
|
thread.start()
|
||||||
|
|
||||||
|
def _setEchoSpeechServersChoice(self, serverInfo):
|
||||||
|
"""Set the active echo module based on stored server info."""
|
||||||
|
|
||||||
|
if not self.echoSpeechServersChoices:
|
||||||
|
self.echoSpeechServersChoice = None
|
||||||
|
self._setupEchoSpeechFamilies()
|
||||||
|
return
|
||||||
|
|
||||||
|
valueSet = False
|
||||||
|
for i, server in enumerate(self.echoSpeechServersChoices):
|
||||||
|
info = server.getInfo()
|
||||||
|
if serverInfo and info == serverInfo:
|
||||||
|
self.get_widget("echoSpeechServers").set_active(i)
|
||||||
|
self.echoSpeechServersChoice = server
|
||||||
|
valueSet = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if not valueSet:
|
||||||
|
self.get_widget("echoSpeechServers").set_active(0)
|
||||||
|
self.echoSpeechServersChoice = self.echoSpeechServersChoices[0]
|
||||||
|
|
||||||
|
self._setupEchoSpeechFamilies()
|
||||||
|
|
||||||
|
def _setupEchoSpeechServers(self):
|
||||||
|
"""Populate available speech-dispatcher modules for echo."""
|
||||||
|
|
||||||
|
combobox = self.get_widget("echoSpeechServers")
|
||||||
|
combobox.set_model(None)
|
||||||
|
self.echoSpeechServersModel.clear()
|
||||||
|
self.echoSpeechServersChoices = []
|
||||||
|
self.echoSpeechServersChoice = None
|
||||||
|
|
||||||
|
if not self.prefsDict.get('enableSpeech', True):
|
||||||
|
combobox.set_model(self.echoSpeechServersModel)
|
||||||
|
self._setupEchoSpeechFamilies()
|
||||||
|
return
|
||||||
|
|
||||||
|
factory = self._getSpeechDispatcherFactory()
|
||||||
|
if factory is None:
|
||||||
|
combobox.set_model(self.echoSpeechServersModel)
|
||||||
|
self._setupEchoSpeechFamilies()
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.echoSpeechServersChoices = factory.SpeechServer.getSpeechServers()
|
||||||
|
except Exception:
|
||||||
|
debug.printException(debug.LEVEL_WARNING)
|
||||||
|
self.echoSpeechServersChoices = []
|
||||||
|
|
||||||
|
for i, server in enumerate(self.echoSpeechServersChoices):
|
||||||
|
name = server.getInfo()[0]
|
||||||
|
self.echoSpeechServersModel.append((i, name))
|
||||||
|
|
||||||
|
combobox.set_model(self.echoSpeechServersModel)
|
||||||
|
self._setEchoSpeechServersChoice(self.prefsDict.get("echoSpeechServerInfo"))
|
||||||
|
|
||||||
|
def _initEchoSpeechState(self):
|
||||||
|
"""Initialize echo voice controls and choices."""
|
||||||
|
|
||||||
|
self.echoVoice = acss.ACSS(
|
||||||
|
self.prefsDict.get("echoVoice", settings.echoVoice) or {})
|
||||||
|
|
||||||
|
baseVoice = self.defaultVoice or acss.ACSS(
|
||||||
|
self.prefsDict.get("voices", {}).get(settings.DEFAULT_VOICE, {}))
|
||||||
|
|
||||||
|
rate = self.echoVoice.get(acss.ACSS.RATE, baseVoice.get(acss.ACSS.RATE, 50.0))
|
||||||
|
pitch = self.echoVoice.get(acss.ACSS.AVERAGE_PITCH, baseVoice.get(acss.ACSS.AVERAGE_PITCH, 5.0))
|
||||||
|
volume = self.echoVoice.get(acss.ACSS.GAIN, baseVoice.get(acss.ACSS.GAIN, 10.0))
|
||||||
|
|
||||||
|
self.get_widget("echoRateScale").set_value(rate)
|
||||||
|
self.get_widget("echoPitchScale").set_value(pitch)
|
||||||
|
self.get_widget("echoVolumeScale").set_value(volume)
|
||||||
|
|
||||||
|
self.initializingEchoSpeech = True
|
||||||
|
self._setupEchoSpeechServers()
|
||||||
|
self.initializingEchoSpeech = False
|
||||||
|
|
||||||
|
self._setEchoVoiceItems()
|
||||||
|
|
||||||
def _setSpokenTextAttributes(self, view, setAttributes,
|
def _setSpokenTextAttributes(self, view, setAttributes,
|
||||||
state, moveToTop=False):
|
state, moveToTop=False):
|
||||||
"""Given a set of spoken text attributes, update the model used by the
|
"""Given a set of spoken text attributes, update the model used by the
|
||||||
@@ -2404,6 +2677,19 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
|
|||||||
prefs["enableEchoByWord"])
|
prefs["enableEchoByWord"])
|
||||||
self.get_widget("enableEchoBySentenceCheckButton").set_active( \
|
self.get_widget("enableEchoBySentenceCheckButton").set_active( \
|
||||||
prefs["enableEchoBySentence"])
|
prefs["enableEchoBySentence"])
|
||||||
|
self.get_widget("useCustomEchoVoiceCheckButton").set_active(
|
||||||
|
prefs.get("useCustomEchoVoice", settings.useCustomEchoVoice))
|
||||||
|
self.get_widget("useCustomEchoSpeechServerCheckButton").set_active(
|
||||||
|
prefs.get("useCustomEchoSpeechServer", settings.useCustomEchoSpeechServer))
|
||||||
|
self.get_widget("useCustomEchoForKeyCheckButton").set_active(
|
||||||
|
prefs.get("useCustomEchoForKey", settings.useCustomEchoForKey))
|
||||||
|
self.get_widget("useCustomEchoForCharacterCheckButton").set_active(
|
||||||
|
prefs.get("useCustomEchoForCharacter", settings.useCustomEchoForCharacter))
|
||||||
|
self.get_widget("useCustomEchoForWordCheckButton").set_active(
|
||||||
|
prefs.get("useCustomEchoForWord", settings.useCustomEchoForWord))
|
||||||
|
self.get_widget("useCustomEchoForSentenceCheckButton").set_active(
|
||||||
|
prefs.get("useCustomEchoForSentence", settings.useCustomEchoForSentence))
|
||||||
|
self._setEchoVoiceItems()
|
||||||
|
|
||||||
# Text attributes pane.
|
# Text attributes pane.
|
||||||
#
|
#
|
||||||
@@ -2906,6 +3192,45 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
|
|||||||
self.get_widget("enableNavigationKeysCheckButton").set_sensitive(enable)
|
self.get_widget("enableNavigationKeysCheckButton").set_sensitive(enable)
|
||||||
self.get_widget("enableDiacriticalKeysCheckButton").set_sensitive( \
|
self.get_widget("enableDiacriticalKeysCheckButton").set_sensitive( \
|
||||||
enable)
|
enable)
|
||||||
|
self._setEchoVoiceItems()
|
||||||
|
|
||||||
|
def _setEchoVoiceItems(self):
|
||||||
|
"""[In]sensitize echo voice controls based on current state."""
|
||||||
|
|
||||||
|
useCustomVoice = self.get_widget("useCustomEchoVoiceCheckButton").get_active()
|
||||||
|
useCustomModule = self.get_widget("useCustomEchoSpeechServerCheckButton").get_active()
|
||||||
|
keyEchoEnabled = self.get_widget("keyEchoCheckButton").get_active()
|
||||||
|
charEchoEnabled = self.get_widget("enableEchoByCharacterCheckButton").get_active()
|
||||||
|
wordEchoEnabled = self.get_widget("enableEchoByWordCheckButton").get_active()
|
||||||
|
sentenceEchoEnabled = self.get_widget("enableEchoBySentenceCheckButton").get_active()
|
||||||
|
speechEnabled = self.get_widget("speechSupportCheckButton").get_active()
|
||||||
|
|
||||||
|
speechSystemIsDispatcher = False
|
||||||
|
if self.speechSystemsChoice:
|
||||||
|
try:
|
||||||
|
speechSystemIsDispatcher = \
|
||||||
|
self.speechSystemsChoice.SpeechServer.getFactoryName() == guilabels.SPEECH_DISPATCHER
|
||||||
|
except Exception:
|
||||||
|
speechSystemIsDispatcher = False
|
||||||
|
|
||||||
|
voiceControlsEnabled = useCustomVoice and speechEnabled
|
||||||
|
moduleOverrideAvailable = voiceControlsEnabled and speechSystemIsDispatcher
|
||||||
|
moduleControlsEnabled = moduleOverrideAvailable and useCustomModule
|
||||||
|
|
||||||
|
self.get_widget("useCustomEchoSpeechServerCheckButton").set_sensitive(moduleOverrideAvailable)
|
||||||
|
self.get_widget("echoSpeechServers").set_sensitive(moduleControlsEnabled)
|
||||||
|
self.get_widget("echoSpeechFamilies").set_sensitive(voiceControlsEnabled)
|
||||||
|
|
||||||
|
self.get_widget("echoRateScale").set_sensitive(useCustomVoice)
|
||||||
|
self.get_widget("echoPitchScale").set_sensitive(useCustomVoice)
|
||||||
|
self.get_widget("echoVolumeScale").set_sensitive(useCustomVoice)
|
||||||
|
|
||||||
|
self.get_widget("useCustomEchoForKeyCheckButton").set_sensitive(useCustomVoice and keyEchoEnabled)
|
||||||
|
self.get_widget("useCustomEchoForCharacterCheckButton").set_sensitive(
|
||||||
|
useCustomVoice and charEchoEnabled)
|
||||||
|
self.get_widget("useCustomEchoForWordCheckButton").set_sensitive(useCustomVoice and wordEchoEnabled)
|
||||||
|
self.get_widget("useCustomEchoForSentenceCheckButton").set_sensitive(
|
||||||
|
useCustomVoice and sentenceEchoEnabled)
|
||||||
|
|
||||||
def _presentMessage(self, text, interrupt=False, voice=None):
|
def _presentMessage(self, text, interrupt=False, voice=None):
|
||||||
"""If the text field is not None, presents the given text, optionally
|
"""If the text field is not None, presents the given text, optionally
|
||||||
@@ -3232,6 +3557,8 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
|
|||||||
enable = widget.get_active()
|
enable = widget.get_active()
|
||||||
self.prefsDict["enableSpeech"] = enable
|
self.prefsDict["enableSpeech"] = enable
|
||||||
self.get_widget("speechOptionsGrid").set_sensitive(enable)
|
self.get_widget("speechOptionsGrid").set_sensitive(enable)
|
||||||
|
self._setupEchoSpeechServers()
|
||||||
|
self._setEchoVoiceItems()
|
||||||
|
|
||||||
def onlySpeakDisplayedTextToggled(self, widget):
|
def onlySpeakDisplayedTextToggled(self, widget):
|
||||||
"""Signal handler for the "toggled" signal for the GtkCheckButton
|
"""Signal handler for the "toggled" signal for the GtkCheckButton
|
||||||
@@ -3263,6 +3590,8 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
|
|||||||
selectedIndex = widget.get_active()
|
selectedIndex = widget.get_active()
|
||||||
self.speechSystemsChoice = self.speechSystemsChoices[selectedIndex]
|
self.speechSystemsChoice = self.speechSystemsChoices[selectedIndex]
|
||||||
self._setupSpeechServers()
|
self._setupSpeechServers()
|
||||||
|
self._setupEchoSpeechServers()
|
||||||
|
self._setEchoVoiceItems()
|
||||||
|
|
||||||
def speechServersChanged(self, widget):
|
def speechServersChanged(self, widget):
|
||||||
"""Signal handler for the "changed" signal for the speechServers
|
"""Signal handler for the "changed" signal for the speechServers
|
||||||
@@ -3295,6 +3624,7 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
self._setupVoices()
|
self._setupVoices()
|
||||||
|
self._setEchoVoiceItems()
|
||||||
|
|
||||||
def speechLanguagesChanged(self, widget):
|
def speechLanguagesChanged(self, widget):
|
||||||
"""Signal handler for the "value_changed" signal for the languages
|
"""Signal handler for the "value_changed" signal for the languages
|
||||||
@@ -3433,6 +3763,75 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
|
|||||||
voices.get(settings.DEFAULT_VOICE, {})[acss.ACSS.GAIN] = volume
|
voices.get(settings.DEFAULT_VOICE, {})[acss.ACSS.GAIN] = volume
|
||||||
cthulhu.cthulhuApp.settingsManager.setSetting('voices', voices)
|
cthulhu.cthulhuApp.settingsManager.setSetting('voices', voices)
|
||||||
|
|
||||||
|
def useCustomEchoVoiceToggled(self, widget):
|
||||||
|
"""Signal handler for enabling/disabling custom echo voice settings."""
|
||||||
|
|
||||||
|
self.prefsDict["useCustomEchoVoice"] = widget.get_active()
|
||||||
|
self._setEchoVoiceItems()
|
||||||
|
self._setupEchoSpeechFamilies()
|
||||||
|
|
||||||
|
def useCustomEchoSpeechServerToggled(self, widget):
|
||||||
|
"""Signal handler for enabling/disabling custom echo module override."""
|
||||||
|
|
||||||
|
self.prefsDict["useCustomEchoSpeechServer"] = widget.get_active()
|
||||||
|
self._setEchoVoiceItems()
|
||||||
|
self._setupEchoSpeechFamilies()
|
||||||
|
|
||||||
|
def echoSpeechServersChanged(self, widget):
|
||||||
|
"""Signal handler for selecting the echo speech-dispatcher module."""
|
||||||
|
|
||||||
|
if self.initializingEchoSpeech:
|
||||||
|
return
|
||||||
|
|
||||||
|
selectedIndex = widget.get_active()
|
||||||
|
if selectedIndex < 0:
|
||||||
|
self.echoSpeechServersChoice = None
|
||||||
|
self.prefsDict["echoSpeechServerInfo"] = None
|
||||||
|
self._setupEchoSpeechFamilies()
|
||||||
|
return
|
||||||
|
|
||||||
|
self.echoSpeechServersChoice = self.echoSpeechServersChoices[selectedIndex]
|
||||||
|
self.prefsDict["echoSpeechServerInfo"] = self.echoSpeechServersChoice.getInfo()
|
||||||
|
self._setupEchoSpeechFamilies()
|
||||||
|
|
||||||
|
def echoSpeechFamiliesChanged(self, widget):
|
||||||
|
"""Signal handler for selecting the echo voice family."""
|
||||||
|
|
||||||
|
if self.initializingEchoSpeech or self._updatingEchoSpeechFamilies:
|
||||||
|
return
|
||||||
|
|
||||||
|
selectedIndex = widget.get_active()
|
||||||
|
if selectedIndex < 0:
|
||||||
|
self.echoSpeechFamiliesChoice = None
|
||||||
|
return
|
||||||
|
|
||||||
|
self.echoSpeechFamiliesChoice = self.echoSpeechFamiliesChoices[selectedIndex]
|
||||||
|
self._setEchoVoiceFamily(self.echoSpeechFamiliesChoice)
|
||||||
|
|
||||||
|
def echoRateValueChanged(self, widget):
|
||||||
|
"""Signal handler for changing custom echo rate."""
|
||||||
|
|
||||||
|
if self.echoVoice is None:
|
||||||
|
self.echoVoice = acss.ACSS({})
|
||||||
|
self.echoVoice[acss.ACSS.RATE] = widget.get_value()
|
||||||
|
self.echoVoice['established'] = True
|
||||||
|
|
||||||
|
def echoPitchValueChanged(self, widget):
|
||||||
|
"""Signal handler for changing custom echo pitch."""
|
||||||
|
|
||||||
|
if self.echoVoice is None:
|
||||||
|
self.echoVoice = acss.ACSS({})
|
||||||
|
self.echoVoice[acss.ACSS.AVERAGE_PITCH] = widget.get_value()
|
||||||
|
self.echoVoice['established'] = True
|
||||||
|
|
||||||
|
def echoVolumeValueChanged(self, widget):
|
||||||
|
"""Signal handler for changing custom echo volume."""
|
||||||
|
|
||||||
|
if self.echoVoice is None:
|
||||||
|
self.echoVoice = acss.ACSS({})
|
||||||
|
self.echoVoice[acss.ACSS.GAIN] = widget.get_value()
|
||||||
|
self.echoVoice['established'] = True
|
||||||
|
|
||||||
def checkButtonToggled(self, widget):
|
def checkButtonToggled(self, widget):
|
||||||
"""Signal handler for "toggled" signal for basic GtkCheckButton
|
"""Signal handler for "toggled" signal for basic GtkCheckButton
|
||||||
widgets. The user has altered the state of the checkbox.
|
widgets. The user has altered the state of the checkbox.
|
||||||
@@ -3450,6 +3849,15 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
|
|||||||
# strip "CheckButton" from the end.
|
# strip "CheckButton" from the end.
|
||||||
settingName = settingName[:-11]
|
settingName = settingName[:-11]
|
||||||
self.prefsDict[settingName] = widget.get_active()
|
self.prefsDict[settingName] = widget.get_active()
|
||||||
|
if settingName in [
|
||||||
|
"enableEchoByCharacter",
|
||||||
|
"enableEchoByWord",
|
||||||
|
"enableEchoBySentence",
|
||||||
|
"useCustomEchoForKey",
|
||||||
|
"useCustomEchoForCharacter",
|
||||||
|
"useCustomEchoForWord",
|
||||||
|
"useCustomEchoForSentence"]:
|
||||||
|
self._setEchoVoiceItems()
|
||||||
|
|
||||||
def keyEchoChecked(self, widget):
|
def keyEchoChecked(self, widget):
|
||||||
"""Signal handler for the "toggled" signal for the
|
"""Signal handler for the "toggled" signal for the
|
||||||
@@ -4252,6 +4660,35 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
|
|||||||
settings.SYSTEM_VOICE: acss.ACSS(self.systemVoice),
|
settings.SYSTEM_VOICE: acss.ACSS(self.systemVoice),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.prefsDict["useCustomEchoVoice"] = \
|
||||||
|
self.get_widget("useCustomEchoVoiceCheckButton").get_active()
|
||||||
|
self.prefsDict["useCustomEchoSpeechServer"] = \
|
||||||
|
self.get_widget("useCustomEchoSpeechServerCheckButton").get_active()
|
||||||
|
self.prefsDict["useCustomEchoForKey"] = \
|
||||||
|
self.get_widget("useCustomEchoForKeyCheckButton").get_active()
|
||||||
|
self.prefsDict["useCustomEchoForCharacter"] = \
|
||||||
|
self.get_widget("useCustomEchoForCharacterCheckButton").get_active()
|
||||||
|
self.prefsDict["useCustomEchoForWord"] = \
|
||||||
|
self.get_widget("useCustomEchoForWordCheckButton").get_active()
|
||||||
|
self.prefsDict["useCustomEchoForSentence"] = \
|
||||||
|
self.get_widget("useCustomEchoForSentenceCheckButton").get_active()
|
||||||
|
|
||||||
|
if self.echoVoice is None:
|
||||||
|
self.echoVoice = acss.ACSS({})
|
||||||
|
|
||||||
|
# Persist slider values directly so saving does not depend on
|
||||||
|
# value-changed signal timing.
|
||||||
|
self.echoVoice[acss.ACSS.RATE] = self.get_widget("echoRateScale").get_value()
|
||||||
|
self.echoVoice[acss.ACSS.AVERAGE_PITCH] = self.get_widget("echoPitchScale").get_value()
|
||||||
|
self.echoVoice[acss.ACSS.GAIN] = self.get_widget("echoVolumeScale").get_value()
|
||||||
|
self.echoVoice['established'] = True
|
||||||
|
self.prefsDict["echoVoice"] = acss.ACSS(self.echoVoice)
|
||||||
|
|
||||||
|
if self.echoSpeechServersChoice:
|
||||||
|
self.prefsDict["echoSpeechServerInfo"] = self.echoSpeechServersChoice.getInfo()
|
||||||
|
else:
|
||||||
|
self.prefsDict["echoSpeechServerInfo"] = None
|
||||||
|
|
||||||
def applyButtonClicked(self, widget):
|
def applyButtonClicked(self, widget):
|
||||||
"""Signal handler for the "clicked" signal for the applyButton
|
"""Signal handler for the "clicked" signal for the applyButton
|
||||||
GtkButton widget. The user has clicked the Apply button.
|
GtkButton widget. The user has clicked the Apply button.
|
||||||
@@ -4284,6 +4721,7 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
|
|||||||
self._refresh_dynamic_plugin_tabs()
|
self._refresh_dynamic_plugin_tabs()
|
||||||
braille.checkBrailleSetting()
|
braille.checkBrailleSetting()
|
||||||
self._initSpeechState()
|
self._initSpeechState()
|
||||||
|
self._initEchoSpeechState()
|
||||||
self._populateKeyBindings()
|
self._populateKeyBindings()
|
||||||
self.__initProfileCombo()
|
self.__initProfileCombo()
|
||||||
|
|
||||||
@@ -4546,6 +4984,7 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
|
|||||||
braille.checkBrailleSetting()
|
braille.checkBrailleSetting()
|
||||||
|
|
||||||
self._initSpeechState()
|
self._initSpeechState()
|
||||||
|
self._initEchoSpeechState()
|
||||||
|
|
||||||
self._populateKeyBindings()
|
self._populateKeyBindings()
|
||||||
|
|
||||||
|
|||||||
@@ -5122,6 +5122,9 @@ class Utilities:
|
|||||||
if not event.type.startswith("object:text-changed:insert"):
|
if not event.type.startswith("object:text-changed:insert"):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
if not cthulhu.cthulhuApp.settingsManager.getSetting("enableKeyEcho"):
|
||||||
|
return False
|
||||||
|
|
||||||
if AXUtilities.is_focusable(event.source) \
|
if AXUtilities.is_focusable(event.source) \
|
||||||
and not AXUtilities.is_focused(event.source) \
|
and not AXUtilities.is_focused(event.source) \
|
||||||
and event.source != cthulhu_state.locusOfFocus:
|
and event.source != cthulhu_state.locusOfFocus:
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ from gi.repository import Gdk
|
|||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
import cthulhu.acss as acss
|
||||||
import cthulhu.braille as braille
|
import cthulhu.braille as braille
|
||||||
import cthulhu.cmdnames as cmdnames
|
import cthulhu.cmdnames as cmdnames
|
||||||
import cthulhu.dbus_service as dbus_service
|
import cthulhu.dbus_service as dbus_service
|
||||||
@@ -1818,6 +1819,9 @@ class Script(script.Script):
|
|||||||
if len(string) != 1:
|
if len(string) != 1:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if not cthulhu.cthulhuApp.settingsManager.getSetting('enableKeyEcho'):
|
||||||
|
return
|
||||||
|
|
||||||
if cthulhu.cthulhuApp.settingsManager.getSetting('enableEchoBySentence') \
|
if cthulhu.cthulhuApp.settingsManager.getSetting('enableEchoBySentence') \
|
||||||
and self.echoPreviousSentence(event.source):
|
and self.echoPreviousSentence(event.source):
|
||||||
return
|
return
|
||||||
@@ -2228,9 +2232,12 @@ class Script(script.Script):
|
|||||||
sentence = self.utilities.substring(obj, sentenceStartOffset + 1,
|
sentence = self.utilities.substring(obj, sentenceStartOffset + 1,
|
||||||
sentenceEndOffset + 1)
|
sentenceEndOffset + 1)
|
||||||
|
|
||||||
voice = self.speechGenerator.voice(obj=obj, string=sentence)
|
|
||||||
sentence = self.utilities.adjustForRepeats(sentence)
|
sentence = self.utilities.adjustForRepeats(sentence)
|
||||||
self.speakMessage(sentence, voice)
|
if self._shouldUseCustomEchoVoice("sentence"):
|
||||||
|
speech.speakEchoText(sentence, self._getCustomEchoVoice())
|
||||||
|
else:
|
||||||
|
voice = self.speechGenerator.voice(obj=obj, string=sentence)
|
||||||
|
self.speakMessage(sentence, voice)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def echoPreviousWord(self, obj, offset=None):
|
def echoPreviousWord(self, obj, offset=None):
|
||||||
@@ -2297,9 +2304,12 @@ class Script(script.Script):
|
|||||||
word = self.utilities.\
|
word = self.utilities.\
|
||||||
substring(obj, wordStartOffset + 1, wordEndOffset + 1)
|
substring(obj, wordStartOffset + 1, wordEndOffset + 1)
|
||||||
|
|
||||||
voice = self.speechGenerator.voice(obj=obj, string=word)
|
|
||||||
word = self.utilities.adjustForRepeats(word)
|
word = self.utilities.adjustForRepeats(word)
|
||||||
self.speakMessage(word, voice)
|
if self._shouldUseCustomEchoVoice("word"):
|
||||||
|
speech.speakEchoText(word, self._getCustomEchoVoice())
|
||||||
|
else:
|
||||||
|
voice = self.speechGenerator.voice(obj=obj, string=word)
|
||||||
|
self.speakMessage(word, voice)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def sayCharacter(self, obj):
|
def sayCharacter(self, obj):
|
||||||
@@ -2901,6 +2911,9 @@ class Script(script.Script):
|
|||||||
if not event.shouldEcho or event.isCthulhuModified():
|
if not event.shouldEcho or event.isCthulhuModified():
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
if not cthulhu.cthulhuApp.settingsManager.getSetting('enableKeyEcho'):
|
||||||
|
return False
|
||||||
|
|
||||||
role = AXObject.get_role(cthulhu_state.locusOfFocus)
|
role = AXObject.get_role(cthulhu_state.locusOfFocus)
|
||||||
if role in [Atspi.Role.DIALOG, Atspi.Role.FRAME, Atspi.Role.WINDOW]:
|
if role in [Atspi.Role.DIALOG, Atspi.Role.FRAME, Atspi.Role.WINDOW]:
|
||||||
focusedObject = AXUtilities.get_focused_object(cthulhu_state.activeWindow)
|
focusedObject = AXUtilities.get_focused_object(cthulhu_state.activeWindow)
|
||||||
@@ -3341,6 +3354,47 @@ class Script(script.Script):
|
|||||||
# #
|
# #
|
||||||
########################################################################
|
########################################################################
|
||||||
|
|
||||||
|
def _shouldUseCustomEchoVoice(self, echoType):
|
||||||
|
"""Returns True if custom echo settings should be used for the given type."""
|
||||||
|
|
||||||
|
settingForType = {
|
||||||
|
"key": "useCustomEchoForKey",
|
||||||
|
"character": "useCustomEchoForCharacter",
|
||||||
|
"word": "useCustomEchoForWord",
|
||||||
|
"sentence": "useCustomEchoForSentence",
|
||||||
|
}
|
||||||
|
|
||||||
|
settingName = settingForType.get(echoType)
|
||||||
|
if not settingName:
|
||||||
|
return False
|
||||||
|
|
||||||
|
settingsManager = cthulhu.cthulhuApp.settingsManager
|
||||||
|
if not settingsManager.getSetting("enableKeyEcho"):
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not settingsManager.getSetting("useCustomEchoVoice"):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return settingsManager.getSetting(settingName)
|
||||||
|
|
||||||
|
def _getCustomEchoVoice(self):
|
||||||
|
"""Returns the effective ACSS voice for custom echo output."""
|
||||||
|
|
||||||
|
settingsManager = cthulhu.cthulhuApp.settingsManager
|
||||||
|
voices = settingsManager.getSetting("voices") or {}
|
||||||
|
defaultVoice = acss.ACSS(voices.get(settings.DEFAULT_VOICE, {}))
|
||||||
|
|
||||||
|
echoVoice = settingsManager.getSetting("echoVoice")
|
||||||
|
if not echoVoice:
|
||||||
|
return defaultVoice
|
||||||
|
|
||||||
|
try:
|
||||||
|
defaultVoice.update(acss.ACSS(echoVoice))
|
||||||
|
except Exception:
|
||||||
|
debug.printException(debug.LEVEL_INFO)
|
||||||
|
|
||||||
|
return defaultVoice
|
||||||
|
|
||||||
def speakKeyEvent(self, event):
|
def speakKeyEvent(self, event):
|
||||||
"""Method to speak a keyboard event. Scripts should use this method
|
"""Method to speak a keyboard event. Scripts should use this method
|
||||||
rather than calling speech.speakKeyEvent directly."""
|
rather than calling speech.speakKeyEvent directly."""
|
||||||
@@ -3349,6 +3403,10 @@ class Script(script.Script):
|
|||||||
if event.is_printable_key():
|
if event.is_printable_key():
|
||||||
string = event.event_string
|
string = event.event_string
|
||||||
|
|
||||||
|
if self._shouldUseCustomEchoVoice("key"):
|
||||||
|
speech.speakEchoKeyEvent(event, self._getCustomEchoVoice())
|
||||||
|
return
|
||||||
|
|
||||||
voice = self.speechGenerator.voice(string=string)
|
voice = self.speechGenerator.voice(string=string)
|
||||||
speech.speakKeyEvent(event, voice)
|
speech.speakKeyEvent(event, voice)
|
||||||
|
|
||||||
@@ -3356,6 +3414,10 @@ class Script(script.Script):
|
|||||||
"""Method to speak a single character. Scripts should use this
|
"""Method to speak a single character. Scripts should use this
|
||||||
method rather than calling speech.speakCharacter directly."""
|
method rather than calling speech.speakCharacter directly."""
|
||||||
|
|
||||||
|
if self._shouldUseCustomEchoVoice("character"):
|
||||||
|
speech.speakEchoCharacter(character, self._getCustomEchoVoice())
|
||||||
|
return
|
||||||
|
|
||||||
voice = self.speechGenerator.voice(string=character)
|
voice = self.speechGenerator.voice(string=character)
|
||||||
speech.speakCharacter(character, voice)
|
speech.speakCharacter(character, voice)
|
||||||
|
|
||||||
|
|||||||
@@ -53,6 +53,14 @@ userCustomizableSettings = [
|
|||||||
"enableEchoByWord",
|
"enableEchoByWord",
|
||||||
"enableEchoBySentence",
|
"enableEchoBySentence",
|
||||||
"enableKeyEcho",
|
"enableKeyEcho",
|
||||||
|
"useCustomEchoVoice",
|
||||||
|
"useCustomEchoSpeechServer",
|
||||||
|
"echoSpeechServerInfo",
|
||||||
|
"echoVoice",
|
||||||
|
"useCustomEchoForKey",
|
||||||
|
"useCustomEchoForCharacter",
|
||||||
|
"useCustomEchoForWord",
|
||||||
|
"useCustomEchoForSentence",
|
||||||
"gameMode",
|
"gameMode",
|
||||||
"nvda2cthulhuTranslateEnabled",
|
"nvda2cthulhuTranslateEnabled",
|
||||||
"enableAlphabeticKeys",
|
"enableAlphabeticKeys",
|
||||||
@@ -352,6 +360,14 @@ enableDiacriticalKeys = False
|
|||||||
enableEchoByCharacter = False
|
enableEchoByCharacter = False
|
||||||
enableEchoByWord = False
|
enableEchoByWord = False
|
||||||
enableEchoBySentence = False
|
enableEchoBySentence = False
|
||||||
|
useCustomEchoVoice = False
|
||||||
|
useCustomEchoSpeechServer = False
|
||||||
|
echoSpeechServerInfo = None
|
||||||
|
echoVoice = ACSS({})
|
||||||
|
useCustomEchoForKey = True
|
||||||
|
useCustomEchoForCharacter = True
|
||||||
|
useCustomEchoForWord = False
|
||||||
|
useCustomEchoForSentence = False
|
||||||
presentLockingKeys = None
|
presentLockingKeys = None
|
||||||
|
|
||||||
# Mouse review
|
# Mouse review
|
||||||
|
|||||||
@@ -314,6 +314,13 @@ class SettingsManager(object):
|
|||||||
converted_voices[voice_type] = voice_def
|
converted_voices[voice_type] = voice_def
|
||||||
general["voices"] = converted_voices
|
general["voices"] = converted_voices
|
||||||
|
|
||||||
|
echo_voice = general.get("echoVoice")
|
||||||
|
if isinstance(echo_voice, dict):
|
||||||
|
try:
|
||||||
|
general["echoVoice"] = ACSS(echo_voice)
|
||||||
|
except Exception:
|
||||||
|
general["echoVoice"] = echo_voice
|
||||||
|
|
||||||
return general
|
return general
|
||||||
|
|
||||||
def getDefaultSetting(self, settingName: str) -> Any:
|
def getDefaultSetting(self, settingName: str) -> Any:
|
||||||
@@ -632,6 +639,14 @@ class SettingsManager(object):
|
|||||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||||
|
|
||||||
self.profileGeneral = {}
|
self.profileGeneral = {}
|
||||||
|
alwaysPersistKeys = {
|
||||||
|
'useCustomEchoVoice',
|
||||||
|
'useCustomEchoSpeechServer',
|
||||||
|
'useCustomEchoForKey',
|
||||||
|
'useCustomEchoForCharacter',
|
||||||
|
'useCustomEchoForWord',
|
||||||
|
'useCustomEchoForSentence',
|
||||||
|
}
|
||||||
|
|
||||||
for key, value in general.items():
|
for key, value in general.items():
|
||||||
if key in ['startingProfile', 'activeProfile']:
|
if key in ['startingProfile', 'activeProfile']:
|
||||||
@@ -640,6 +655,8 @@ class SettingsManager(object):
|
|||||||
self.profileGeneral[key] = value
|
self.profileGeneral[key] = value
|
||||||
elif key in ['activePlugins', 'pluginSources']:
|
elif key in ['activePlugins', 'pluginSources']:
|
||||||
self.profileGeneral[key] = copy.deepcopy(value)
|
self.profileGeneral[key] = copy.deepcopy(value)
|
||||||
|
elif key in alwaysPersistKeys:
|
||||||
|
self.profileGeneral[key] = copy.deepcopy(value)
|
||||||
elif value != self.defaultGeneral.get(key):
|
elif value != self.defaultGeneral.get(key):
|
||||||
self.profileGeneral[key] = value
|
self.profileGeneral[key] = value
|
||||||
elif self.general.get(key) != value:
|
elif self.general.get(key) != value:
|
||||||
|
|||||||
+129
-11
@@ -69,6 +69,7 @@ def _ensureLogger() -> None:
|
|||||||
# The speech server to use for all speech operations.
|
# The speech server to use for all speech operations.
|
||||||
#
|
#
|
||||||
_speechserver: Optional[SpeechServer] = None
|
_speechserver: Optional[SpeechServer] = None
|
||||||
|
_echoSpeechserver: Optional[SpeechServer] = None
|
||||||
|
|
||||||
# The last time something was spoken.
|
# The last time something was spoken.
|
||||||
_timestamp: float = 0.0
|
_timestamp: float = 0.0
|
||||||
@@ -76,12 +77,15 @@ _timestamp: float = 0.0
|
|||||||
# Optional callback for live monitoring of spoken text.
|
# Optional callback for live monitoring of spoken text.
|
||||||
_monitorWriteTextCallback: Optional[Callable[[str], None]] = None
|
_monitorWriteTextCallback: Optional[Callable[[str], None]] = None
|
||||||
|
|
||||||
def _initSpeechServer(moduleName: Optional[str], speechServerInfo: Optional[Any]) -> None:
|
def _isSpeechDispatcherFactory(moduleName: Optional[str]) -> bool:
|
||||||
|
if not moduleName:
|
||||||
|
return False
|
||||||
|
return moduleName.split(".")[-1] == "speechdispatcherfactory"
|
||||||
|
|
||||||
global _speechserver
|
def _initSpeechServer(moduleName: Optional[str], speechServerInfo: Optional[Any]) -> SpeechServer:
|
||||||
|
|
||||||
if not moduleName:
|
if not moduleName:
|
||||||
return
|
raise Exception("ERROR: No speech server module name provided")
|
||||||
|
|
||||||
factory = None
|
factory = None
|
||||||
try:
|
try:
|
||||||
@@ -94,35 +98,58 @@ def _initSpeechServer(moduleName: Optional[str], speechServerInfo: Optional[Any]
|
|||||||
|
|
||||||
# Now, get the speech server we care about.
|
# Now, get the speech server we care about.
|
||||||
#
|
#
|
||||||
speechServerInfo = settings.speechServerInfo
|
speech_server = None
|
||||||
if factory:
|
if factory:
|
||||||
if speechServerInfo:
|
if speechServerInfo:
|
||||||
_speechserver = factory.SpeechServer.getSpeechServer(speechServerInfo) # type: ignore
|
speech_server = factory.SpeechServer.getSpeechServer(speechServerInfo) # type: ignore
|
||||||
|
|
||||||
if not _speechserver:
|
if not speech_server:
|
||||||
_speechserver = factory.SpeechServer.getSpeechServer() # type: ignore
|
speech_server = factory.SpeechServer.getSpeechServer() # type: ignore
|
||||||
if speechServerInfo:
|
if speechServerInfo:
|
||||||
tokens = ["SPEECH: Invalid speechServerInfo:", speechServerInfo]
|
tokens = ["SPEECH: Invalid speechServerInfo:", speechServerInfo]
|
||||||
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
||||||
|
|
||||||
if not _speechserver:
|
if not speech_server:
|
||||||
raise Exception(f"ERROR: No speech server for factory: {moduleName}")
|
raise Exception(f"ERROR: No speech server for factory: {moduleName}")
|
||||||
|
|
||||||
|
return speech_server
|
||||||
|
|
||||||
|
def _refreshEchoSpeechServer() -> None:
|
||||||
|
global _echoSpeechserver
|
||||||
|
_echoSpeechserver = None
|
||||||
|
|
||||||
|
if not settings.enableSpeech or not settings.useCustomEchoSpeechServer:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not _isSpeechDispatcherFactory(settings.speechServerFactory):
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
_echoSpeechserver = _initSpeechServer(
|
||||||
|
settings.speechServerFactory,
|
||||||
|
settings.echoSpeechServerInfo
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
debug.printException(debug.LEVEL_INFO)
|
||||||
|
_echoSpeechserver = None
|
||||||
|
|
||||||
def init() -> None:
|
def init() -> None:
|
||||||
|
global _speechserver
|
||||||
debug.printMessage(debug.LEVEL_INFO, 'SPEECH: Initializing', True)
|
debug.printMessage(debug.LEVEL_INFO, 'SPEECH: Initializing', True)
|
||||||
if _speechserver:
|
if _speechserver:
|
||||||
debug.printMessage(debug.LEVEL_INFO, 'SPEECH: Already initialized', True)
|
debug.printMessage(debug.LEVEL_INFO, 'SPEECH: Already initialized', True)
|
||||||
|
_refreshEchoSpeechServer()
|
||||||
return
|
return
|
||||||
|
|
||||||
chosenModuleName = settings.speechServerFactory
|
chosenModuleName = settings.speechServerFactory
|
||||||
try:
|
try:
|
||||||
_initSpeechServer(chosenModuleName, settings.speechServerInfo)
|
_speechserver = _initSpeechServer(chosenModuleName, settings.speechServerInfo)
|
||||||
except Exception:
|
except Exception:
|
||||||
moduleNames = settings.speechFactoryModules
|
moduleNames = settings.speechFactoryModules
|
||||||
for moduleName in moduleNames:
|
for moduleName in moduleNames:
|
||||||
if moduleName != settings.speechServerFactory:
|
if moduleName != settings.speechServerFactory:
|
||||||
try:
|
try:
|
||||||
_initSpeechServer(moduleName, None)
|
_speechserver = _initSpeechServer(moduleName, None)
|
||||||
if _speechserver:
|
if _speechserver:
|
||||||
chosenModuleName = moduleName
|
chosenModuleName = moduleName
|
||||||
break
|
break
|
||||||
@@ -144,6 +171,7 @@ def init() -> None:
|
|||||||
msg = 'SPEECH: Not available'
|
msg = 'SPEECH: Not available'
|
||||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||||
|
|
||||||
|
_refreshEchoSpeechServer()
|
||||||
debug.printMessage(debug.LEVEL_INFO, 'SPEECH: Initialized', True)
|
debug.printMessage(debug.LEVEL_INFO, 'SPEECH: Initialized', True)
|
||||||
|
|
||||||
def checkSpeechSetting() -> None:
|
def checkSpeechSetting() -> None:
|
||||||
@@ -157,6 +185,9 @@ def checkSpeechSetting() -> None:
|
|||||||
|
|
||||||
if not _speechserver:
|
if not _speechserver:
|
||||||
init()
|
init()
|
||||||
|
return
|
||||||
|
|
||||||
|
_refreshEchoSpeechServer()
|
||||||
|
|
||||||
def getSpeechServer() -> Optional[SpeechServer]:
|
def getSpeechServer() -> Optional[SpeechServer]:
|
||||||
"""Returns the speech server instance."""
|
"""Returns the speech server instance."""
|
||||||
@@ -170,6 +201,7 @@ def setSpeechServer(speechServer: SpeechServer) -> None:
|
|||||||
"""
|
"""
|
||||||
global _speechserver
|
global _speechserver
|
||||||
_speechserver = speechServer
|
_speechserver = speechServer
|
||||||
|
_refreshEchoSpeechServer()
|
||||||
|
|
||||||
def set_monitor_callbacks(writeText: Optional[Callable[[str], None]] = None) -> None:
|
def set_monitor_callbacks(writeText: Optional[Callable[[str], None]] = None) -> None:
|
||||||
"""Sets runtime callbacks for live speech monitoring."""
|
"""Sets runtime callbacks for live speech monitoring."""
|
||||||
@@ -418,6 +450,35 @@ def speakKeyEvent(event: Any, acss: Optional[Any] = None) -> None:
|
|||||||
if _speechserver:
|
if _speechserver:
|
||||||
_speechserver.speakKeyEvent(event, acss) # type: ignore
|
_speechserver.speakKeyEvent(event, acss) # type: ignore
|
||||||
|
|
||||||
|
def speakEchoKeyEvent(event: Any, acss: Optional[Any] = None) -> None:
|
||||||
|
"""Speaks a key event for key/typing echo using echo speech settings."""
|
||||||
|
|
||||||
|
_ensureLogger()
|
||||||
|
|
||||||
|
if settings.silenceSpeech:
|
||||||
|
return
|
||||||
|
|
||||||
|
global _timestamp
|
||||||
|
if _timestamp:
|
||||||
|
msg = f"SPEECH: Last spoke {time.time() - _timestamp:.4f} seconds ago"
|
||||||
|
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||||
|
_timestamp = time.time()
|
||||||
|
|
||||||
|
keyname = event.getKeyName()
|
||||||
|
lockingStateString = event.getLockingStateString()
|
||||||
|
acss = __resolveACSS(acss)
|
||||||
|
msg = f"{keyname} {lockingStateString}"
|
||||||
|
logLine = f"SPEECH ECHO OUTPUT: '{msg.strip()}' {acss}"
|
||||||
|
debug.printMessage(debug.LEVEL_INFO, logLine, True)
|
||||||
|
if log:
|
||||||
|
log.info(logLine)
|
||||||
|
|
||||||
|
_write_to_monitor(msg.strip())
|
||||||
|
|
||||||
|
server = _echoSpeechserver or _speechserver
|
||||||
|
if server:
|
||||||
|
server.speakKeyEvent(event, acss) # type: ignore
|
||||||
|
|
||||||
def speakCharacter(character: str, acss: Optional[Any] = None) -> None:
|
def speakCharacter(character: str, acss: Optional[Any] = None) -> None:
|
||||||
"""Speaks a single character immediately.
|
"""Speaks a single character immediately.
|
||||||
|
|
||||||
@@ -452,6 +513,58 @@ def speakCharacter(character: str, acss: Optional[Any] = None) -> None:
|
|||||||
if _speechserver:
|
if _speechserver:
|
||||||
_speechserver.speakCharacter(character, acss=acss) # type: ignore
|
_speechserver.speakCharacter(character, acss=acss) # type: ignore
|
||||||
|
|
||||||
|
def speakEchoCharacter(character: str, acss: Optional[Any] = None) -> None:
|
||||||
|
"""Speaks a single character for key/typing echo using echo speech settings."""
|
||||||
|
|
||||||
|
_ensureLogger()
|
||||||
|
|
||||||
|
if settings.silenceSpeech:
|
||||||
|
return
|
||||||
|
|
||||||
|
global _timestamp
|
||||||
|
if _timestamp:
|
||||||
|
msg = f"SPEECH: Last spoke {time.time() - _timestamp:.4f} seconds ago"
|
||||||
|
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||||
|
_timestamp = time.time()
|
||||||
|
|
||||||
|
acss = __resolveACSS(acss)
|
||||||
|
tokens = [f"SPEECH ECHO OUTPUT: '{character}'", acss]
|
||||||
|
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
||||||
|
if log:
|
||||||
|
log.info(f"SPEECH ECHO OUTPUT: '{character}'")
|
||||||
|
|
||||||
|
_write_to_monitor(character)
|
||||||
|
|
||||||
|
server = _echoSpeechserver or _speechserver
|
||||||
|
if server:
|
||||||
|
server.speakCharacter(character, acss=acss) # type: ignore
|
||||||
|
|
||||||
|
def speakEchoText(text: str, acss: Optional[Any] = None, interrupt: bool = True) -> None:
|
||||||
|
"""Speaks text for typing echo using echo speech settings."""
|
||||||
|
|
||||||
|
_ensureLogger()
|
||||||
|
|
||||||
|
if settings.silenceSpeech:
|
||||||
|
return
|
||||||
|
|
||||||
|
global _timestamp
|
||||||
|
if _timestamp:
|
||||||
|
msg = f"SPEECH: Last spoke {time.time() - _timestamp:.4f} seconds ago"
|
||||||
|
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||||
|
_timestamp = time.time()
|
||||||
|
|
||||||
|
acss = __resolveACSS(acss)
|
||||||
|
logLine = f"SPEECH ECHO OUTPUT: '{text}' {acss}"
|
||||||
|
debug.printMessage(debug.LEVEL_INFO, logLine, True)
|
||||||
|
if log:
|
||||||
|
log.info(logLine)
|
||||||
|
|
||||||
|
_write_to_monitor(text)
|
||||||
|
|
||||||
|
server = _echoSpeechserver or _speechserver
|
||||||
|
if server:
|
||||||
|
server.speak(text, acss, interrupt) # type: ignore
|
||||||
|
|
||||||
def getInfo() -> Optional[Any]:
|
def getInfo() -> Optional[Any]:
|
||||||
info = None
|
info = None
|
||||||
if _speechserver:
|
if _speechserver:
|
||||||
@@ -462,13 +575,18 @@ def getInfo() -> Optional[Any]:
|
|||||||
def stop() -> None:
|
def stop() -> None:
|
||||||
if _speechserver:
|
if _speechserver:
|
||||||
_speechserver.stop() # type: ignore
|
_speechserver.stop() # type: ignore
|
||||||
|
if _echoSpeechserver and _echoSpeechserver != _speechserver:
|
||||||
|
_echoSpeechserver.stop() # type: ignore
|
||||||
|
|
||||||
def shutdown() -> None:
|
def shutdown() -> None:
|
||||||
debug.printMessage(debug.LEVEL_INFO, 'SPEECH: Shutting down', True)
|
debug.printMessage(debug.LEVEL_INFO, 'SPEECH: Shutting down', True)
|
||||||
global _speechserver
|
global _speechserver, _echoSpeechserver
|
||||||
if _speechserver:
|
if _speechserver:
|
||||||
_speechserver.shutdownActiveServers() # type: ignore
|
_speechserver.shutdownActiveServers() # type: ignore
|
||||||
_speechserver = None
|
_speechserver = None
|
||||||
|
elif _echoSpeechserver:
|
||||||
|
_echoSpeechserver.shutdownActiveServers() # type: ignore
|
||||||
|
_echoSpeechserver = None
|
||||||
|
|
||||||
def reset(text: Optional[str] = None, acss: Optional[Any] = None) -> None:
|
def reset(text: Optional[str] = None, acss: Optional[Any] = None) -> None:
|
||||||
if _speechserver:
|
if _speechserver:
|
||||||
|
|||||||
Reference in New Issue
Block a user