Merge branch 'testing'
This commit is contained in:
@@ -17,7 +17,6 @@ This package requires the following packages, all available from SlackBuilds.org
|
|||||||
- at-spi2-core
|
- at-spi2-core
|
||||||
- brltty
|
- brltty
|
||||||
- gobject-introspection
|
- gobject-introspection
|
||||||
- gsettings-desktop-schemas
|
|
||||||
- gstreamer
|
- gstreamer
|
||||||
- gst-plugins-base
|
- gst-plugins-base
|
||||||
- gst-plugins-good
|
- gst-plugins-good
|
||||||
|
|||||||
@@ -5,6 +5,6 @@ DOWNLOAD="https://git.stormux.org/storm/cthulhu.git"
|
|||||||
MD5SUM="SKIP"
|
MD5SUM="SKIP"
|
||||||
DOWNLOAD_x86_64=""
|
DOWNLOAD_x86_64=""
|
||||||
MD5SUM_x86_64=""
|
MD5SUM_x86_64=""
|
||||||
REQUIRES="at-spi2-core brltty gobject-introspection gsettings-desktop-schemas gstreamer gst-plugins-base gst-plugins-good gtk3 liblouis libwnck3 python3-atspi python3-cairo python3-gobject python3-setproctitle speech-dispatcher"
|
REQUIRES="at-spi2-core brltty gobject-introspection gstreamer gst-plugins-base gst-plugins-good gtk3 liblouis libwnck3 python3-atspi python3-cairo python3-gobject python3-setproctitle speech-dispatcher"
|
||||||
MAINTAINER="Storm Dragon"
|
MAINTAINER="Storm Dragon"
|
||||||
EMAIL="storm_dragon@stormux.org"
|
EMAIL="storm_dragon@stormux.org"
|
||||||
|
|||||||
+9
-11
@@ -322,17 +322,15 @@ originated as a community effort led by the Sun Microsystems Inc.
|
|||||||
Accessibility Program Office and with contributions from many community members.
|
Accessibility Program Office and with contributions from many community members.
|
||||||
.SH SEE ALSO
|
.SH SEE ALSO
|
||||||
For more information please visit
|
For more information please visit
|
||||||
.B cthulhu
|
.B Cthulhu
|
||||||
wiki at
|
at
|
||||||
.UR http://live.gnome.org/Cthulhu
|
.UR https://git.stormux.org/storm/cthulhu
|
||||||
<http://live.gnome.org/Cthulhu>
|
<https://git.stormux.org/storm/cthulhu>
|
||||||
.UE
|
.UE
|
||||||
.P
|
.P
|
||||||
The
|
The
|
||||||
.B cthulhu
|
.B Stormux
|
||||||
mailing list
|
community list is available at
|
||||||
.UR http://mail.gnome.org/mailman/listinfo/cthulhu-list
|
.UR https://groups.io/g/stormux
|
||||||
<http://mail.gnome.org/mailman/listinfo/cthulhu-list>
|
<https://groups.io/g/stormux>
|
||||||
To post a message to all
|
.UE
|
||||||
.B cthulhu
|
|
||||||
list, send a email to https://groups.io/g/stormux
|
|
||||||
|
|||||||
@@ -109,6 +109,111 @@ class Backend:
|
|||||||
if key not in targetTable:
|
if key not in targetTable:
|
||||||
targetTable[key] = newValue
|
targetTable[key] = newValue
|
||||||
|
|
||||||
|
def _legacyProfileValue(self, profileTable, sectionName, legacyKey):
|
||||||
|
section = profileTable.get(sectionName)
|
||||||
|
if not isinstance(section, dict):
|
||||||
|
return None
|
||||||
|
return section.get(legacyKey)
|
||||||
|
|
||||||
|
def _normalizeLegacyValue(self, currentKey, legacyValue):
|
||||||
|
if currentKey == 'keyboardLayout' and isinstance(legacyValue, str):
|
||||||
|
layout = legacyValue.strip().lower()
|
||||||
|
if layout == 'desktop':
|
||||||
|
return settings.GENERAL_KEYBOARD_LAYOUT_DESKTOP
|
||||||
|
if layout == 'laptop':
|
||||||
|
return settings.GENERAL_KEYBOARD_LAYOUT_LAPTOP
|
||||||
|
return legacyValue
|
||||||
|
|
||||||
|
def _normalizeLegacyProfile(self, profileName, profileTable):
|
||||||
|
if not isinstance(profileTable, dict):
|
||||||
|
return profileTable
|
||||||
|
|
||||||
|
profileSettings = {}
|
||||||
|
legacySections = {
|
||||||
|
'metadata',
|
||||||
|
'plugins',
|
||||||
|
'ai-assistant',
|
||||||
|
'ocr',
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, value in profileTable.items():
|
||||||
|
if key in legacySections:
|
||||||
|
continue
|
||||||
|
if key == 'keybindings':
|
||||||
|
if isinstance(value, dict):
|
||||||
|
keybindings = dict(value)
|
||||||
|
keybindings.pop('keyboard-layout', None)
|
||||||
|
keybindings.pop('desktop-modifier-keys', None)
|
||||||
|
keybindings.pop('laptop-modifier-keys', None)
|
||||||
|
if keybindings:
|
||||||
|
profileSettings[key] = keybindings
|
||||||
|
continue
|
||||||
|
profileSettings[key] = value
|
||||||
|
|
||||||
|
displayName = self._legacyProfileValue(profileTable, 'metadata', 'display-name')
|
||||||
|
internalName = self._legacyProfileValue(profileTable, 'metadata', 'internal-name')
|
||||||
|
if 'profile' not in profileSettings and (displayName or internalName):
|
||||||
|
profileSettings['profile'] = [
|
||||||
|
displayName or str(profileName).title(),
|
||||||
|
internalName or profileName,
|
||||||
|
]
|
||||||
|
|
||||||
|
legacyKeyMap = {
|
||||||
|
('keybindings', 'keyboard-layout'): 'keyboardLayout',
|
||||||
|
('keybindings', 'desktop-modifier-keys'): 'cthulhuModifierKeys',
|
||||||
|
('keybindings', 'laptop-modifier-keys'): 'cthulhuModifierKeys',
|
||||||
|
('plugins', 'active-plugins'): 'activePlugins',
|
||||||
|
('plugins', 'plugin-sources'): 'pluginSources',
|
||||||
|
('ai-assistant', 'enabled'): 'aiAssistantEnabled',
|
||||||
|
('ai-assistant', 'provider'): 'aiProvider',
|
||||||
|
('ai-assistant', 'api-key-file'): 'aiApiKeyFile',
|
||||||
|
('ai-assistant', 'ollama-model'): 'aiOllamaModel',
|
||||||
|
('ai-assistant', 'ollama-endpoint'): 'aiOllamaEndpoint',
|
||||||
|
('ai-assistant', 'confirmation-required'): 'aiConfirmationRequired',
|
||||||
|
('ai-assistant', 'action-timeout'): 'aiActionTimeout',
|
||||||
|
('ai-assistant', 'screenshot-quality'): 'aiScreenshotQuality',
|
||||||
|
('ai-assistant', 'max-context-length'): 'aiMaxContextLength',
|
||||||
|
('ocr', 'language-code'): 'ocrLanguageCode',
|
||||||
|
('ocr', 'scale-factor'): 'ocrScaleFactor',
|
||||||
|
('ocr', 'grayscale-image'): 'ocrGrayscaleImg',
|
||||||
|
('ocr', 'invert-image'): 'ocrInvertImg',
|
||||||
|
('ocr', 'black-white-image'): 'ocrBlackWhiteImg',
|
||||||
|
('ocr', 'black-white-threshold'): 'ocrBlackWhiteImgValue',
|
||||||
|
('ocr', 'color-calculation'): 'ocrColorCalculation',
|
||||||
|
('ocr', 'color-calculation-max'): 'ocrColorCalculationMax',
|
||||||
|
('ocr', 'copy-to-clipboard'): 'ocrCopyToClipboard',
|
||||||
|
}
|
||||||
|
|
||||||
|
for (sectionName, legacyKey), currentKey in legacyKeyMap.items():
|
||||||
|
if currentKey in profileSettings:
|
||||||
|
continue
|
||||||
|
legacyValue = self._legacyProfileValue(profileTable, sectionName, legacyKey)
|
||||||
|
if legacyValue is not None:
|
||||||
|
profileSettings[currentKey] = self._normalizeLegacyValue(currentKey, legacyValue)
|
||||||
|
|
||||||
|
return profileSettings
|
||||||
|
|
||||||
|
def _normalizeProfiles(self, profiles):
|
||||||
|
if not isinstance(profiles, dict):
|
||||||
|
return {}
|
||||||
|
return {
|
||||||
|
profileName: self._normalizeLegacyProfile(profileName, profileTable)
|
||||||
|
for profileName, profileTable in profiles.items()
|
||||||
|
}
|
||||||
|
|
||||||
|
def _normalizeProfilesDocument(self, prefsDoc):
|
||||||
|
profiles = prefsDoc.get('profiles')
|
||||||
|
if not isinstance(profiles, dict):
|
||||||
|
return
|
||||||
|
|
||||||
|
for profileName in list(profiles.keys()):
|
||||||
|
profileTable = profiles[profileName]
|
||||||
|
normalizedProfile = self._normalizeLegacyProfile(profileName, profileTable)
|
||||||
|
profiles[profileName] = self._stripNone(normalizedProfile)
|
||||||
|
|
||||||
|
if 'format-version' in prefsDoc:
|
||||||
|
del prefsDoc['format-version']
|
||||||
|
|
||||||
def saveDefaultSettings(self, general, pronunciations, keybindings):
|
def saveDefaultSettings(self, general, pronunciations, keybindings):
|
||||||
""" Save default settings for all the properties from
|
""" Save default settings for all the properties from
|
||||||
cthulhu.settings. """
|
cthulhu.settings. """
|
||||||
@@ -167,6 +272,7 @@ class Backend:
|
|||||||
general = self._stripNone(general)
|
general = self._stripNone(general)
|
||||||
|
|
||||||
prefsDoc = self._readDocument(self.settingsFile)
|
prefsDoc = self._readDocument(self.settingsFile)
|
||||||
|
self._normalizeProfilesDocument(prefsDoc)
|
||||||
profiles = prefsDoc.get('profiles')
|
profiles = prefsDoc.get('profiles')
|
||||||
if profiles is None or not isinstance(profiles, dict):
|
if profiles is None or not isinstance(profiles, dict):
|
||||||
prefsDoc['profiles'] = {}
|
prefsDoc['profiles'] = {}
|
||||||
@@ -192,7 +298,7 @@ class Backend:
|
|||||||
self.general = dict(prefsDoc.get('general', {}))
|
self.general = dict(prefsDoc.get('general', {}))
|
||||||
self.pronunciations = dict(prefsDoc.get('pronunciations', {}))
|
self.pronunciations = dict(prefsDoc.get('pronunciations', {}))
|
||||||
self.keybindings = dict(prefsDoc.get('keybindings', {}))
|
self.keybindings = dict(prefsDoc.get('keybindings', {}))
|
||||||
self.profiles = dict(prefsDoc.get('profiles', {}))
|
self.profiles = self._normalizeProfiles(dict(prefsDoc.get('profiles', {})))
|
||||||
except Exception:
|
except Exception:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
@@ -178,7 +178,7 @@ class BrailleGenerator(generator.Generator):
|
|||||||
Atspi.Role.EXTENDED,
|
Atspi.Role.EXTENDED,
|
||||||
Atspi.Role.LINK]
|
Atspi.Role.LINK]
|
||||||
|
|
||||||
# egg-list-box, e.g. privacy panel in gnome-control-center
|
# egg-list-box-style containers can expose selected panels as list items.
|
||||||
if AXUtilities.is_list_box(AXObject.get_parent(obj)):
|
if AXUtilities.is_list_box(AXObject.get_parent(obj)):
|
||||||
doNotPresent.append(AXObject.get_role(obj))
|
doNotPresent.append(AXObject.get_role(obj))
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ from . import dbus_service
|
|||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from types import FrameType
|
from types import FrameType
|
||||||
from gi.repository.Gio import Settings as GSettings
|
|
||||||
from gi.repository import Gtk
|
from gi.repository import Gtk
|
||||||
|
|
||||||
from .settings_manager import SettingsManager
|
from .settings_manager import SettingsManager
|
||||||
@@ -250,12 +249,6 @@ from gi.repository import Atspi
|
|||||||
from gi.repository import Gdk
|
from gi.repository import Gdk
|
||||||
from gi.repository import GObject
|
from gi.repository import GObject
|
||||||
|
|
||||||
try:
|
|
||||||
from gi.repository.Gio import Settings
|
|
||||||
a11yAppSettings: Optional[GSettings] = Settings(schema_id='org.gnome.desktop.a11y.applications')
|
|
||||||
except Exception:
|
|
||||||
a11yAppSettings = None
|
|
||||||
|
|
||||||
from . import braille
|
from . import braille
|
||||||
from . import debug
|
from . import debug
|
||||||
from . import event_manager
|
from . import event_manager
|
||||||
@@ -299,15 +292,6 @@ from . import resource_manager
|
|||||||
|
|
||||||
# Old global variables removed - now using cthulhuApp.* instead
|
# Old global variables removed - now using cthulhuApp.* instead
|
||||||
|
|
||||||
def onEnabledChanged(gsetting: GSettings, key: str) -> None:
|
|
||||||
try:
|
|
||||||
enabled: bool = gsetting.get_boolean(key)
|
|
||||||
except Exception:
|
|
||||||
return
|
|
||||||
|
|
||||||
if key == 'screen-reader-enabled' and not enabled:
|
|
||||||
shutdown()
|
|
||||||
|
|
||||||
EXIT_CODE_HANG: int = 50
|
EXIT_CODE_HANG: int = 50
|
||||||
|
|
||||||
# The user-settings module (see loadUserSettings).
|
# The user-settings module (see loadUserSettings).
|
||||||
@@ -651,12 +635,6 @@ def init() -> bool:
|
|||||||
signal.alarm(0)
|
signal.alarm(0)
|
||||||
|
|
||||||
_initialized = True
|
_initialized = True
|
||||||
# In theory, we can do this through dbus. In practice, it fails to
|
|
||||||
# work sometimes. Until we know why, we need to leave this as-is
|
|
||||||
# so that we respond when gnome-control-center is used to stop Cthulhu.
|
|
||||||
if a11yAppSettings:
|
|
||||||
a11yAppSettings.connect('changed', onEnabledChanged)
|
|
||||||
|
|
||||||
debug.printMessage(debug.LEVEL_INFO, 'CTHULHU: Initialized', True)
|
debug.printMessage(debug.LEVEL_INFO, 'CTHULHU: Initialized', True)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -4984,12 +4984,10 @@ print(json.dumps(result))
|
|||||||
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.
|
||||||
Write out the users preferences. If GNOME accessibility hadn't
|
Write out the users preferences. Shut down any active speech servers
|
||||||
previously been enabled, warn the user that they will need to
|
that were started. Reload the users preferences to get the new
|
||||||
log out. Shut down any active speech servers that were started.
|
speech, braille and key echo value to take effect. Do not dismiss
|
||||||
Reload the users preferences to get the new speech, braille and
|
the configuration window.
|
||||||
key echo value to take effect. Do not dismiss the configuration
|
|
||||||
window.
|
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
- widget: the component that generated the signal.
|
- widget: the component that generated the signal.
|
||||||
@@ -5042,11 +5040,10 @@ print(json.dumps(result))
|
|||||||
def okButtonClicked(self, widget=None):
|
def okButtonClicked(self, widget=None):
|
||||||
"""Signal handler for the "clicked" signal for the okButton
|
"""Signal handler for the "clicked" signal for the okButton
|
||||||
GtkButton widget. The user has clicked the OK button.
|
GtkButton widget. The user has clicked the OK button.
|
||||||
Write out the users preferences. If GNOME accessibility hadn't
|
Write out the users preferences. Shut down any active speech servers
|
||||||
previously been enabled, warn the user that they will need to
|
that were started. Reload the users preferences to get the new
|
||||||
log out. Shut down any active speech servers that were started.
|
speech, braille and key echo value to take effect. Hide the
|
||||||
Reload the users preferences to get the new speech, braille and
|
configuration window.
|
||||||
key echo value to take effect. Hide the configuration window.
|
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
- widget: the component that generated the signal.
|
- widget: the component that generated the signal.
|
||||||
|
|||||||
@@ -1525,6 +1525,106 @@ class Script(script.Script):
|
|||||||
for character in itemString:
|
for character in itemString:
|
||||||
self.speakCharacter(character)
|
self.speakCharacter(character)
|
||||||
|
|
||||||
|
def _diagnostic_object_summary(self, obj):
|
||||||
|
if obj is None:
|
||||||
|
return "None"
|
||||||
|
|
||||||
|
try:
|
||||||
|
if AXObject.is_dead(obj):
|
||||||
|
return "dead"
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
name = AXObject.get_name(obj) or ""
|
||||||
|
except Exception:
|
||||||
|
name = "<error>"
|
||||||
|
|
||||||
|
try:
|
||||||
|
role = AXObject.get_role_name(obj) or ""
|
||||||
|
except Exception:
|
||||||
|
role = "<error>"
|
||||||
|
|
||||||
|
try:
|
||||||
|
pid = AXObject.get_process_id(obj)
|
||||||
|
except Exception:
|
||||||
|
pid = "<error>"
|
||||||
|
|
||||||
|
return f"name={name!r}, role={role!r}, pid={pid}"
|
||||||
|
|
||||||
|
def _diagnostic_callable_value(self, obj, methodName):
|
||||||
|
method = getattr(obj, methodName, None)
|
||||||
|
if not callable(method):
|
||||||
|
return "<unavailable>"
|
||||||
|
try:
|
||||||
|
return method()
|
||||||
|
except Exception as error:
|
||||||
|
return f"<error: {error}>"
|
||||||
|
|
||||||
|
@dbus_service.command
|
||||||
|
def getDiagnosticState(self, script=None, event=None, notify_user=True) -> str:
|
||||||
|
"""Dumps runtime state useful for diagnosing sluggish web-app behavior."""
|
||||||
|
|
||||||
|
app = cthulhu.cthulhuApp
|
||||||
|
activeScript = cthulhu_state.activeScript
|
||||||
|
eventManager = getattr(app, "eventManager", None)
|
||||||
|
compositor = getattr(app, "compositorStateAdapter", None)
|
||||||
|
inputManager = getattr(eventManager, "_inputEventManager", None)
|
||||||
|
|
||||||
|
lines = [
|
||||||
|
f"timestamp={time.strftime('%Y-%m-%d %H:%M:%S')}",
|
||||||
|
f"default-script={self.__class__.__module__}.{self.__class__.__name__}",
|
||||||
|
f"active-script={activeScript.__class__.__module__}.{activeScript.__class__.__name__}" if activeScript else "active-script=None",
|
||||||
|
f"active-window={self._diagnostic_object_summary(cthulhu_state.activeWindow)}",
|
||||||
|
f"locus-of-focus={self._diagnostic_object_summary(cthulhu_state.locusOfFocus)}",
|
||||||
|
f"pending-self-hosted-focus={self._diagnostic_object_summary(getattr(cthulhu_state, 'pendingSelfHostedFocus', None))}",
|
||||||
|
]
|
||||||
|
|
||||||
|
if eventManager is not None:
|
||||||
|
eventQueue = getattr(eventManager, "_eventQueue", None)
|
||||||
|
try:
|
||||||
|
queueSize = eventQueue.qsize() if eventQueue is not None else "<unavailable>"
|
||||||
|
except Exception as error:
|
||||||
|
queueSize = f"<error: {error}>"
|
||||||
|
prioritizedEvent = getattr(eventManager, "_prioritizedEvent", None)
|
||||||
|
lines.extend([
|
||||||
|
f"event-manager-active={getattr(eventManager, '_active', '<unavailable>')}",
|
||||||
|
f"event-queue-size={queueSize}",
|
||||||
|
f"events-suspended={getattr(eventManager, '_eventsSuspended', '<unavailable>')}",
|
||||||
|
f"churn-suppressed={getattr(eventManager, '_churnSuppressed', '<unavailable>')}",
|
||||||
|
f"state-pause-atspi-churn={cthulhu_state.pauseAtspiChurn}",
|
||||||
|
f"prioritized-context-token={getattr(eventManager, '_prioritizedContextToken', None)}",
|
||||||
|
f"state-prioritized-context-token={cthulhu_state.prioritizedDesktopContextToken}",
|
||||||
|
f"prioritized-event-type={getattr(prioritizedEvent, 'type', None)}",
|
||||||
|
f"gidle-id={getattr(eventManager, '_gidleId', '<unavailable>')}",
|
||||||
|
f"prioritized-idle-id={getattr(eventManager, '_prioritizedIdleId', '<unavailable>')}",
|
||||||
|
])
|
||||||
|
|
||||||
|
if inputManager is not None:
|
||||||
|
lines.extend([
|
||||||
|
f"key-handling-active={getattr(eventManager, '_keyHandlingActive', '<unavailable>')}",
|
||||||
|
f"input-manager-device={getattr(inputManager, '_device', None)}",
|
||||||
|
f"input-manager-watcher={getattr(inputManager, '_keyWatcher', None)}",
|
||||||
|
])
|
||||||
|
|
||||||
|
if compositor is not None:
|
||||||
|
snapshot = self._diagnostic_callable_value(compositor, "get_snapshot")
|
||||||
|
lines.append(f"compositor-snapshot={snapshot}")
|
||||||
|
|
||||||
|
if activeScript is not None:
|
||||||
|
lines.extend([
|
||||||
|
f"active-script-focus-mode={self._diagnostic_callable_value(activeScript, 'inFocusMode')}",
|
||||||
|
f"active-script-focus-sticky={self._diagnostic_callable_value(activeScript, 'focusModeIsSticky')}",
|
||||||
|
f"active-script-browse-sticky={self._diagnostic_callable_value(activeScript, 'browseModeIsSticky')}",
|
||||||
|
f"active-script-structural-navigation={getattr(getattr(activeScript, 'structuralNavigation', None), 'enabled', '<unavailable>')}",
|
||||||
|
])
|
||||||
|
|
||||||
|
report = "\n".join(lines)
|
||||||
|
debug.printMessage(debug.LEVEL_INFO, "CTHULHU DIAGNOSTIC STATE:\n" + report, True)
|
||||||
|
if notify_user and script is not None:
|
||||||
|
script.presentMessage("Cthulhu diagnostic state written to debug log.")
|
||||||
|
return report
|
||||||
|
|
||||||
@dbus_service.command
|
@dbus_service.command
|
||||||
def sayAll(self, inputEvent, obj=None, offset=None, notify_user=True):
|
def sayAll(self, inputEvent, obj=None, offset=None, notify_user=True):
|
||||||
"""Speaks the entire document or text, starting from the current position."""
|
"""Speaks the entire document or text, starting from the current position."""
|
||||||
|
|||||||
@@ -462,8 +462,7 @@ class SettingsManager(object):
|
|||||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||||
|
|
||||||
def _enableAccessibility(self) -> bool:
|
def _enableAccessibility(self) -> bool:
|
||||||
"""Enables the GNOME accessibility flag. Users need to log out and
|
"""Enables the desktop accessibility bus flag when available.
|
||||||
then back in for this to take effect.
|
|
||||||
|
|
||||||
Returns True if an action was taken (i.e., accessibility was not
|
Returns True if an action was taken (i.e., accessibility was not
|
||||||
set prior to this call).
|
set prior to this call).
|
||||||
|
|||||||
@@ -1,532 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
#
|
|
||||||
# Copyright (c) 2025 Stormux
|
|
||||||
# Copyright (c) 2025 Igalia, S.L.
|
|
||||||
#
|
|
||||||
# This library is free software; you can redistribute it and/or
|
|
||||||
# modify it under the terms of the GNU Lesser General Public
|
|
||||||
# License as published by the Free Software Foundation; either
|
|
||||||
# version 2.1 of the License, or (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This library is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
# Lesser General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Lesser General Public
|
|
||||||
# License along with this library; if not, write to the
|
|
||||||
# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
|
|
||||||
# Boston MA 02110-1301 USA.
|
|
||||||
|
|
||||||
"""Enhanced speech settings management for D-Bus remote controller."""
|
|
||||||
|
|
||||||
__id__ = "$Id$"
|
|
||||||
__version__ = "$Revision$"
|
|
||||||
__date__ = "$Date$"
|
|
||||||
__copyright__ = "Copyright (c) 2025 Stormux"
|
|
||||||
__license__ = "LGPL"
|
|
||||||
|
|
||||||
from . import cthulhu_state
|
|
||||||
from . import debug
|
|
||||||
from . import dbus_service
|
|
||||||
from . import messages
|
|
||||||
from . import settings
|
|
||||||
from . import settings_manager
|
|
||||||
|
|
||||||
class SpeechDBusManager:
|
|
||||||
"""Enhanced speech settings for D-Bus remote control."""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
"""Initialize the speech D-Bus manager."""
|
|
||||||
self._settings_manager = settings_manager.getManager()
|
|
||||||
|
|
||||||
@dbus_service.getter
|
|
||||||
def get_verbosity_level(self) -> str:
|
|
||||||
"""Returns the current speech verbosity level."""
|
|
||||||
|
|
||||||
level = self._settings_manager.getSetting("speechVerbosityLevel")
|
|
||||||
if level == settings.VERBOSITY_LEVEL_BRIEF:
|
|
||||||
return "brief"
|
|
||||||
else:
|
|
||||||
return "verbose"
|
|
||||||
|
|
||||||
@dbus_service.setter
|
|
||||||
def set_verbosity_level(self, value: str) -> bool:
|
|
||||||
"""Sets the speech verbosity level."""
|
|
||||||
|
|
||||||
if value.lower() == "brief":
|
|
||||||
setting_value = settings.VERBOSITY_LEVEL_BRIEF
|
|
||||||
elif value.lower() == "verbose":
|
|
||||||
setting_value = settings.VERBOSITY_LEVEL_VERBOSE
|
|
||||||
else:
|
|
||||||
msg = f"SPEECH DBUS MANAGER: Invalid verbosity level: {value}"
|
|
||||||
debug.printMessage(debug.LEVEL_WARNING, msg, True)
|
|
||||||
return False
|
|
||||||
|
|
||||||
msg = f"SPEECH DBUS MANAGER: Setting verbosity level to {value}."
|
|
||||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
|
||||||
self._settings_manager.setSetting("speechVerbosityLevel", setting_value)
|
|
||||||
return True
|
|
||||||
|
|
||||||
@dbus_service.getter
|
|
||||||
def get_capitalization_style(self) -> str:
|
|
||||||
"""Returns the current capitalization style."""
|
|
||||||
|
|
||||||
style = self._settings_manager.getSetting("capitalizationStyle")
|
|
||||||
if style == settings.CAPITALIZATION_STYLE_NONE:
|
|
||||||
return "none"
|
|
||||||
elif style == settings.CAPITALIZATION_STYLE_SPELL:
|
|
||||||
return "spell"
|
|
||||||
elif style == settings.CAPITALIZATION_STYLE_ICON:
|
|
||||||
return "icon"
|
|
||||||
else:
|
|
||||||
return "none"
|
|
||||||
|
|
||||||
@dbus_service.setter
|
|
||||||
def set_capitalization_style(self, value: str) -> bool:
|
|
||||||
"""Sets the capitalization style."""
|
|
||||||
|
|
||||||
value_lower = value.lower()
|
|
||||||
if value_lower == "none":
|
|
||||||
setting_value = settings.CAPITALIZATION_STYLE_NONE
|
|
||||||
elif value_lower == "spell":
|
|
||||||
setting_value = settings.CAPITALIZATION_STYLE_SPELL
|
|
||||||
elif value_lower == "icon":
|
|
||||||
setting_value = settings.CAPITALIZATION_STYLE_ICON
|
|
||||||
else:
|
|
||||||
msg = f"SPEECH DBUS MANAGER: Invalid capitalization style: {value}"
|
|
||||||
debug.printMessage(debug.LEVEL_WARNING, msg, True)
|
|
||||||
return False
|
|
||||||
|
|
||||||
msg = f"SPEECH DBUS MANAGER: Setting capitalization style to {value}."
|
|
||||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
|
||||||
self._settings_manager.setSetting("capitalizationStyle", setting_value)
|
|
||||||
return True
|
|
||||||
|
|
||||||
@dbus_service.getter
|
|
||||||
def get_punctuation_level(self) -> str:
|
|
||||||
"""Returns the current punctuation level."""
|
|
||||||
|
|
||||||
level = self._settings_manager.getSetting("verbalizePunctuationStyle")
|
|
||||||
if level == settings.PUNCTUATION_STYLE_NONE:
|
|
||||||
return "none"
|
|
||||||
elif level == settings.PUNCTUATION_STYLE_SOME:
|
|
||||||
return "some"
|
|
||||||
elif level == settings.PUNCTUATION_STYLE_MOST:
|
|
||||||
return "most"
|
|
||||||
elif level == settings.PUNCTUATION_STYLE_ALL:
|
|
||||||
return "all"
|
|
||||||
else:
|
|
||||||
return "some"
|
|
||||||
|
|
||||||
@dbus_service.setter
|
|
||||||
def set_punctuation_level(self, value: str) -> bool:
|
|
||||||
"""Sets the punctuation level."""
|
|
||||||
|
|
||||||
value_lower = value.lower()
|
|
||||||
if value_lower == "none":
|
|
||||||
setting_value = settings.PUNCTUATION_STYLE_NONE
|
|
||||||
elif value_lower == "some":
|
|
||||||
setting_value = settings.PUNCTUATION_STYLE_SOME
|
|
||||||
elif value_lower == "most":
|
|
||||||
setting_value = settings.PUNCTUATION_STYLE_MOST
|
|
||||||
elif value_lower == "all":
|
|
||||||
setting_value = settings.PUNCTUATION_STYLE_ALL
|
|
||||||
else:
|
|
||||||
msg = f"SPEECH DBUS MANAGER: Invalid punctuation level: {value}"
|
|
||||||
debug.printMessage(debug.LEVEL_WARNING, msg, True)
|
|
||||||
return False
|
|
||||||
|
|
||||||
msg = f"SPEECH DBUS MANAGER: Setting punctuation level to {value}."
|
|
||||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
|
||||||
self._settings_manager.setSetting("verbalizePunctuationStyle", setting_value)
|
|
||||||
return True
|
|
||||||
|
|
||||||
@dbus_service.getter
|
|
||||||
def get_speak_numbers_as_digits(self) -> bool:
|
|
||||||
"""Returns whether numbers are spoken as digits."""
|
|
||||||
|
|
||||||
return self._settings_manager.getSetting("speakNumbersAsDigits")
|
|
||||||
|
|
||||||
@dbus_service.setter
|
|
||||||
def set_speak_numbers_as_digits(self, value: bool) -> bool:
|
|
||||||
"""Sets whether numbers are spoken as digits."""
|
|
||||||
|
|
||||||
msg = f"SPEECH DBUS MANAGER: Setting speak numbers as digits to {value}."
|
|
||||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
|
||||||
self._settings_manager.setSetting("speakNumbersAsDigits", value)
|
|
||||||
return True
|
|
||||||
|
|
||||||
@dbus_service.getter
|
|
||||||
def get_speech_is_muted(self) -> bool:
|
|
||||||
"""Returns whether speech output is temporarily muted."""
|
|
||||||
|
|
||||||
return self._settings_manager.getSetting("silenceSpeech")
|
|
||||||
|
|
||||||
@dbus_service.setter
|
|
||||||
def set_speech_is_muted(self, value: bool) -> bool:
|
|
||||||
"""Sets whether speech output is temporarily muted."""
|
|
||||||
|
|
||||||
msg = f"SPEECH DBUS MANAGER: Setting speech muted to {value}."
|
|
||||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
|
||||||
self._settings_manager.setSetting("silenceSpeech", value)
|
|
||||||
return True
|
|
||||||
|
|
||||||
@dbus_service.getter
|
|
||||||
def get_only_speak_displayed_text(self) -> bool:
|
|
||||||
"""Returns whether only displayed text should be spoken."""
|
|
||||||
|
|
||||||
return self._settings_manager.getSetting("onlySpeakDisplayedText")
|
|
||||||
|
|
||||||
@dbus_service.setter
|
|
||||||
def set_only_speak_displayed_text(self, value: bool) -> bool:
|
|
||||||
"""Sets whether only displayed text should be spoken."""
|
|
||||||
|
|
||||||
msg = f"SPEECH DBUS MANAGER: Setting only speak displayed text to {value}."
|
|
||||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
|
||||||
self._settings_manager.setSetting("onlySpeakDisplayedText", value)
|
|
||||||
return True
|
|
||||||
|
|
||||||
@dbus_service.getter
|
|
||||||
def get_speak_indentation_and_justification(self) -> bool:
|
|
||||||
"""Returns whether speaking of indentation and justification is enabled."""
|
|
||||||
|
|
||||||
return self._settings_manager.getSetting("enableSpeechIndentation")
|
|
||||||
|
|
||||||
def _sync_indentation_presentation_mode(self, enable_speech):
|
|
||||||
mode = self._settings_manager.getSetting("indentationPresentationMode") \
|
|
||||||
or settings.indentationPresentationMode
|
|
||||||
if enable_speech:
|
|
||||||
if mode == settings.INDENTATION_PRESENTATION_OFF:
|
|
||||||
mode = settings.INDENTATION_PRESENTATION_SPEECH
|
|
||||||
elif mode == settings.INDENTATION_PRESENTATION_BEEPS:
|
|
||||||
mode = settings.INDENTATION_PRESENTATION_SPEECH_AND_BEEPS
|
|
||||||
else:
|
|
||||||
if mode == settings.INDENTATION_PRESENTATION_SPEECH:
|
|
||||||
mode = settings.INDENTATION_PRESENTATION_OFF
|
|
||||||
elif mode == settings.INDENTATION_PRESENTATION_SPEECH_AND_BEEPS:
|
|
||||||
mode = settings.INDENTATION_PRESENTATION_BEEPS
|
|
||||||
|
|
||||||
self._settings_manager.setSetting("indentationPresentationMode", mode)
|
|
||||||
|
|
||||||
@dbus_service.setter
|
|
||||||
def set_speak_indentation_and_justification(self, value: bool) -> bool:
|
|
||||||
"""Sets whether speaking of indentation and justification is enabled."""
|
|
||||||
|
|
||||||
msg = f"SPEECH DBUS MANAGER: Setting speak indentation and justification to {value}."
|
|
||||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
|
||||||
self._settings_manager.setSetting("enableSpeechIndentation", value)
|
|
||||||
self._sync_indentation_presentation_mode(value)
|
|
||||||
return True
|
|
||||||
|
|
||||||
@dbus_service.command
|
|
||||||
def toggle_speech(self, script=None, event=None):
|
|
||||||
"""Toggles speech on and off."""
|
|
||||||
|
|
||||||
tokens = ["SPEECH DBUS MANAGER: toggle_speech. Script:", script, "Event:", event]
|
|
||||||
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
|
||||||
|
|
||||||
if script is not None:
|
|
||||||
script.presentationInterrupt()
|
|
||||||
|
|
||||||
if self.get_speech_is_muted():
|
|
||||||
self.set_speech_is_muted(False)
|
|
||||||
if script is not None:
|
|
||||||
script.presentMessage(messages.SPEECH_ENABLED)
|
|
||||||
elif not self._settings_manager.getSetting("enableSpeech"):
|
|
||||||
self._settings_manager.setSetting("enableSpeech", True)
|
|
||||||
if script is not None:
|
|
||||||
script.presentMessage(messages.SPEECH_ENABLED)
|
|
||||||
else:
|
|
||||||
if script is not None:
|
|
||||||
script.presentMessage(messages.SPEECH_DISABLED)
|
|
||||||
self.set_speech_is_muted(True)
|
|
||||||
|
|
||||||
@dbus_service.command
|
|
||||||
def toggle_verbosity(self, script=None, event=None):
|
|
||||||
"""Toggles speech verbosity level between verbose and brief."""
|
|
||||||
|
|
||||||
tokens = ["SPEECH DBUS MANAGER: toggle_verbosity. Script:", script, "Event:", event]
|
|
||||||
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
|
||||||
|
|
||||||
current_level = self._settings_manager.getSetting("speechVerbosityLevel")
|
|
||||||
if current_level == settings.VERBOSITY_LEVEL_BRIEF:
|
|
||||||
if script is not None:
|
|
||||||
script.presentMessage(messages.SPEECH_VERBOSITY_VERBOSE)
|
|
||||||
self._settings_manager.setSetting("speechVerbosityLevel", settings.VERBOSITY_LEVEL_VERBOSE)
|
|
||||||
else:
|
|
||||||
if script is not None:
|
|
||||||
script.presentMessage(messages.SPEECH_VERBOSITY_BRIEF)
|
|
||||||
self._settings_manager.setSetting("speechVerbosityLevel", settings.VERBOSITY_LEVEL_BRIEF)
|
|
||||||
|
|
||||||
@dbus_service.command
|
|
||||||
def change_number_style(self, script=None, event=None):
|
|
||||||
"""Changes spoken number style between digits and words."""
|
|
||||||
|
|
||||||
tokens = ["SPEECH DBUS MANAGER: change_number_style. Script:", script, "Event:", event]
|
|
||||||
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
|
||||||
|
|
||||||
speak_digits = self.get_speak_numbers_as_digits()
|
|
||||||
if speak_digits:
|
|
||||||
brief = messages.NUMBER_STYLE_WORDS_BRIEF
|
|
||||||
full = messages.NUMBER_STYLE_WORDS_FULL
|
|
||||||
else:
|
|
||||||
brief = messages.NUMBER_STYLE_DIGITS_BRIEF
|
|
||||||
full = messages.NUMBER_STYLE_DIGITS_FULL
|
|
||||||
|
|
||||||
self.set_speak_numbers_as_digits(not speak_digits)
|
|
||||||
if script is not None:
|
|
||||||
script.presentMessage(full, brief)
|
|
||||||
|
|
||||||
@dbus_service.command
|
|
||||||
def say_all(self, script=None, event=None):
|
|
||||||
"""Speaks the entire document or text, starting from the current position."""
|
|
||||||
|
|
||||||
tokens = ["SPEECH DBUS MANAGER: say_all. Script:", script, "Event:", event]
|
|
||||||
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
|
||||||
|
|
||||||
# Use the current active script if not provided
|
|
||||||
if script is None:
|
|
||||||
script = cthulhu_state.activeScript
|
|
||||||
|
|
||||||
if script is None:
|
|
||||||
msg = "SPEECH DBUS MANAGER: No active script available for Say All"
|
|
||||||
debug.printMessage(debug.LEVEL_WARNING, msg, True)
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Call the script's Say All method
|
|
||||||
try:
|
|
||||||
script.sayAll(event, notify_user=False)
|
|
||||||
return True
|
|
||||||
except Exception as e:
|
|
||||||
msg = f"SPEECH DBUS MANAGER: Error during Say All: {e}"
|
|
||||||
debug.printMessage(debug.LEVEL_SEVERE, msg, True)
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Key Echo Controls
|
|
||||||
@dbus_service.getter
|
|
||||||
def get_key_echo_enabled(self) -> bool:
|
|
||||||
"""Returns whether echo of key presses is enabled."""
|
|
||||||
|
|
||||||
return self._settings_manager.getSetting("enableKeyEcho")
|
|
||||||
|
|
||||||
@dbus_service.setter
|
|
||||||
def set_key_echo_enabled(self, value: bool) -> bool:
|
|
||||||
"""Sets whether echo of key presses is enabled."""
|
|
||||||
|
|
||||||
msg = f"SPEECH DBUS MANAGER: Setting enable key echo to {value}."
|
|
||||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
|
||||||
self._settings_manager.setSetting("enableKeyEcho", value)
|
|
||||||
return True
|
|
||||||
|
|
||||||
@dbus_service.getter
|
|
||||||
def get_character_echo_enabled(self) -> bool:
|
|
||||||
"""Returns whether echo of inserted characters is enabled."""
|
|
||||||
|
|
||||||
return self._settings_manager.getSetting("enableEchoByCharacter")
|
|
||||||
|
|
||||||
@dbus_service.setter
|
|
||||||
def set_character_echo_enabled(self, value: bool) -> bool:
|
|
||||||
"""Sets whether echo of inserted characters is enabled."""
|
|
||||||
|
|
||||||
msg = f"SPEECH DBUS MANAGER: Setting enable character echo to {value}."
|
|
||||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
|
||||||
self._settings_manager.setSetting("enableEchoByCharacter", value)
|
|
||||||
return True
|
|
||||||
|
|
||||||
@dbus_service.getter
|
|
||||||
def get_word_echo_enabled(self) -> bool:
|
|
||||||
"""Returns whether word echo is enabled."""
|
|
||||||
|
|
||||||
return self._settings_manager.getSetting("enableEchoByWord")
|
|
||||||
|
|
||||||
@dbus_service.setter
|
|
||||||
def set_word_echo_enabled(self, value: bool) -> bool:
|
|
||||||
"""Sets whether word echo is enabled."""
|
|
||||||
|
|
||||||
msg = f"SPEECH DBUS MANAGER: Setting enable word echo to {value}."
|
|
||||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
|
||||||
self._settings_manager.setSetting("enableEchoByWord", value)
|
|
||||||
return True
|
|
||||||
|
|
||||||
@dbus_service.getter
|
|
||||||
def get_sentence_echo_enabled(self) -> bool:
|
|
||||||
"""Returns whether sentence echo is enabled."""
|
|
||||||
|
|
||||||
return self._settings_manager.getSetting("enableEchoBySentence")
|
|
||||||
|
|
||||||
@dbus_service.setter
|
|
||||||
def set_sentence_echo_enabled(self, value: bool) -> bool:
|
|
||||||
"""Sets whether sentence echo is enabled."""
|
|
||||||
|
|
||||||
msg = f"SPEECH DBUS MANAGER: Setting enable sentence echo to {value}."
|
|
||||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
|
||||||
self._settings_manager.setSetting("enableEchoBySentence", value)
|
|
||||||
return True
|
|
||||||
|
|
||||||
@dbus_service.getter
|
|
||||||
def get_alphabetic_keys_enabled(self) -> bool:
|
|
||||||
"""Returns whether alphabetic keys will be echoed when key echo is enabled."""
|
|
||||||
|
|
||||||
return self._settings_manager.getSetting("enableAlphabeticKeys")
|
|
||||||
|
|
||||||
@dbus_service.setter
|
|
||||||
def set_alphabetic_keys_enabled(self, value: bool) -> bool:
|
|
||||||
"""Sets whether alphabetic keys will be echoed when key echo is enabled."""
|
|
||||||
|
|
||||||
msg = f"SPEECH DBUS MANAGER: Setting enable alphabetic keys to {value}."
|
|
||||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
|
||||||
self._settings_manager.setSetting("enableAlphabeticKeys", value)
|
|
||||||
return True
|
|
||||||
|
|
||||||
@dbus_service.getter
|
|
||||||
def get_numeric_keys_enabled(self) -> bool:
|
|
||||||
"""Returns whether numeric keys will be echoed when key echo is enabled."""
|
|
||||||
|
|
||||||
return self._settings_manager.getSetting("enableNumericKeys")
|
|
||||||
|
|
||||||
@dbus_service.setter
|
|
||||||
def set_numeric_keys_enabled(self, value: bool) -> bool:
|
|
||||||
"""Sets whether numeric keys will be echoed when key echo is enabled."""
|
|
||||||
|
|
||||||
msg = f"SPEECH DBUS MANAGER: Setting enable numeric keys to {value}."
|
|
||||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
|
||||||
self._settings_manager.setSetting("enableNumericKeys", value)
|
|
||||||
return True
|
|
||||||
|
|
||||||
@dbus_service.getter
|
|
||||||
def get_punctuation_keys_enabled(self) -> bool:
|
|
||||||
"""Returns whether punctuation keys will be echoed when key echo is enabled."""
|
|
||||||
|
|
||||||
return self._settings_manager.getSetting("enablePunctuationKeys")
|
|
||||||
|
|
||||||
@dbus_service.setter
|
|
||||||
def set_punctuation_keys_enabled(self, value: bool) -> bool:
|
|
||||||
"""Sets whether punctuation keys will be echoed when key echo is enabled."""
|
|
||||||
|
|
||||||
msg = f"SPEECH DBUS MANAGER: Setting enable punctuation keys to {value}."
|
|
||||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
|
||||||
self._settings_manager.setSetting("enablePunctuationKeys", value)
|
|
||||||
return True
|
|
||||||
|
|
||||||
@dbus_service.getter
|
|
||||||
def get_space_enabled(self) -> bool:
|
|
||||||
"""Returns whether space key will be echoed when key echo is enabled."""
|
|
||||||
|
|
||||||
return self._settings_manager.getSetting("enableSpace")
|
|
||||||
|
|
||||||
@dbus_service.setter
|
|
||||||
def set_space_enabled(self, value: bool) -> bool:
|
|
||||||
"""Sets whether space key will be echoed when key echo is enabled."""
|
|
||||||
|
|
||||||
msg = f"SPEECH DBUS MANAGER: Setting enable space to {value}."
|
|
||||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
|
||||||
self._settings_manager.setSetting("enableSpace", value)
|
|
||||||
return True
|
|
||||||
|
|
||||||
@dbus_service.getter
|
|
||||||
def get_modifier_keys_enabled(self) -> bool:
|
|
||||||
"""Returns whether modifier keys will be echoed when key echo is enabled."""
|
|
||||||
|
|
||||||
return self._settings_manager.getSetting("enableModifierKeys")
|
|
||||||
|
|
||||||
@dbus_service.setter
|
|
||||||
def set_modifier_keys_enabled(self, value: bool) -> bool:
|
|
||||||
"""Sets whether modifier keys will be echoed when key echo is enabled."""
|
|
||||||
|
|
||||||
msg = f"SPEECH DBUS MANAGER: Setting enable modifier keys to {value}."
|
|
||||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
|
||||||
self._settings_manager.setSetting("enableModifierKeys", value)
|
|
||||||
return True
|
|
||||||
|
|
||||||
@dbus_service.getter
|
|
||||||
def get_function_keys_enabled(self) -> bool:
|
|
||||||
"""Returns whether function keys will be echoed when key echo is enabled."""
|
|
||||||
|
|
||||||
return self._settings_manager.getSetting("enableFunctionKeys")
|
|
||||||
|
|
||||||
@dbus_service.setter
|
|
||||||
def set_function_keys_enabled(self, value: bool) -> bool:
|
|
||||||
"""Sets whether function keys will be echoed when key echo is enabled."""
|
|
||||||
|
|
||||||
msg = f"SPEECH DBUS MANAGER: Setting enable function keys to {value}."
|
|
||||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
|
||||||
self._settings_manager.setSetting("enableFunctionKeys", value)
|
|
||||||
return True
|
|
||||||
|
|
||||||
@dbus_service.getter
|
|
||||||
def get_action_keys_enabled(self) -> bool:
|
|
||||||
"""Returns whether action keys will be echoed when key echo is enabled."""
|
|
||||||
|
|
||||||
return self._settings_manager.getSetting("enableActionKeys")
|
|
||||||
|
|
||||||
@dbus_service.setter
|
|
||||||
def set_action_keys_enabled(self, value: bool) -> bool:
|
|
||||||
"""Sets whether action keys will be echoed when key echo is enabled."""
|
|
||||||
|
|
||||||
msg = f"SPEECH DBUS MANAGER: Setting enable action keys to {value}."
|
|
||||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
|
||||||
self._settings_manager.setSetting("enableActionKeys", value)
|
|
||||||
return True
|
|
||||||
|
|
||||||
@dbus_service.getter
|
|
||||||
def get_navigation_keys_enabled(self) -> bool:
|
|
||||||
"""Returns whether navigation keys will be echoed when key echo is enabled."""
|
|
||||||
|
|
||||||
return self._settings_manager.getSetting("enableNavigationKeys")
|
|
||||||
|
|
||||||
@dbus_service.setter
|
|
||||||
def set_navigation_keys_enabled(self, value: bool) -> bool:
|
|
||||||
"""Sets whether navigation keys will be echoed when key echo is enabled."""
|
|
||||||
|
|
||||||
msg = f"SPEECH DBUS MANAGER: Setting enable navigation keys to {value}."
|
|
||||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
|
||||||
self._settings_manager.setSetting("enableNavigationKeys", value)
|
|
||||||
return True
|
|
||||||
|
|
||||||
@dbus_service.command
|
|
||||||
def cycle_key_echo(self, script=None, event=None):
|
|
||||||
"""Cycle through the key echo levels."""
|
|
||||||
|
|
||||||
tokens = ["SPEECH DBUS MANAGER: cycle_key_echo. Script:", script, "Event:", event]
|
|
||||||
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
|
||||||
|
|
||||||
# Get current settings
|
|
||||||
key = self._settings_manager.getSetting("enableKeyEcho")
|
|
||||||
word = self._settings_manager.getSetting("enableEchoByWord")
|
|
||||||
sentence = self._settings_manager.getSetting("enableEchoBySentence")
|
|
||||||
|
|
||||||
# Cycle through the combinations: none -> key -> word -> sentence -> all -> none
|
|
||||||
if not key and not word and not sentence:
|
|
||||||
# None -> Key only
|
|
||||||
new_key, new_word, new_sentence = True, False, False
|
|
||||||
brief = messages.KEY_ECHO_KEY_BRIEF
|
|
||||||
full = messages.KEY_ECHO_KEY_FULL
|
|
||||||
elif key and not word and not sentence:
|
|
||||||
# Key -> Word
|
|
||||||
new_key, new_word, new_sentence = False, True, False
|
|
||||||
brief = messages.KEY_ECHO_WORD_BRIEF
|
|
||||||
full = messages.KEY_ECHO_WORD_FULL
|
|
||||||
elif not key and word and not sentence:
|
|
||||||
# Word -> Sentence
|
|
||||||
new_key, new_word, new_sentence = False, False, True
|
|
||||||
brief = messages.KEY_ECHO_SENTENCE_BRIEF
|
|
||||||
full = messages.KEY_ECHO_SENTENCE_FULL
|
|
||||||
elif not key and not word and sentence:
|
|
||||||
# Sentence -> All
|
|
||||||
new_key, new_word, new_sentence = True, True, True
|
|
||||||
brief = messages.KEY_ECHO_KEY_AND_WORD_BRIEF
|
|
||||||
full = messages.KEY_ECHO_KEY_AND_WORD_FULL
|
|
||||||
else:
|
|
||||||
# All -> None
|
|
||||||
new_key, new_word, new_sentence = False, False, False
|
|
||||||
brief = messages.KEY_ECHO_NONE_BRIEF
|
|
||||||
full = messages.KEY_ECHO_NONE_FULL
|
|
||||||
|
|
||||||
# Apply new settings
|
|
||||||
self._settings_manager.setSetting("enableKeyEcho", new_key)
|
|
||||||
self._settings_manager.setSetting("enableEchoByWord", new_word)
|
|
||||||
self._settings_manager.setSetting("enableEchoBySentence", new_sentence)
|
|
||||||
|
|
||||||
if script is not None:
|
|
||||||
script.presentMessage(full, brief)
|
|
||||||
@@ -717,7 +717,7 @@ class SpeechGenerator(generator.Generator):
|
|||||||
and AXUtilities.is_selected(obj):
|
and AXUtilities.is_selected(obj):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
# egg-list-box, e.g. privacy panel in gnome-control-center
|
# egg-list-box-style containers can expose selected panels as list items.
|
||||||
if AXUtilities.is_list_box(parent):
|
if AXUtilities.is_list_box(parent):
|
||||||
doNotPresent.append(AXObject.get_role(obj))
|
doNotPresent.append(AXObject.get_role(obj))
|
||||||
|
|
||||||
|
|||||||
@@ -86,6 +86,25 @@ class LegacyTomlSchemaMigrationTests(unittest.TestCase):
|
|||||||
self.assertNotIn("format-version = 2", savedSettings)
|
self.assertNotIn("format-version = 2", savedSettings)
|
||||||
self.assertNotIn("[profiles.default.metadata]", savedSettings)
|
self.assertNotIn("[profiles.default.metadata]", savedSettings)
|
||||||
|
|
||||||
|
def test_legacy_profile_keybindings_are_preserved(self):
|
||||||
|
legacySettings = LEGACY_SETTINGS.replace(
|
||||||
|
'desktop-modifier-keys = ["Insert", "KP_Insert"]',
|
||||||
|
'desktop-modifier-keys = ["Insert", "KP_Insert"]\ncustom-binding = "kb:cthulhu+x"',
|
||||||
|
)
|
||||||
|
|
||||||
|
with tempfile.TemporaryDirectory() as tempDir:
|
||||||
|
Path(tempDir, "user-settings.toml").write_text(
|
||||||
|
legacySettings,
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
|
backend = Backend(tempDir)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
backend.getKeybindings("default"),
|
||||||
|
{"custom-binding": "kb:cthulhu+x"},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
Reference in New Issue
Block a user