Fixed sleep mode after a long and fierce battle.

This commit is contained in:
Storm Dragon
2025-08-14 20:53:03 -04:00
parent fb8c64a406
commit 68bc571a83
14 changed files with 484 additions and 177 deletions
+1 -1
View File
@@ -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"
+20
View File
@@ -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)
+2 -1
View File
@@ -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,
+1 -1
View File
@@ -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"
+1
View File
@@ -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',
+5
View File
@@ -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 {}
+13
View File
@@ -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.
+3 -8
View File
@@ -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(
+97 -166
View File
@@ -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 []
+196
View File
@@ -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
+38
View File
@@ -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'"