Fixed sleep mode after a long and fierce battle.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
# Maintainer: Storm Dragon <storm_dragon@stormux.org>
|
||||
|
||||
pkgname=cthulhu
|
||||
pkgver=2025.08.11
|
||||
pkgver=2025.08.12
|
||||
pkgrel=1
|
||||
pkgdesc="Desktop-agnostic screen reader with plugin system, forked from Orca"
|
||||
url="https://git.stormux.org/storm/cthulhu"
|
||||
|
||||
@@ -1576,6 +1576,16 @@ def displayRegions(regionInfo, flashTime=0):
|
||||
comes along or the user presses a cursor routing key.
|
||||
"""
|
||||
|
||||
# Block braille for applications in sleep mode
|
||||
from . import cthulhu_state
|
||||
from . import sleep_mode_manager
|
||||
if cthulhu_state.activeScript and hasattr(cthulhu_state.activeScript, 'app'):
|
||||
sleepModeManager = sleep_mode_manager.getManager()
|
||||
if sleepModeManager.isActiveForApp(cthulhu_state.activeScript.app):
|
||||
from . import debug
|
||||
debug.printMessage(debug.LEVEL_INFO, "BRAILLE: Blocked by sleep mode", True)
|
||||
return
|
||||
|
||||
_initFlash(flashTime)
|
||||
regions = regionInfo[0]
|
||||
focusedRegion = regionInfo[1]
|
||||
@@ -1602,6 +1612,16 @@ def displayMessage(message, cursor=-1, flashTime=0):
|
||||
comes along or the user presses a cursor routing key.
|
||||
"""
|
||||
|
||||
# Block braille for applications in sleep mode
|
||||
from . import cthulhu_state
|
||||
from . import sleep_mode_manager
|
||||
if cthulhu_state.activeScript and hasattr(cthulhu_state.activeScript, 'app'):
|
||||
sleepModeManager = sleep_mode_manager.getManager()
|
||||
if sleepModeManager.isActiveForApp(cthulhu_state.activeScript.app):
|
||||
from . import debug
|
||||
debug.printMessage(debug.LEVEL_INFO, f"BRAILLE: Blocked by sleep mode: '{message}'", True)
|
||||
return
|
||||
|
||||
_initFlash(flashTime)
|
||||
clear()
|
||||
region = Region(message, cursor)
|
||||
|
||||
@@ -44,6 +44,7 @@ CTHULHU_SHIFT_MODIFIER_MASK = keybindings.CTHULHU_SHIFT_MODIFIER_MASK
|
||||
CTHULHU_CTRL_MODIFIER_MASK = keybindings.CTHULHU_CTRL_MODIFIER_MASK
|
||||
CTHULHU_ALT_MODIFIER_MASK = keybindings.CTHULHU_ALT_MODIFIER_MASK
|
||||
CTHULHU_CTRL_ALT_MODIFIER_MASK = keybindings.CTHULHU_CTRL_ALT_MODIFIER_MASK
|
||||
SHIFT_MODIFIER_MASK = keybindings.SHIFT_MODIFIER_MASK
|
||||
SHIFT_ALT_MODIFIER_MASK = keybindings.SHIFT_ALT_MODIFIER_MASK
|
||||
|
||||
keymap = (
|
||||
@@ -57,7 +58,7 @@ keymap = (
|
||||
("BackSpace", defaultModifierMask, CTHULHU_MODIFIER_MASK,
|
||||
"bypassNextCommandHandler"),
|
||||
|
||||
("q", defaultModifierMask, CTHULHU_CTRL_ALT_MODIFIER_MASK | CTHULHU_SHIFT_MODIFIER_MASK,
|
||||
("q", defaultModifierMask, CTHULHU_CTRL_MODIFIER_MASK | SHIFT_MODIFIER_MASK,
|
||||
"toggleSleepModeHandler"),
|
||||
|
||||
("q", defaultModifierMask, CTHULHU_MODIFIER_MASK,
|
||||
|
||||
@@ -23,5 +23,5 @@
|
||||
# Fork of Orca Screen Reader (GNOME)
|
||||
# Original source: https://gitlab.gnome.org/GNOME/orca
|
||||
|
||||
version = "2025.08.12"
|
||||
version = "2025.08.14"
|
||||
codeName = "testing"
|
||||
|
||||
@@ -71,6 +71,7 @@ cthulhu_python_sources = files([
|
||||
'settings.py',
|
||||
'settings_manager.py',
|
||||
'signal_manager.py',
|
||||
'sleep_mode_manager.py',
|
||||
'sound.py',
|
||||
'sound_generator.py',
|
||||
'speech_and_verbosity_manager.py',
|
||||
|
||||
@@ -68,6 +68,7 @@ from . import script_manager
|
||||
from . import script_utilities
|
||||
from . import settings
|
||||
from . import settings_manager
|
||||
from . import sleep_mode_manager
|
||||
from . import sound_generator
|
||||
from . import speech_and_verbosity_manager
|
||||
from . import speech_generator
|
||||
@@ -638,5 +639,9 @@ class Script:
|
||||
"""Called when this script is deactivated."""
|
||||
pass
|
||||
|
||||
def getSleepModeManager(self):
|
||||
"""Returns the sleep mode manager for this script."""
|
||||
return sleep_mode_manager.getManager()
|
||||
|
||||
def getTransferableAttributes(self):
|
||||
return {}
|
||||
|
||||
@@ -44,6 +44,7 @@ class ScriptManager:
|
||||
self.appScripts = {}
|
||||
self.toolkitScripts = {}
|
||||
self.customScripts = {}
|
||||
self._sleepModeScripts = {}
|
||||
self._appModules = apps.__all__
|
||||
self._toolkitModules = toolkits.__all__
|
||||
self._defaultScript = None
|
||||
@@ -300,6 +301,18 @@ class ScriptManager:
|
||||
|
||||
return appScript
|
||||
|
||||
def getOrCreateSleepModeScript(self, app):
|
||||
"""Gets or creates the sleep mode script."""
|
||||
script = self._sleepModeScripts.get(app)
|
||||
if script is not None:
|
||||
return script
|
||||
|
||||
# Import sleepmode dynamically to avoid circular imports
|
||||
from .scripts import sleepmode
|
||||
script = sleepmode.Script(app)
|
||||
self._sleepModeScripts[app] = script
|
||||
return script
|
||||
|
||||
def setActiveScript(self, newScript, reason=None):
|
||||
"""Set the new active script.
|
||||
|
||||
|
||||
@@ -785,14 +785,9 @@ class Script(script.Script):
|
||||
|
||||
def toggleSleepMode(self, input_event=None):
|
||||
"""Toggles between sleep mode and regular mode."""
|
||||
script_manager = _scriptManager
|
||||
debug.printMessage(debug.LEVEL_INFO, f"SLEEP: Attempting to create sleepmode script for app: {self.app}", True)
|
||||
sleepScript = script_manager._newNamedScript(self.app, "sleepmode")
|
||||
debug.printMessage(debug.LEVEL_INFO, f"SLEEP: Result of _newNamedScript: {sleepScript}", True)
|
||||
if sleepScript:
|
||||
script_manager.setActiveScript(sleepScript, "Sleep mode toggled")
|
||||
else:
|
||||
self.presentMessage("Could not activate sleep mode")
|
||||
# Sleep mode is now handled by the sleep mode manager
|
||||
sleepModeManager = self.getSleepModeManager()
|
||||
sleepModeManager.toggleSleepMode(self)
|
||||
return True
|
||||
|
||||
def bypassNextCommand(self, inputEvent=None):
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Copyright (c) 2024 Stormux
|
||||
# Copyright (c) 2023 Igalia, S.L.
|
||||
# Author: Joanmarie Diggs <jdiggs@igalia.com>
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# Fork of Orca Screen Reader (GNOME)
|
||||
# Original source: https://gitlab.gnome.org/GNOME/orca
|
||||
|
||||
"""Braille Generator for Sleep Mode. Does nothing."""
|
||||
|
||||
__id__ = "$Id$"
|
||||
__version__ = "$Revision$"
|
||||
__date__ = "$Date$"
|
||||
__copyright__ = "Copyright (c) 2024 Stormux"
|
||||
__license__ = "LGPL"
|
||||
|
||||
import cthulhu.debug as debug
|
||||
import cthulhu.braille_generator as braille_generator
|
||||
|
||||
class BrailleGenerator(braille_generator.BrailleGenerator):
|
||||
"""Braille Generator for Sleep Mode. Does nothing."""
|
||||
|
||||
def generateBraille(self, obj, **args):
|
||||
"""Generates braille for the given object. Returns empty list in sleep mode."""
|
||||
msg = "SLEEP MODE BRAILLE GENERATOR: Generating nothing."
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
return []
|
||||
@@ -1,7 +1,9 @@
|
||||
sleepmode_python_sources = files([
|
||||
'__init__.py',
|
||||
'braille_generator.py',
|
||||
'script.py',
|
||||
'script_utilities.py',
|
||||
'speech_generator.py',
|
||||
])
|
||||
|
||||
python3.install_sources(
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Copyright (c) 2024 Stormux
|
||||
# Copyright (c) 2010-2012 The Orca Team
|
||||
# Copyright (c) 2012 Igalia, S.L.
|
||||
# Copyright (c) 2005-2010 Sun Microsystems Inc.
|
||||
# Copyright (c) 2023 Igalia, S.L.
|
||||
# Author: Joanmarie Diggs <jdiggs@igalia.com>
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
@@ -23,7 +22,12 @@
|
||||
# Fork of Orca Screen Reader (GNOME)
|
||||
# Original source: https://gitlab.gnome.org/GNOME/orca
|
||||
|
||||
"""Script for sleep mode where Cthulhu ignores events and commands."""
|
||||
"""Script for sleep mode where Cthulhu ignores events and commands.
|
||||
|
||||
This script is now a minimal implementation that relies on the global
|
||||
sleep mode flag in cthulhu_state.sleepMode to block speech and braille
|
||||
at the system level.
|
||||
"""
|
||||
|
||||
__id__ = "$Id$"
|
||||
__version__ = "$Revision$"
|
||||
@@ -31,195 +35,122 @@ __date__ = "$Date$"
|
||||
__copyright__ = "Copyright (c) 2024 Stormux"
|
||||
__license__ = "LGPL"
|
||||
|
||||
import cthulhu.debug as debug
|
||||
import cthulhu.scripts.default as default
|
||||
import cthulhu.input_event as input_event
|
||||
import cthulhu.keybindings as keybindings
|
||||
import cthulhu.messages as messages
|
||||
import cthulhu.script_manager as script_manager
|
||||
from cthulhu.ax_object import AXObject
|
||||
from cthulhu.ax_utilities import AXUtilities
|
||||
|
||||
_scriptManager = script_manager.getManager()
|
||||
import cthulhu.debug as debug
|
||||
import cthulhu.sleep_mode_manager as sleep_mode_manager
|
||||
|
||||
class Script(default.Script):
|
||||
"""The sleep-mode script."""
|
||||
"""The sleep mode script.
|
||||
|
||||
This script now relies on the global sleep mode flag for blocking
|
||||
speech and braille output at the system level. It only handles
|
||||
keybinding management and basic event blocking.
|
||||
"""
|
||||
|
||||
def __init__(self, app):
|
||||
super().__init__(app)
|
||||
self.presentIfInactive = True
|
||||
|
||||
def activate(self):
|
||||
"""Called when this script is activated."""
|
||||
tokens = ["SLEEP MODE: Activating script for", self.app]
|
||||
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
||||
|
||||
# Only keep the sleep mode toggle binding active
|
||||
self.removeKeyGrabs()
|
||||
self.addKeyGrabs()
|
||||
debug.printMessage(debug.LEVEL_INFO, "SLEEP MODE SCRIPT: Activating", True)
|
||||
super().activate()
|
||||
|
||||
# Present sleep mode status
|
||||
self.clearBraille()
|
||||
app_name = AXObject.get_name(self.app) if self.app else "unknown application"
|
||||
message = messages.SLEEP_MODE_ENABLED_FOR % app_name
|
||||
self.presentMessage(message)
|
||||
# Get the manager and add its bindings and handlers
|
||||
manager = sleep_mode_manager.getManager()
|
||||
managerBindings = manager.getBindings()
|
||||
if hasattr(managerBindings, 'keyBindings'):
|
||||
for binding in managerBindings.keyBindings:
|
||||
self.keyBindings.add(binding)
|
||||
self.inputEventHandlers.update(manager.getHandlers())
|
||||
|
||||
# Remove most key grabs except sleep mode toggle
|
||||
self.removeKeyGrabs()
|
||||
|
||||
debug.printMessage(debug.LEVEL_INFO, "SLEEP MODE SCRIPT: Activated successfully", True)
|
||||
|
||||
def deactivate(self):
|
||||
"""Called when this script is deactivated."""
|
||||
tokens = ["SLEEP MODE: De-activating script for", self.app]
|
||||
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
||||
self.removeKeyGrabs()
|
||||
|
||||
def getKeyBindings(self):
|
||||
"""Only provide the key binding needed to exit sleep mode."""
|
||||
keyBindings = keybindings.KeyBindings()
|
||||
|
||||
keyBindings.load([("q", keybindings.defaultModifierMask,
|
||||
keybindings.CTHULHU_CTRL_ALT_MODIFIER_MASK | keybindings.SHIFT_ALT_MODIFIER_MASK,
|
||||
"toggleSleepModeHandler")],
|
||||
self.inputEventHandlers)
|
||||
return keyBindings
|
||||
debug.printMessage(debug.LEVEL_INFO, "SLEEP MODE SCRIPT: Deactivating", True)
|
||||
|
||||
# Restore key grabs
|
||||
self.addKeyGrabs()
|
||||
|
||||
super().deactivate()
|
||||
|
||||
def setupInputEventHandlers(self):
|
||||
"""Sets up the input event handlers for sleep mode."""
|
||||
super().setupInputEventHandlers()
|
||||
self.inputEventHandlers["toggleSleepModeHandler"] = \
|
||||
input_event.InputEventHandler(
|
||||
Script.toggleSleepMode,
|
||||
"Toggles sleep mode on/off")
|
||||
def removeKeyGrabs(self):
|
||||
"""Remove key grabs except for sleep mode toggle."""
|
||||
|
||||
try:
|
||||
self.grab_ids = []
|
||||
for keyBinding in self.keyBindings:
|
||||
if hasattr(keyBinding, 'handler') and hasattr(keyBinding.handler, 'function'):
|
||||
if hasattr(keyBinding.handler.function, '__name__'):
|
||||
if 'toggleSleepMode' in keyBinding.handler.function.__name__:
|
||||
# Keep sleep mode toggle
|
||||
try:
|
||||
import cthulhu
|
||||
grab_id = cthulhu.addKeyGrab(keyBinding)
|
||||
if grab_id:
|
||||
self.grab_ids.append(grab_id)
|
||||
debug.printMessage(debug.LEVEL_INFO, f"SLEEP MODE: Kept sleep toggle key grab: {grab_id}", True)
|
||||
except Exception as e:
|
||||
debug.printMessage(debug.LEVEL_INFO, f"SLEEP MODE: Error keeping key grab: {e}", True)
|
||||
else:
|
||||
debug.printMessage(debug.LEVEL_INFO, f"SLEEP MODE: Skipping key grab for {keyBinding.handler.function.__name__}", True)
|
||||
|
||||
except Exception as e:
|
||||
debug.printMessage(debug.LEVEL_INFO, f"SLEEP MODE: Error in removeKeyGrabs: {e}", True)
|
||||
|
||||
def toggleSleepMode(self, input_event=None):
|
||||
"""Toggles between sleep mode and regular mode."""
|
||||
script_manager = _scriptManager
|
||||
script_manager.setActiveScript(script_manager.getDefaultScript(), "Sleep mode toggled")
|
||||
self.presentMessage(messages.SLEEP_MODE_DISABLED_FOR % AXObject.get_name(self.app))
|
||||
return True
|
||||
|
||||
def locusOfFocusChanged(self, event, oldLocusOfFocus, newLocusOfFocus):
|
||||
"""Handles changes of focus of interest to the script."""
|
||||
tokens = ["SLEEP MODE: focus changed from", oldLocusOfFocus, "to", newLocusOfFocus]
|
||||
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
||||
if oldLocusOfFocus is None and AXUtilities.is_application(AXObject.get_parent(newLocusOfFocus)):
|
||||
self.clearBraille()
|
||||
self.presentMessage(messages.SLEEP_MODE_ENABLED_FOR % AXObject.get_name(self.app))
|
||||
return
|
||||
|
||||
msg = "SLEEP MODE: Ignoring event."
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
|
||||
def presentKeyboardEvent(self, event):
|
||||
"""Prevents keyboard echo in sleep mode."""
|
||||
msg = "SLEEP MODE: Not presenting keyboard event."
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
return True
|
||||
|
||||
def updateBraille(self, obj, **args):
|
||||
"""Don't update braille in sleep mode."""
|
||||
msg = "SLEEP MODE: Not updating braille."
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
|
||||
# Event handler overrides - all do nothing
|
||||
def onActiveChanged(self, event):
|
||||
msg = "SLEEP MODE: Ignoring event."
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
|
||||
def onActiveDescendantChanged(self, event):
|
||||
msg = "SLEEP MODE: Ignoring event."
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
|
||||
def onBusyChanged(self, event):
|
||||
msg = "SLEEP MODE: Ignoring event."
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
def addKeyGrabs(self):
|
||||
"""Add back all key grabs."""
|
||||
|
||||
try:
|
||||
# Remove our limited grabs first
|
||||
if hasattr(self, 'grab_ids'):
|
||||
import cthulhu
|
||||
for grab_id in self.grab_ids:
|
||||
try:
|
||||
cthulhu.removeKeyGrab(grab_id)
|
||||
debug.printMessage(debug.LEVEL_INFO, f"SLEEP MODE: Removed key grab: {grab_id}", True)
|
||||
except Exception as e:
|
||||
debug.printMessage(debug.LEVEL_INFO, f"SLEEP MODE: Error removing key grab {grab_id}: {e}", True)
|
||||
self.grab_ids = []
|
||||
|
||||
# Let the parent class restore all grabs
|
||||
super().addKeyGrabs()
|
||||
|
||||
except Exception as e:
|
||||
debug.printMessage(debug.LEVEL_INFO, f"SLEEP MODE: Error in addKeyGrabs: {e}", True)
|
||||
|
||||
# Block common event handlers as an additional layer of protection
|
||||
def onCaretMoved(self, event):
|
||||
msg = "SLEEP MODE: Ignoring event."
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
|
||||
def onCheckedChanged(self, event):
|
||||
msg = "SLEEP MODE: Ignoring event."
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
|
||||
def onChildrenAdded(self, event):
|
||||
msg = "SLEEP MODE: Ignoring event."
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
|
||||
def onChildrenRemoved(self, event):
|
||||
msg = "SLEEP MODE: Ignoring event."
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
|
||||
def onColumnReordered(self, event):
|
||||
msg = "SLEEP MODE: Ignoring event."
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
|
||||
def onDocumentLoadComplete(self, event):
|
||||
msg = "SLEEP MODE: Ignoring event."
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
|
||||
def onDocumentLoadStopped(self, event):
|
||||
msg = "SLEEP MODE: Ignoring event."
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
|
||||
def onDocumentReload(self, event):
|
||||
msg = "SLEEP MODE: Ignoring event."
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
|
||||
def onExpandedChanged(self, event):
|
||||
msg = "SLEEP MODE: Ignoring event."
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
|
||||
def onFocus(self, event):
|
||||
msg = "SLEEP MODE: Ignoring event."
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
|
||||
def onFocusedChanged(self, event):
|
||||
msg = "SLEEP MODE: Ignoring event."
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
|
||||
def onMouseButton(self, event):
|
||||
msg = "SLEEP MODE: Ignoring event."
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
|
||||
def onNameChanged(self, event):
|
||||
msg = "SLEEP MODE: Ignoring event."
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
|
||||
def onSelectedChanged(self, event):
|
||||
msg = "SLEEP MODE: Ignoring event."
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
|
||||
def onSelectionChanged(self, event):
|
||||
msg = "SLEEP MODE: Ignoring event."
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
|
||||
def onShowingChanged(self, event):
|
||||
msg = "SLEEP MODE: Ignoring event."
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
|
||||
def onTextAttributesChanged(self, event):
|
||||
msg = "SLEEP MODE: Ignoring event."
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
"""Block caret movement events."""
|
||||
debug.printMessage(debug.LEVEL_INFO, "SLEEP MODE: Blocking onCaretMoved", True)
|
||||
return True
|
||||
|
||||
def onTextDeleted(self, event):
|
||||
msg = "SLEEP MODE: Ignoring event."
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
"""Block text deletion events."""
|
||||
debug.printMessage(debug.LEVEL_INFO, "SLEEP MODE: Blocking onTextDeleted", True)
|
||||
return True
|
||||
|
||||
def onTextInserted(self, event):
|
||||
msg = "SLEEP MODE: Ignoring event."
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
"""Block text insertion events."""
|
||||
debug.printMessage(debug.LEVEL_INFO, "SLEEP MODE: Blocking onTextInserted", True)
|
||||
return True
|
||||
|
||||
def onTextSelectionChanged(self, event):
|
||||
msg = "SLEEP MODE: Ignoring event."
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
def onFocusChanged(self, event):
|
||||
"""Block focus change events."""
|
||||
debug.printMessage(debug.LEVEL_INFO, "SLEEP MODE: Blocking onFocusChanged", True)
|
||||
return True
|
||||
|
||||
def onWindowActivated(self, event):
|
||||
"""Callback for window:activate accessibility events."""
|
||||
self.clearBraille()
|
||||
self.presentMessage(messages.SLEEP_MODE_ENABLED_FOR % AXObject.get_name(self.app))
|
||||
"""Block window activation events."""
|
||||
debug.printMessage(debug.LEVEL_INFO, "SLEEP MODE: Blocking onWindowActivated", True)
|
||||
return True
|
||||
|
||||
def onWindowDeactivated(self, event):
|
||||
msg = "SLEEP MODE: Ignoring event."
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
|
||||
def getScript(app):
|
||||
"""Returns the script for the given application."""
|
||||
return Script(app)
|
||||
return Script(app)
|
||||
@@ -0,0 +1,62 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Copyright (c) 2024 Stormux
|
||||
# Copyright (c) 2023 Igalia, S.L.
|
||||
# Author: Joanmarie Diggs <jdiggs@igalia.com>
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# Fork of Orca Screen Reader (GNOME)
|
||||
# Original source: https://gitlab.gnome.org/GNOME/orca
|
||||
|
||||
"""Speech Generator for Sleep Mode. Does nothing."""
|
||||
|
||||
__id__ = "$Id$"
|
||||
__version__ = "$Revision$"
|
||||
__date__ = "$Date$"
|
||||
__copyright__ = "Copyright (c) 2024 Stormux"
|
||||
__license__ = "LGPL"
|
||||
|
||||
import cthulhu.debug as debug
|
||||
import cthulhu.speech_generator as speech_generator
|
||||
|
||||
class SpeechGenerator(speech_generator.SpeechGenerator):
|
||||
"""Speech Generator for Sleep Mode. Does nothing."""
|
||||
|
||||
def generateSpeech(self, obj, **args):
|
||||
"""Generates speech for the given object. Returns empty list in sleep mode."""
|
||||
msg = "SLEEP MODE SPEECH GENERATOR: Generating nothing."
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
print("DEBUG: SLEEP MODE SPEECH GENERATOR CALLED!")
|
||||
return []
|
||||
|
||||
def generateSelectedItems(self, obj, **args):
|
||||
"""Override selected items generation."""
|
||||
msg = "SLEEP MODE SPEECH GENERATOR: generateSelectedItems - generating nothing."
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
return []
|
||||
|
||||
def generateContext(self, obj, **args):
|
||||
"""Override context generation."""
|
||||
msg = "SLEEP MODE SPEECH GENERATOR: generateContext - generating nothing."
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
return []
|
||||
|
||||
def generateTitle(self, obj, **args):
|
||||
"""Override title generation."""
|
||||
msg = "SLEEP MODE SPEECH GENERATOR: generateTitle - generating nothing."
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
return []
|
||||
@@ -0,0 +1,196 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Copyright (c) 2024 Stormux
|
||||
# Copyright (c) 2024 Igalia, S.L.
|
||||
# Author: Joanmarie Diggs <jdiggs@igalia.com>
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# Fork of Orca Screen Reader (GNOME)
|
||||
# Original source: https://gitlab.gnome.org/GNOME/orca
|
||||
|
||||
"""Module for sleep mode management."""
|
||||
|
||||
__id__ = "$Id$"
|
||||
__version__ = "$Revision$"
|
||||
__date__ = "$Date$"
|
||||
__copyright__ = "Copyright (c) 2024 Stormux"
|
||||
__license__ = "LGPL"
|
||||
|
||||
import time
|
||||
from gi.repository import GLib
|
||||
import cthulhu.braille as braille
|
||||
import cthulhu.cmdnames as cmdnames
|
||||
import cthulhu.debug as debug
|
||||
import cthulhu.input_event as input_event
|
||||
import cthulhu.keybindings as keybindings
|
||||
import cthulhu.messages as messages
|
||||
import cthulhu.script_manager as script_manager
|
||||
from cthulhu.ax_object import AXObject
|
||||
|
||||
class SleepModeManager:
|
||||
"""Provides sleep mode implementation."""
|
||||
|
||||
def __init__(self):
|
||||
self._handlers = self.getHandlers(True)
|
||||
self._bindings = keybindings.KeyBindings()
|
||||
self._apps = []
|
||||
self._lastToggleTime = 0
|
||||
self._toggleDebounceDelay = 0.1 # 100ms debounce (reduced for better responsiveness)
|
||||
|
||||
def getBindings(self, refresh=False, isDesktop=True):
|
||||
"""Returns the sleep-mode-manager keybindings."""
|
||||
|
||||
if refresh:
|
||||
msg = f"SLEEP MODE MANAGER: Refreshing bindings. Is desktop: {isDesktop}"
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
self._setupBindings()
|
||||
elif len(self._bindings.keyBindings) == 0:
|
||||
self._setupBindings()
|
||||
|
||||
return self._bindings
|
||||
|
||||
def getHandlers(self, refresh=False):
|
||||
"""Returns the sleep-mode-manager handlers."""
|
||||
|
||||
if refresh:
|
||||
msg = "SLEEP MODE MANAGER: Refreshing handlers."
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
self._setupHandlers()
|
||||
|
||||
return self._handlers
|
||||
|
||||
def isActiveForApp(self, app):
|
||||
"""Returns True if sleep mode is active for app."""
|
||||
|
||||
result = bool(app and hash(app) in self._apps)
|
||||
if result:
|
||||
tokens = ["SLEEP MODE MANAGER: Is active for", app]
|
||||
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
||||
return result
|
||||
|
||||
def _setupHandlers(self):
|
||||
"""Sets up and returns the sleep-mode-manager input event handlers."""
|
||||
|
||||
self._handlers = {}
|
||||
|
||||
self._handlers["toggleSleepMode"] = \
|
||||
input_event.InputEventHandler(
|
||||
self.toggleSleepMode,
|
||||
cmdnames.TOGGLE_SLEEP_MODE)
|
||||
|
||||
msg = "SLEEP MODE MANAGER: Handlers set up."
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
|
||||
def _setupBindings(self):
|
||||
"""Sets up and returns the sleep-mode-manager key bindings."""
|
||||
|
||||
self._bindings = keybindings.KeyBindings()
|
||||
|
||||
self._bindings.add(
|
||||
keybindings.KeyBinding(
|
||||
"q",
|
||||
keybindings.defaultModifierMask,
|
||||
keybindings.CTHULHU_CTRL_MODIFIER_MASK | keybindings.SHIFT_MODIFIER_MASK,
|
||||
self._handlers["toggleSleepMode"]))
|
||||
|
||||
msg = "SLEEP MODE MANAGER: Bindings set up."
|
||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||
|
||||
def toggleSleepMode(self, script, inputEvent=None, notifyUser=True):
|
||||
"""Toggles sleep mode for the active application."""
|
||||
|
||||
tokens = ["SLEEP MODE MANAGER: toggleSleepMode. Script:", script,
|
||||
"Event:", inputEvent, "notifyUser:", notifyUser]
|
||||
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
||||
|
||||
# Debounce rapid key presses
|
||||
currentTime = time.time()
|
||||
timeSinceLastToggle = currentTime - self._lastToggleTime
|
||||
debug.printMessage(debug.LEVEL_INFO, f"SLEEP MODE: Toggle attempt - time since last: {timeSinceLastToggle:.3f}s", True)
|
||||
|
||||
if timeSinceLastToggle < self._toggleDebounceDelay:
|
||||
debug.printMessage(debug.LEVEL_INFO, f"SLEEP MODE: Ignoring rapid toggle (delay: {timeSinceLastToggle:.3f}s < {self._toggleDebounceDelay}s)", True)
|
||||
return True
|
||||
|
||||
self._lastToggleTime = currentTime
|
||||
debug.printMessage(debug.LEVEL_INFO, f"SLEEP MODE: Processing toggle - app: {AXObject.get_name(script.app) if script and script.app else 'None'}", True)
|
||||
|
||||
if not (script and script.app):
|
||||
return True
|
||||
|
||||
from . import cthulhu_state
|
||||
scriptManager = script_manager.getManager()
|
||||
|
||||
if self.isActiveForApp(script.app):
|
||||
# Turning OFF sleep mode
|
||||
self._apps.remove(hash(script.app))
|
||||
newScript = scriptManager.getScript(script.app)
|
||||
if notifyUser:
|
||||
newScript.presentMessage(
|
||||
messages.SLEEP_MODE_DISABLED_FOR % AXObject.get_name(script.app))
|
||||
scriptManager.setActiveScript(newScript, "Sleep mode toggled off")
|
||||
debug.printMessage(debug.LEVEL_INFO, f"SLEEP MODE: Disabled for {AXObject.get_name(script.app)}", True)
|
||||
# Reset debounce timer after successful toggle
|
||||
self._lastToggleTime = 0
|
||||
return True
|
||||
|
||||
# Turning ON sleep mode
|
||||
braille.clear()
|
||||
if notifyUser:
|
||||
# Announce BEFORE switching to sleep mode script
|
||||
script.presentMessage(messages.SLEEP_MODE_ENABLED_FOR % AXObject.get_name(script.app))
|
||||
debug.printMessage(debug.LEVEL_INFO, f"SLEEP MODE: Scheduling delayed activation for {AXObject.get_name(script.app)}", True)
|
||||
# Use a small delay to ensure the message is spoken before switching scripts
|
||||
GLib.timeout_add(250, self._enableSleepModeDelayed, script, scriptManager)
|
||||
else:
|
||||
debug.printMessage(debug.LEVEL_INFO, f"SLEEP MODE: Immediate activation for {AXObject.get_name(script.app)}", True)
|
||||
self._enableSleepModeDelayed(script, scriptManager)
|
||||
|
||||
return True
|
||||
|
||||
def _enableSleepModeDelayed(self, script, scriptManager):
|
||||
"""Enable sleep mode after a small delay to allow announcement."""
|
||||
|
||||
debug.printMessage(debug.LEVEL_INFO, f"SLEEP MODE: _enableSleepModeDelayed called for {AXObject.get_name(script.app) if script and script.app else 'None'}", True)
|
||||
|
||||
try:
|
||||
debug.printMessage(debug.LEVEL_INFO, f"SLEEP MODE: Getting sleep script for {AXObject.get_name(script.app)}", True)
|
||||
sleepScript = scriptManager.getOrCreateSleepModeScript(script.app)
|
||||
debug.printMessage(debug.LEVEL_INFO, f"SLEEP MODE: Got sleep script: {sleepScript}", True)
|
||||
|
||||
debug.printMessage(debug.LEVEL_INFO, f"SLEEP MODE: Setting active script", True)
|
||||
scriptManager.setActiveScript(sleepScript, "Sleep mode toggled on")
|
||||
debug.printMessage(debug.LEVEL_INFO, f"SLEEP MODE: Active script set successfully", True)
|
||||
|
||||
debug.printMessage(debug.LEVEL_INFO, f"SLEEP MODE: Adding app to sleep list", True)
|
||||
self._apps.append(hash(script.app))
|
||||
debug.printMessage(debug.LEVEL_INFO, f"SLEEP MODE: Enabled for {AXObject.get_name(script.app)} (delayed)", True)
|
||||
# Reset debounce timer after successful toggle
|
||||
self._lastToggleTime = 0
|
||||
except Exception as e:
|
||||
debug.printMessage(debug.LEVEL_INFO, f"SLEEP MODE: Error enabling delayed: {e}", True)
|
||||
import traceback
|
||||
debug.printMessage(debug.LEVEL_INFO, f"SLEEP MODE: Exception traceback: {traceback.format_exc()}", True)
|
||||
|
||||
return False # Don't repeat the timeout
|
||||
|
||||
|
||||
_manager = SleepModeManager()
|
||||
|
||||
def getManager():
|
||||
"""Returns the Sleep Mode Manager singleton."""
|
||||
return _manager
|
||||
@@ -153,6 +153,19 @@ def sayAll(utteranceIterator, progressCallback):
|
||||
def _speak(text, acss, interrupt):
|
||||
"""Speaks the individual string using the given ACSS."""
|
||||
|
||||
# Block speech for applications in sleep mode, except sleep mode status messages
|
||||
from . import cthulhu_state
|
||||
from . import sleep_mode_manager
|
||||
if cthulhu_state.activeScript and hasattr(cthulhu_state.activeScript, 'app'):
|
||||
sleepModeManager = sleep_mode_manager.getManager()
|
||||
if sleepModeManager.isActiveForApp(cthulhu_state.activeScript.app):
|
||||
# Allow sleep mode status messages to get through
|
||||
if "Sleep mode enabled" in text or "Sleep mode disabled" in text:
|
||||
debug.printMessage(debug.LEVEL_INFO, f"SPEECH: Allowing sleep mode status: '{text}'", True)
|
||||
else:
|
||||
debug.printMessage(debug.LEVEL_INFO, f"SPEECH: Blocked by sleep mode: '{text}'", True)
|
||||
return
|
||||
|
||||
if not _speechserver:
|
||||
logLine = f"SPEECH OUTPUT: '{text}' {acss}"
|
||||
debug.printMessage(debug.LEVEL_INFO, logLine, True)
|
||||
@@ -179,6 +192,31 @@ def speak(content, acss=None, interrupt=True):
|
||||
if settings.silenceSpeech:
|
||||
return
|
||||
|
||||
# Block speech for applications in sleep mode, except sleep mode status messages
|
||||
from . import cthulhu_state
|
||||
from . import sleep_mode_manager
|
||||
if cthulhu_state.activeScript and hasattr(cthulhu_state.activeScript, 'app'):
|
||||
sleepModeManager = sleep_mode_manager.getManager()
|
||||
if sleepModeManager.isActiveForApp(cthulhu_state.activeScript.app):
|
||||
# Allow sleep mode status messages to get through
|
||||
if isinstance(content, str):
|
||||
if "Sleep mode enabled" in content or "Sleep mode disabled" in content:
|
||||
debug.printMessage(debug.LEVEL_INFO, f"SPEECH: Allowing sleep mode status: '{content}'", True)
|
||||
else:
|
||||
debug.printMessage(debug.LEVEL_INFO, f"SPEECH: Blocked by sleep mode: '{content}'", True)
|
||||
return
|
||||
elif isinstance(content, list):
|
||||
# Check if any element in the content list contains sleep mode messages
|
||||
content_str = str(content)
|
||||
if "Sleep mode enabled" in content_str or "Sleep mode disabled" in content_str:
|
||||
debug.printMessage(debug.LEVEL_INFO, f"SPEECH: Allowing sleep mode status list: '{content_str}'", True)
|
||||
else:
|
||||
debug.printMessage(debug.LEVEL_INFO, f"SPEECH: Blocked by sleep mode list: '{content_str}'", True)
|
||||
return
|
||||
else:
|
||||
debug.printMessage(debug.LEVEL_INFO, f"SPEECH: Blocked by sleep mode: '{content}'", True)
|
||||
return
|
||||
|
||||
validTypes = (str, list, speech_generator.Pause,
|
||||
speech_generator.LineBreak, ACSS)
|
||||
error = "SPEECH: bad content sent to speak(): '%s'"
|
||||
|
||||
Reference in New Issue
Block a user