many many type hints added. Lots more to go.
This commit is contained in:
@@ -36,6 +36,7 @@ gi.require_version("Atspi", "2.0")
|
|||||||
from gi.repository import Atspi
|
from gi.repository import Atspi
|
||||||
|
|
||||||
from . import braille
|
from . import braille
|
||||||
|
from . import cthulhu # Need access to cthulhuApp
|
||||||
from . import debug
|
from . import debug
|
||||||
from . import generator
|
from . import generator
|
||||||
from . import messages
|
from . import messages
|
||||||
|
|||||||
+119
-101
@@ -25,6 +25,8 @@
|
|||||||
|
|
||||||
"""The main module for the Cthulhu screen reader."""
|
"""The main module for the Cthulhu screen reader."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
__id__ = "$Id$"
|
__id__ = "$Id$"
|
||||||
__version__ = "$Revision$"
|
__version__ = "$Revision$"
|
||||||
__date__ = "$Date$"
|
__date__ = "$Date$"
|
||||||
@@ -34,22 +36,37 @@ __copyright__ = "Copyright (c) 2004-2009 Sun Microsystems Inc." \
|
|||||||
__license__ = "LGPL"
|
__license__ = "LGPL"
|
||||||
|
|
||||||
import faulthandler
|
import faulthandler
|
||||||
|
from typing import TYPE_CHECKING, Any, Callable, Optional
|
||||||
|
|
||||||
from . import dbus_service
|
from . import dbus_service
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from types import FrameType
|
||||||
|
from gi.repository.Gio import Settings as GSettings
|
||||||
|
|
||||||
class APIHelper:
|
class APIHelper:
|
||||||
"""Helper class for plugin API interactions, including keybindings."""
|
"""Helper class for plugin API interactions, including keybindings."""
|
||||||
|
|
||||||
def __init__(self, app):
|
def __init__(self, app: Cthulhu) -> None:
|
||||||
"""Initialize the APIHelper.
|
"""Initialize the APIHelper.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
- app: the Cthulhu application
|
- app: the Cthulhu application
|
||||||
"""
|
"""
|
||||||
self.app = app
|
self.app: Cthulhu = app
|
||||||
self._gestureBindings = {}
|
self._gestureBindings: dict[Optional[str], list[Any]] = {}
|
||||||
|
|
||||||
def registerGestureByString(self, function, name, gestureString, inputEventType='default', normalizer='cthulhu', learnModeEnabled=True, contextName=None, globalBinding=False):
|
def registerGestureByString(
|
||||||
|
self,
|
||||||
|
function: Callable[..., Any],
|
||||||
|
name: str,
|
||||||
|
gestureString: str,
|
||||||
|
inputEventType: str = 'default',
|
||||||
|
normalizer: str = 'cthulhu',
|
||||||
|
learnModeEnabled: bool = True,
|
||||||
|
contextName: Optional[str] = None,
|
||||||
|
globalBinding: bool = False
|
||||||
|
) -> Optional[Any]:
|
||||||
"""Register a gesture by string."""
|
"""Register a gesture by string."""
|
||||||
import logging
|
import logging
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -100,12 +117,12 @@ class APIHelper:
|
|||||||
|
|
||||||
# Create a keybinding handler
|
# Create a keybinding handler
|
||||||
class GestureHandler:
|
class GestureHandler:
|
||||||
def __init__(self, function, description, learnModeEnabled=True):
|
def __init__(self, function: Callable[..., Any], description: str, learnModeEnabled: bool = True) -> None:
|
||||||
self.function = function
|
self.function: Callable[..., Any] = function
|
||||||
self.description = description
|
self.description: str = description
|
||||||
self.learnModeEnabled = learnModeEnabled
|
self.learnModeEnabled: bool = learnModeEnabled
|
||||||
|
|
||||||
def __call__(self, script, inputEvent):
|
def __call__(self, script: Any, inputEvent: Any) -> bool:
|
||||||
try:
|
try:
|
||||||
logger.info(f"=== Plugin keybinding handler called! ===")
|
logger.info(f"=== Plugin keybinding handler called! ===")
|
||||||
return function(script, inputEvent)
|
return function(script, inputEvent)
|
||||||
@@ -173,14 +190,14 @@ class APIHelper:
|
|||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def unregisterShortcut(self, binding, contextName=None):
|
def unregisterShortcut(self, binding: Any, contextName: Optional[str] = None) -> None:
|
||||||
"""Unregister a previously registered shortcut.
|
"""Unregister a previously registered shortcut.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
- binding: the binding to unregister
|
- binding: the binding to unregister
|
||||||
- contextName: the context for this gesture
|
- contextName: the context for this gesture
|
||||||
"""
|
"""
|
||||||
removed_via_plugin_manager = False
|
removed_via_plugin_manager: bool = False
|
||||||
if contextName and self.app:
|
if contextName and self.app:
|
||||||
try:
|
try:
|
||||||
plugin_manager = self.app.getPluginSystemManager()
|
plugin_manager = self.app.getPluginSystemManager()
|
||||||
@@ -221,7 +238,7 @@ from gi.repository import GObject
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
from gi.repository.Gio import Settings
|
from gi.repository.Gio import Settings
|
||||||
a11yAppSettings = Settings(schema_id='org.gnome.desktop.a11y.applications')
|
a11yAppSettings: Optional[GSettings] = Settings(schema_id='org.gnome.desktop.a11y.applications')
|
||||||
except Exception:
|
except Exception:
|
||||||
a11yAppSettings = None
|
a11yAppSettings = None
|
||||||
|
|
||||||
@@ -266,20 +283,20 @@ from . import resource_manager
|
|||||||
|
|
||||||
# Old global variables removed - now using cthulhuApp.* instead
|
# Old global variables removed - now using cthulhuApp.* instead
|
||||||
|
|
||||||
def onEnabledChanged(gsetting, key):
|
def onEnabledChanged(gsetting: GSettings, key: str) -> None:
|
||||||
try:
|
try:
|
||||||
enabled = gsetting.get_boolean(key)
|
enabled: bool = gsetting.get_boolean(key)
|
||||||
except Exception:
|
except Exception:
|
||||||
return
|
return
|
||||||
|
|
||||||
if key == 'screen-reader-enabled' and not enabled:
|
if key == 'screen-reader-enabled' and not enabled:
|
||||||
shutdown()
|
shutdown()
|
||||||
|
|
||||||
EXIT_CODE_HANG = 50
|
EXIT_CODE_HANG: int = 50
|
||||||
|
|
||||||
# The user-settings module (see loadUserSettings).
|
# The user-settings module (see loadUserSettings).
|
||||||
#
|
#
|
||||||
_userSettings = None
|
_userSettings: Optional[Any] = None
|
||||||
|
|
||||||
########################################################################
|
########################################################################
|
||||||
# #
|
# #
|
||||||
@@ -287,29 +304,29 @@ _userSettings = None
|
|||||||
# #
|
# #
|
||||||
########################################################################
|
########################################################################
|
||||||
|
|
||||||
CARET_TRACKING = "caret-tracking"
|
CARET_TRACKING: str = "caret-tracking"
|
||||||
FOCUS_TRACKING = "focus-tracking"
|
FOCUS_TRACKING: str = "focus-tracking"
|
||||||
FLAT_REVIEW = "flat-review"
|
FLAT_REVIEW: str = "flat-review"
|
||||||
MOUSE_REVIEW = "mouse-review"
|
MOUSE_REVIEW: str = "mouse-review"
|
||||||
OBJECT_NAVIGATOR = "object-navigator"
|
OBJECT_NAVIGATOR: str = "object-navigator"
|
||||||
SAY_ALL = "say-all"
|
SAY_ALL: str = "say-all"
|
||||||
|
|
||||||
def getActiveModeAndObjectOfInterest():
|
def getActiveModeAndObjectOfInterest() -> tuple[str, Any]:
|
||||||
return focus_manager.get_manager().get_active_mode_and_object_of_interest()
|
return focus_manager.get_manager().get_active_mode_and_object_of_interest()
|
||||||
|
|
||||||
def emitRegionChanged(obj, startOffset=None, endOffset=None, mode=None):
|
def emitRegionChanged(obj: Any, startOffset: Optional[int] = None, endOffset: Optional[int] = None, mode: Optional[str] = None) -> None:
|
||||||
"""Notifies interested clients that the current region of interest has changed."""
|
"""Notifies interested clients that the current region of interest has changed."""
|
||||||
focus_manager.get_manager().emit_region_changed(obj, startOffset, endOffset, mode)
|
focus_manager.get_manager().emit_region_changed(obj, startOffset, endOffset, mode)
|
||||||
|
|
||||||
def setActiveWindow(frame, app=None, alsoSetLocusOfFocus=False, notifyScript=False):
|
def setActiveWindow(frame: Any, app: Optional[Any] = None, alsoSetLocusOfFocus: bool = False, notifyScript: bool = False) -> None:
|
||||||
real_app = app
|
real_app: Optional[Any] = app
|
||||||
real_frame = frame
|
real_frame: Any = frame
|
||||||
if frame is not None and hasattr(AXObject, "find_real_app_and_window_for"):
|
if frame is not None and hasattr(AXObject, "find_real_app_and_window_for"):
|
||||||
real_app, real_frame = AXObject.find_real_app_and_window_for(frame, app)
|
real_app, real_frame = AXObject.find_real_app_and_window_for(frame, app)
|
||||||
focus_manager.get_manager().set_active_window(
|
focus_manager.get_manager().set_active_window(
|
||||||
real_frame, real_app, set_window_as_focus=alsoSetLocusOfFocus, notify_script=notifyScript)
|
real_frame, real_app, set_window_as_focus=alsoSetLocusOfFocus, notify_script=notifyScript)
|
||||||
|
|
||||||
def setLocusOfFocus(event, obj, notifyScript=True, force=False):
|
def setLocusOfFocus(event: Optional[Any], obj: Any, notifyScript: bool = True, force: bool = False) -> None:
|
||||||
"""Sets the locus of focus (i.e., the object with visual focus) and
|
"""Sets the locus of focus (i.e., the object with visual focus) and
|
||||||
notifies the script of the change should the script wish to present
|
notifies the script of the change should the script wish to present
|
||||||
the change to the user.
|
the change to the user.
|
||||||
@@ -331,7 +348,7 @@ def setLocusOfFocus(event, obj, notifyScript=True, force=False):
|
|||||||
# #
|
# #
|
||||||
########################################################################
|
########################################################################
|
||||||
|
|
||||||
def _processBrailleEvent(event):
|
def _processBrailleEvent(event: Any) -> bool:
|
||||||
"""Called whenever a key is pressed on the Braille display.
|
"""Called whenever a key is pressed on the Braille display.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
@@ -340,7 +357,7 @@ def _processBrailleEvent(event):
|
|||||||
Returns True if the event was consumed; otherwise False
|
Returns True if the event was consumed; otherwise False
|
||||||
"""
|
"""
|
||||||
|
|
||||||
consumed = False
|
consumed: bool = False
|
||||||
|
|
||||||
# Braille key presses always interrupt speech.
|
# Braille key presses always interrupt speech.
|
||||||
#
|
#
|
||||||
@@ -368,16 +385,16 @@ def _processBrailleEvent(event):
|
|||||||
# #
|
# #
|
||||||
########################################################################
|
########################################################################
|
||||||
|
|
||||||
def deviceChangeHandler(deviceManager, device):
|
def deviceChangeHandler(deviceManager: Any, device: Any) -> None:
|
||||||
"""New keyboards being plugged in stomp on our changes to the keymappings,
|
"""New keyboards being plugged in stomp on our changes to the keymappings,
|
||||||
so we have to re-apply"""
|
so we have to re-apply"""
|
||||||
source = device.get_source()
|
source = device.get_source()
|
||||||
if source == Gdk.InputSource.KEYBOARD:
|
if source == Gdk.InputSource.KEYBOARD:
|
||||||
msg = "CTHULHU: Keyboard change detected, re-creating the xmodmap"
|
msg: str = "CTHULHU: Keyboard change detected, re-creating the xmodmap"
|
||||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||||
cthulhu_modifier_manager.getManager().refreshCthulhuModifiers("Keyboard change detected.")
|
cthulhu_modifier_manager.getManager().refreshCthulhuModifiers("Keyboard change detected.")
|
||||||
|
|
||||||
def loadUserSettings(script=None, inputEvent=None, skipReloadMessage=False):
|
def loadUserSettings(script: Optional[Any] = None, inputEvent: Optional[Any] = None, skipReloadMessage: bool = False) -> bool:
|
||||||
"""Loads (and reloads) the user settings module, reinitializing
|
"""Loads (and reloads) the user settings module, reinitializing
|
||||||
things such as speech if necessary.
|
things such as speech if necessary.
|
||||||
|
|
||||||
@@ -398,7 +415,7 @@ def loadUserSettings(script=None, inputEvent=None, skipReloadMessage=False):
|
|||||||
cthulhuApp.scriptManager.deactivate()
|
cthulhuApp.scriptManager.deactivate()
|
||||||
cthulhuApp.getSignalManager().emitSignal('load-setting-begin')
|
cthulhuApp.getSignalManager().emitSignal('load-setting-begin')
|
||||||
|
|
||||||
reloaded = False
|
reloaded: bool = False
|
||||||
if _userSettings:
|
if _userSettings:
|
||||||
_profile = cthulhuApp.settingsManager.getSetting('activeProfile')[1]
|
_profile = cthulhuApp.settingsManager.getSetting('activeProfile')[1]
|
||||||
try:
|
try:
|
||||||
@@ -491,7 +508,7 @@ def loadUserSettings(script=None, inputEvent=None, skipReloadMessage=False):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _showPreferencesUI(script, prefs):
|
def _showPreferencesUI(script: Any, prefs: dict[str, Any]) -> None:
|
||||||
if cthulhu_state.cthulhuOS:
|
if cthulhu_state.cthulhuOS:
|
||||||
cthulhu_state.cthulhuOS.showGUI()
|
cthulhu_state.cthulhuOS.showGUI()
|
||||||
return
|
return
|
||||||
@@ -502,7 +519,7 @@ def _showPreferencesUI(script, prefs):
|
|||||||
debug.printException(debug.LEVEL_SEVERE)
|
debug.printException(debug.LEVEL_SEVERE)
|
||||||
return
|
return
|
||||||
|
|
||||||
uiFile = os.path.join(cthulhu_platform.datadir,
|
uiFile: str = os.path.join(cthulhu_platform.datadir,
|
||||||
cthulhu_platform.package,
|
cthulhu_platform.package,
|
||||||
"ui",
|
"ui",
|
||||||
"cthulhu-setup.ui")
|
"cthulhu-setup.ui")
|
||||||
@@ -511,7 +528,7 @@ def _showPreferencesUI(script, prefs):
|
|||||||
cthulhu_state.cthulhuOS.init(script)
|
cthulhu_state.cthulhuOS.init(script)
|
||||||
cthulhu_state.cthulhuOS.showGUI()
|
cthulhu_state.cthulhuOS.showGUI()
|
||||||
|
|
||||||
def showAppPreferencesGUI(script=None, inputEvent=None):
|
def showAppPreferencesGUI(script: Optional[Any] = None, inputEvent: Optional[Any] = None) -> bool:
|
||||||
"""Displays the user interface to configure the settings for a
|
"""Displays the user interface to configure the settings for a
|
||||||
specific applications within Cthulhu and set up those app-specific
|
specific applications within Cthulhu and set up those app-specific
|
||||||
user preferences using a GUI.
|
user preferences using a GUI.
|
||||||
@@ -519,7 +536,7 @@ def showAppPreferencesGUI(script=None, inputEvent=None):
|
|||||||
Returns True to indicate the input event has been consumed.
|
Returns True to indicate the input event has been consumed.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
prefs = {}
|
prefs: dict[str, Any] = {}
|
||||||
for key in settings.userCustomizableSettings:
|
for key in settings.userCustomizableSettings:
|
||||||
prefs[key] = cthulhuApp.settingsManager.getSetting(key)
|
prefs[key] = cthulhuApp.settingsManager.getSetting(key)
|
||||||
|
|
||||||
@@ -528,36 +545,36 @@ def showAppPreferencesGUI(script=None, inputEvent=None):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def showPreferencesGUI(script=None, inputEvent=None):
|
def showPreferencesGUI(script: Optional[Any] = None, inputEvent: Optional[Any] = None) -> bool:
|
||||||
"""Displays the user interface to configure Cthulhu and set up
|
"""Displays the user interface to configure Cthulhu and set up
|
||||||
user preferences using a GUI.
|
user preferences using a GUI.
|
||||||
|
|
||||||
Returns True to indicate the input event has been consumed.
|
Returns True to indicate the input event has been consumed.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
prefs = cthulhuApp.settingsManager.getGeneralSettings(cthulhuApp.settingsManager.profile)
|
prefs: dict[str, Any] = cthulhuApp.settingsManager.getGeneralSettings(cthulhuApp.settingsManager.profile)
|
||||||
script = cthulhuApp.scriptManager.get_default_script()
|
script = cthulhuApp.scriptManager.get_default_script()
|
||||||
_showPreferencesUI(script, prefs)
|
_showPreferencesUI(script, prefs)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def addKeyGrab(binding):
|
def addKeyGrab(binding: Any) -> list[int]:
|
||||||
""" Add a key grab for the given key binding."""
|
""" Add a key grab for the given key binding."""
|
||||||
|
|
||||||
manager = input_event_manager.get_manager()
|
manager = input_event_manager.get_manager()
|
||||||
return manager.add_grabs_for_keybinding(binding)
|
return manager.add_grabs_for_keybinding(binding)
|
||||||
|
|
||||||
def removeKeyGrab(id):
|
def removeKeyGrab(id: int) -> None:
|
||||||
""" Remove the key grab for the given key binding."""
|
""" Remove the key grab for the given key binding."""
|
||||||
|
|
||||||
manager = input_event_manager.get_manager()
|
manager = input_event_manager.get_manager()
|
||||||
manager.remove_grab_by_id(id)
|
manager.remove_grab_by_id(id)
|
||||||
|
|
||||||
def mapModifier(keycode):
|
def mapModifier(keycode: int) -> int:
|
||||||
manager = input_event_manager.get_manager()
|
manager = input_event_manager.get_manager()
|
||||||
return manager.map_keycode_to_modifier(keycode)
|
return manager.map_keycode_to_modifier(keycode)
|
||||||
|
|
||||||
def quitCthulhu(script=None, inputEvent=None):
|
def quitCthulhu(script: Optional[Any] = None, inputEvent: Optional[Any] = None) -> bool:
|
||||||
"""Quit Cthulhu. Check if the user wants to confirm this action.
|
"""Quit Cthulhu. Check if the user wants to confirm this action.
|
||||||
If so, show the confirmation GUI otherwise just shutdown.
|
If so, show the confirmation GUI otherwise just shutdown.
|
||||||
|
|
||||||
@@ -568,7 +585,7 @@ def quitCthulhu(script=None, inputEvent=None):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def showFindGUI(script=None, inputEvent=None):
|
def showFindGUI(script: Optional[Any] = None, inputEvent: Optional[Any] = None) -> None:
|
||||||
"""Displays the user interface to perform an Cthulhu Find.
|
"""Displays the user interface to perform an Cthulhu Find.
|
||||||
|
|
||||||
Returns True to indicate the input event has been consumed.
|
Returns True to indicate the input event has been consumed.
|
||||||
@@ -582,9 +599,9 @@ def showFindGUI(script=None, inputEvent=None):
|
|||||||
|
|
||||||
# If True, this module has been initialized.
|
# If True, this module has been initialized.
|
||||||
#
|
#
|
||||||
_initialized = False
|
_initialized: bool = False
|
||||||
|
|
||||||
def init():
|
def init() -> bool:
|
||||||
"""Initialize the cthulhu module, which initializes the speech and braille
|
"""Initialize the cthulhu module, which initializes the speech and braille
|
||||||
modules. Also builds up the application list, registers for AT-SPI events,
|
modules. Also builds up the application list, registers for AT-SPI events,
|
||||||
and creates scripts for all known applications.
|
and creates scripts for all known applications.
|
||||||
@@ -627,7 +644,7 @@ def init():
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _start_dbus_service():
|
def _start_dbus_service() -> bool:
|
||||||
"""Starts the D-Bus remote controller service in an idle callback."""
|
"""Starts the D-Bus remote controller service in an idle callback."""
|
||||||
debug.printMessage(debug.LEVEL_INFO, 'CTHULHU: Starting D-Bus remote controller', True)
|
debug.printMessage(debug.LEVEL_INFO, 'CTHULHU: Starting D-Bus remote controller', True)
|
||||||
try:
|
try:
|
||||||
@@ -652,11 +669,11 @@ def _start_dbus_service():
|
|||||||
dbus_service.get_remote_controller().register_decorated_module("PluginSystemManager", plugin_manager)
|
dbus_service.get_remote_controller().register_decorated_module("PluginSystemManager", plugin_manager)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
msg = f"CTHULHU: Failed to start D-Bus service: {e}"
|
msg: str = f"CTHULHU: Failed to start D-Bus service: {e}"
|
||||||
debug.printMessage(debug.LEVEL_SEVERE, msg, True)
|
debug.printMessage(debug.LEVEL_SEVERE, msg, True)
|
||||||
return False # Remove the idle callback
|
return False # Remove the idle callback
|
||||||
|
|
||||||
def start():
|
def start() -> None:
|
||||||
"""Starts Cthulhu."""
|
"""Starts Cthulhu."""
|
||||||
|
|
||||||
debug.printMessage(debug.LEVEL_INFO, 'CTHULHU: Starting', True)
|
debug.printMessage(debug.LEVEL_INFO, 'CTHULHU: Starting', True)
|
||||||
@@ -693,8 +710,8 @@ def start():
|
|||||||
|
|
||||||
Atspi.event_main()
|
Atspi.event_main()
|
||||||
|
|
||||||
def die(exitCode=1):
|
def die(exitCode: int = 1) -> None:
|
||||||
pid = os.getpid()
|
pid: int = os.getpid()
|
||||||
if exitCode == EXIT_CODE_HANG:
|
if exitCode == EXIT_CODE_HANG:
|
||||||
# Someting is hung and we wish to abort.
|
# Someting is hung and we wish to abort.
|
||||||
os.kill(pid, signal.SIGKILL)
|
os.kill(pid, signal.SIGKILL)
|
||||||
@@ -705,14 +722,14 @@ def die(exitCode=1):
|
|||||||
if exitCode > 1:
|
if exitCode > 1:
|
||||||
os.kill(pid, signal.SIGTERM)
|
os.kill(pid, signal.SIGTERM)
|
||||||
|
|
||||||
def timeout(signum=None, frame=None):
|
def timeout(signum: Optional[int] = None, frame: Optional[FrameType] = None) -> None:
|
||||||
msg = 'TIMEOUT: something has hung. Aborting.'
|
msg: str = 'TIMEOUT: something has hung. Aborting.'
|
||||||
debug.printMessage(debug.LEVEL_SEVERE, msg, True)
|
debug.printMessage(debug.LEVEL_SEVERE, msg, True)
|
||||||
debug.printStack(debug.LEVEL_SEVERE)
|
debug.printStack(debug.LEVEL_SEVERE)
|
||||||
debug.examineProcesses(force=True)
|
debug.examineProcesses(force=True)
|
||||||
die(EXIT_CODE_HANG)
|
die(EXIT_CODE_HANG)
|
||||||
|
|
||||||
def shutdown(script=None, inputEvent=None):
|
def shutdown(script: Optional[Any] = None, inputEvent: Optional[Any] = None) -> bool:
|
||||||
"""Exits Cthulhu. Unregisters any event listeners and cleans up.
|
"""Exits Cthulhu. Unregisters any event listeners and cleans up.
|
||||||
|
|
||||||
Returns True if the shutdown procedure ran or False if this module
|
Returns True if the shutdown procedure ran or False if this module
|
||||||
@@ -769,12 +786,13 @@ def shutdown(script=None, inputEvent=None):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
exitCount = 0
|
exitCount: int = 0
|
||||||
def shutdownOnSignal(signum, frame):
|
|
||||||
|
def shutdownOnSignal(signum: int, frame: Optional[FrameType]) -> None:
|
||||||
global exitCount
|
global exitCount
|
||||||
|
|
||||||
signalString = f'({signal.strsignal(signum)})'
|
signalString: str = f'({signal.strsignal(signum)})'
|
||||||
msg = f"CTHULHU: Shutting down and exiting due to signal={signum} {signalString}"
|
msg: str = f"CTHULHU: Shutting down and exiting due to signal={signum} {signalString}"
|
||||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||||
|
|
||||||
# Well...we'll try to exit nicely, but if we keep getting called,
|
# Well...we'll try to exit nicely, but if we keep getting called,
|
||||||
@@ -800,7 +818,7 @@ def shutdownOnSignal(signum, frame):
|
|||||||
#
|
#
|
||||||
speech.shutdown()
|
speech.shutdown()
|
||||||
shutdown()
|
shutdown()
|
||||||
cleanExit = True
|
cleanExit: bool = True
|
||||||
except Exception:
|
except Exception:
|
||||||
cleanExit = False
|
cleanExit = False
|
||||||
|
|
||||||
@@ -810,28 +828,28 @@ def shutdownOnSignal(signum, frame):
|
|||||||
if not cleanExit:
|
if not cleanExit:
|
||||||
die(EXIT_CODE_HANG)
|
die(EXIT_CODE_HANG)
|
||||||
|
|
||||||
def crashOnSignal(signum, frame):
|
def crashOnSignal(signum: int, frame: Optional[FrameType]) -> None:
|
||||||
signalString = f'({signal.strsignal(signum)})'
|
signalString: str = f'({signal.strsignal(signum)})'
|
||||||
msg = f"CTHULHU: Shutting down and exiting due to signal={signum} {signalString}"
|
msg: str = f"CTHULHU: Shutting down and exiting due to signal={signum} {signalString}"
|
||||||
debug.printMessage(debug.LEVEL_SEVERE, msg, True)
|
debug.printMessage(debug.LEVEL_SEVERE, msg, True)
|
||||||
debug.printStack(debug.LEVEL_SEVERE)
|
debug.printStack(debug.LEVEL_SEVERE)
|
||||||
cthulhu_modifier_manager.getManager().unsetCthulhuModifiers("Crashed")
|
cthulhu_modifier_manager.getManager().unsetCthulhuModifiers("Crashed")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
def main():
|
def main() -> int:
|
||||||
"""The main entry point for Cthulhu. The exit codes for Cthulhu will
|
"""The main entry point for Cthulhu. The exit codes for Cthulhu will
|
||||||
loosely be based on signals, where the exit code will be the
|
loosely be based on signals, where the exit code will be the
|
||||||
signal used to terminate Cthulhu (if a signal was used). Otherwise,
|
signal used to terminate Cthulhu (if a signal was used). Otherwise,
|
||||||
an exit code of 0 means normal completion and an exit code of 50
|
an exit code of 0 means normal completion and an exit code of 50
|
||||||
means Cthulhu exited because of a hang."""
|
means Cthulhu exited because of a hang."""
|
||||||
|
|
||||||
msg = f"CTHULHU: Launching version {cthulhu_platform.version}"
|
msg: str = f"CTHULHU: Launching version {cthulhu_platform.version}"
|
||||||
if cthulhu_platform.revision:
|
if cthulhu_platform.revision:
|
||||||
msg += f" (rev {cthulhu_platform.revision})"
|
msg += f" (rev {cthulhu_platform.revision})"
|
||||||
|
|
||||||
sessionType = os.environ.get('XDG_SESSION_TYPE') or ""
|
sessionType: str = os.environ.get('XDG_SESSION_TYPE') or ""
|
||||||
sessionDesktop = os.environ.get('XDG_SESSION_DESKTOP') or ""
|
sessionDesktop: str = os.environ.get('XDG_SESSION_DESKTOP') or ""
|
||||||
session = "%s %s".strip() % (sessionType, sessionDesktop)
|
session: str = "%s %s".strip() % (sessionType, sessionDesktop)
|
||||||
if session:
|
if session:
|
||||||
msg += f" session: {session}"
|
msg += f" session: {session}"
|
||||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||||
@@ -896,7 +914,7 @@ def main():
|
|||||||
|
|
||||||
class Cthulhu(GObject.Object):
|
class Cthulhu(GObject.Object):
|
||||||
# basic signals
|
# basic signals
|
||||||
__gsignals__ = {
|
__gsignals__: dict[str, tuple[Any, ...]] = {
|
||||||
"start-application-completed": (GObject.SignalFlags.RUN_LAST, None, ()),
|
"start-application-completed": (GObject.SignalFlags.RUN_LAST, None, ()),
|
||||||
"stop-application-completed": (GObject.SignalFlags.RUN_LAST, None, ()),
|
"stop-application-completed": (GObject.SignalFlags.RUN_LAST, None, ()),
|
||||||
"load-setting-begin": (GObject.SignalFlags.RUN_LAST, None, ()),
|
"load-setting-begin": (GObject.SignalFlags.RUN_LAST, None, ()),
|
||||||
@@ -906,56 +924,56 @@ class Cthulhu(GObject.Object):
|
|||||||
"request-application-preferences": (GObject.SignalFlags.RUN_LAST, None, ()),
|
"request-application-preferences": (GObject.SignalFlags.RUN_LAST, None, ()),
|
||||||
"active-script-changed": (GObject.SignalFlags.RUN_LAST, None, (GObject.TYPE_PYOBJECT,)), # New signal to indicate active script change
|
"active-script-changed": (GObject.SignalFlags.RUN_LAST, None, (GObject.TYPE_PYOBJECT,)), # New signal to indicate active script change
|
||||||
}
|
}
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
GObject.Object.__init__(self)
|
GObject.Object.__init__(self)
|
||||||
# add members
|
# add members
|
||||||
self.resourceManager = resource_manager.ResourceManager(self)
|
self.resourceManager: Any = resource_manager.ResourceManager(self)
|
||||||
self.settingsManager = settings_manager.SettingsManager(self) # Directly instantiate
|
self.settingsManager: Any = settings_manager.SettingsManager(self) # Directly instantiate
|
||||||
self.eventManager = event_manager.EventManager(self) # Directly instantiate
|
self.eventManager: Any = event_manager.EventManager(self) # Directly instantiate
|
||||||
self.scriptManager = script_manager.ScriptManager(self) # Directly instantiate
|
self.scriptManager: Any = script_manager.ScriptManager(self) # Directly instantiate
|
||||||
self.logger = logger.Logger() # Directly instantiate
|
self.logger: Any = logger.Logger() # Directly instantiate
|
||||||
self.signalManager = signal_manager.SignalManager(self)
|
self.signalManager: Any = signal_manager.SignalManager(self)
|
||||||
self.dynamicApiManager = dynamic_api_manager.DynamicApiManager(self)
|
self.dynamicApiManager: Any = dynamic_api_manager.DynamicApiManager(self)
|
||||||
self.translationManager = translation_manager.TranslationManager(self)
|
self.translationManager: Any = translation_manager.TranslationManager(self)
|
||||||
self.debugManager = debug
|
self.debugManager: Any = debug
|
||||||
self.APIHelper = APIHelper(self)
|
self.APIHelper: APIHelper = APIHelper(self)
|
||||||
self.createCompatAPI()
|
self.createCompatAPI()
|
||||||
self.pluginSystemManager = plugin_system_manager.PluginSystemManager(self)
|
self.pluginSystemManager: Any = plugin_system_manager.PluginSystemManager(self)
|
||||||
# Scan for available plugins at startup
|
# Scan for available plugins at startup
|
||||||
self.pluginSystemManager.rescanPlugins()
|
self.pluginSystemManager.rescanPlugins()
|
||||||
def getAPIHelper(self):
|
def getAPIHelper(self) -> APIHelper:
|
||||||
return self.APIHelper
|
return self.APIHelper
|
||||||
def getPluginSystemManager(self):
|
def getPluginSystemManager(self) -> Any:
|
||||||
return self.pluginSystemManager
|
return self.pluginSystemManager
|
||||||
def getDynamicApiManager(self):
|
def getDynamicApiManager(self) -> Any:
|
||||||
return self.dynamicApiManager
|
return self.dynamicApiManager
|
||||||
def getSignalManager(self):
|
def getSignalManager(self) -> Any:
|
||||||
return self.signalManager
|
return self.signalManager
|
||||||
def getEventManager(self):
|
def getEventManager(self) -> Any:
|
||||||
return self.eventManager
|
return self.eventManager
|
||||||
def getSettingsManager(self):
|
def getSettingsManager(self) -> Any:
|
||||||
return self.settingsManager
|
return self.settingsManager
|
||||||
def getScriptManager(self):
|
def getScriptManager(self) -> Any:
|
||||||
return self.scriptManager
|
return self.scriptManager
|
||||||
def get_scriptManager(self):
|
def get_scriptManager(self) -> Any:
|
||||||
return self.scriptManager
|
return self.scriptManager
|
||||||
def getDebugManager(self):
|
def getDebugManager(self) -> Any:
|
||||||
return self.debugManager
|
return self.debugManager
|
||||||
def getTranslationManager(self):
|
def getTranslationManager(self) -> Any:
|
||||||
return self.translationManager
|
return self.translationManager
|
||||||
def getResourceManager(self):
|
def getResourceManager(self) -> Any:
|
||||||
return self.resourceManager
|
return self.resourceManager
|
||||||
def getLogger(self): # New getter for the logger
|
def getLogger(self) -> Any: # New getter for the logger
|
||||||
return self.logger
|
return self.logger
|
||||||
def addKeyGrab(self, binding):
|
def addKeyGrab(self, binding: Any) -> list[int]:
|
||||||
return addKeyGrab(binding)
|
return addKeyGrab(binding)
|
||||||
def removeKeyGrab(self, grab_id):
|
def removeKeyGrab(self, grab_id: int) -> None:
|
||||||
return removeKeyGrab(grab_id)
|
return removeKeyGrab(grab_id)
|
||||||
def run(self, cacheValues=True):
|
def run(self, cacheValues: bool = True) -> int:
|
||||||
return main(cacheValues)
|
return main()
|
||||||
def stop(self):
|
def stop(self) -> None:
|
||||||
pass
|
pass
|
||||||
def createCompatAPI(self):
|
def createCompatAPI(self) -> None:
|
||||||
# for now add compatibility layer using Dynamic API
|
# for now add compatibility layer using Dynamic API
|
||||||
# should be removed step by step
|
# should be removed step by step
|
||||||
# use clean objects, getters and setters instead
|
# use clean objects, getters and setters instead
|
||||||
@@ -990,9 +1008,9 @@ class Cthulhu(GObject.Object):
|
|||||||
self.getDynamicApiManager().registerAPI('LoadUserSettings', loadUserSettings)
|
self.getDynamicApiManager().registerAPI('LoadUserSettings', loadUserSettings)
|
||||||
self.getDynamicApiManager().registerAPI('APIHelper', self.APIHelper)
|
self.getDynamicApiManager().registerAPI('APIHelper', self.APIHelper)
|
||||||
|
|
||||||
cthulhuApp = Cthulhu()
|
cthulhuApp: Cthulhu = Cthulhu()
|
||||||
|
|
||||||
def getManager():
|
def getManager() -> Cthulhu:
|
||||||
return cthulhuApp
|
return cthulhuApp
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -26,6 +26,8 @@
|
|||||||
"""Holds state that is shared among many modules.
|
"""Holds state that is shared among many modules.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from typing import Optional, Any
|
||||||
|
|
||||||
__id__ = "$Id$"
|
__id__ = "$Id$"
|
||||||
__version__ = "$Revision$"
|
__version__ = "$Revision$"
|
||||||
__date__ = "$Date$"
|
__date__ = "$Date$"
|
||||||
@@ -36,54 +38,58 @@ __license__ = "LGPL"
|
|||||||
# NOTE: resist the temptation to do any imports here. They can
|
# NOTE: resist the temptation to do any imports here. They can
|
||||||
# easily cause circular imports.
|
# easily cause circular imports.
|
||||||
#
|
#
|
||||||
|
# We use forward references in type hints to avoid importing:
|
||||||
|
# - Atspi.Accessible for focus and window
|
||||||
|
# - Script for activeScript
|
||||||
|
# - InputEvent for lastInputEvent
|
||||||
|
|
||||||
# The Accessible that has visual focus.
|
# The Accessible that has visual focus.
|
||||||
#
|
#
|
||||||
locusOfFocus = None
|
locusOfFocus: Optional[Any] = None # Actually: Optional[Atspi.Accessible]
|
||||||
|
|
||||||
# The currently active window.
|
# The currently active window.
|
||||||
#
|
#
|
||||||
activeWindow = None
|
activeWindow: Optional[Any] = None # Actually: Optional[Atspi.Accessible]
|
||||||
|
|
||||||
# The currently active script.
|
# The currently active script.
|
||||||
#
|
#
|
||||||
activeScript = None
|
activeScript: Optional[Any] = None # Actually: Optional[Script]
|
||||||
|
|
||||||
# The currently active mode (focus, say all, flat review, etc.) and obj
|
# The currently active mode (focus, say all, flat review, etc.) and obj
|
||||||
activeMode = None
|
activeMode: Optional[str] = None
|
||||||
objOfInterest = None
|
objOfInterest: Optional[Any] = None # Actually: Optional[Atspi.Accessible]
|
||||||
|
|
||||||
# Used to capture keys to redefine key bindings by the user.
|
# Used to capture keys to redefine key bindings by the user.
|
||||||
#
|
#
|
||||||
capturingKeys = False
|
capturingKeys: bool = False
|
||||||
|
|
||||||
# The last non-modifier key event received.
|
# The last non-modifier key event received.
|
||||||
#
|
#
|
||||||
lastNonModifierKeyEvent = None
|
lastNonModifierKeyEvent: Optional[Any] = None # Actually: Optional[KeyboardEvent]
|
||||||
|
|
||||||
# The InputEvent instance representing the last input event. This is
|
# The InputEvent instance representing the last input event. This is
|
||||||
# set each time a mouse, keyboard or braille event is received.
|
# set each time a mouse, keyboard or braille event is received.
|
||||||
#
|
#
|
||||||
lastInputEvent = None
|
lastInputEvent: Optional[Any] = None # Actually: Optional[InputEvent]
|
||||||
|
|
||||||
# Used to determine if the user wishes Cthulhu to pass the next command
|
# Used to determine if the user wishes Cthulhu to pass the next command
|
||||||
# along to the current application rather than consuming it.
|
# along to the current application rather than consuming it.
|
||||||
#
|
#
|
||||||
bypassNextCommand = False
|
bypassNextCommand: bool = False
|
||||||
|
|
||||||
# The last searchQuery
|
# The last searchQuery
|
||||||
#
|
#
|
||||||
searchQuery = None
|
searchQuery: Optional[str] = None
|
||||||
|
|
||||||
# Handle to the Cthulhu Preferences Glade GUI object.
|
# Handle to the Cthulhu Preferences Glade GUI object.
|
||||||
#
|
#
|
||||||
cthulhuOS = None
|
cthulhuOS: Optional[Any] = None # Actually: Optional[CthulhuSetupGUI]
|
||||||
|
|
||||||
# Set to True if the last key opened the preferences dialog
|
# Set to True if the last key opened the preferences dialog
|
||||||
#
|
#
|
||||||
openingDialog = False
|
openingDialog: bool = False
|
||||||
|
|
||||||
# The AT-SPI device (needed for key grabs). Will be set to None if AT-SPI
|
# The AT-SPI device (needed for key grabs). Will be set to None if AT-SPI
|
||||||
# is too old to support the new device API.
|
# is too old to support the new device API.
|
||||||
#
|
#
|
||||||
device = None
|
device: Optional[Any] = None # Actually: Optional[Atspi.Device]
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ __copyright__ = "Copyright (c) 2005-2008 Sun Microsystems Inc." \
|
|||||||
__license__ = "LGPL"
|
__license__ = "LGPL"
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
from typing import Optional, Dict, Callable, Any
|
||||||
|
|
||||||
from . import cthulhu # Need access to cthulhuApp
|
from . import cthulhu # Need access to cthulhuApp
|
||||||
from . import cmdnames
|
from . import cmdnames
|
||||||
@@ -45,21 +46,21 @@ _settingsManager = None # Removed - use cthulhu.cthulhuApp.settingsManager
|
|||||||
class DateAndTimePresenter:
|
class DateAndTimePresenter:
|
||||||
"""Provides commands to present the date and time."""
|
"""Provides commands to present the date and time."""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
self._handlers = self._setup_handlers()
|
self._handlers: Dict[str, input_event.InputEventHandler] = self._setup_handlers()
|
||||||
self._bindings = self._setup_bindings()
|
self._bindings: keybindings.KeyBindings = self._setup_bindings()
|
||||||
|
|
||||||
def get_bindings(self):
|
def get_bindings(self) -> keybindings.KeyBindings:
|
||||||
"""Returns the date-and-time-presenter keybindings."""
|
"""Returns the date-and-time-presenter keybindings."""
|
||||||
|
|
||||||
return self._bindings
|
return self._bindings
|
||||||
|
|
||||||
def get_handlers(self):
|
def get_handlers(self) -> Dict[str, input_event.InputEventHandler]:
|
||||||
"""Returns the date-and-time-presenter handlers."""
|
"""Returns the date-and-time-presenter handlers."""
|
||||||
|
|
||||||
return self._handlers
|
return self._handlers
|
||||||
|
|
||||||
def _setup_handlers(self):
|
def _setup_handlers(self) -> Dict[str, input_event.InputEventHandler]:
|
||||||
"""Sets up and returns the date-and-time-presenter input event handlers."""
|
"""Sets up and returns the date-and-time-presenter input event handlers."""
|
||||||
|
|
||||||
handlers = {}
|
handlers = {}
|
||||||
@@ -76,7 +77,7 @@ class DateAndTimePresenter:
|
|||||||
|
|
||||||
return handlers
|
return handlers
|
||||||
|
|
||||||
def _setup_bindings(self):
|
def _setup_bindings(self) -> keybindings.KeyBindings:
|
||||||
"""Sets up and returns the date-and-time-presenter key bindings."""
|
"""Sets up and returns the date-and-time-presenter key bindings."""
|
||||||
|
|
||||||
bindings = keybindings.KeyBindings()
|
bindings = keybindings.KeyBindings()
|
||||||
@@ -99,14 +100,14 @@ class DateAndTimePresenter:
|
|||||||
|
|
||||||
return bindings
|
return bindings
|
||||||
|
|
||||||
def present_time(self, script, event=None):
|
def present_time(self, script: Any, event: Optional[Any] = None) -> bool:
|
||||||
"""Presents the current time."""
|
"""Presents the current time."""
|
||||||
|
|
||||||
format = cthulhu.cthulhuApp.settingsManager.getSetting('presentTimeFormat')
|
format = cthulhu.cthulhuApp.settingsManager.getSetting('presentTimeFormat')
|
||||||
script.presentMessage(time.strftime(format, time.localtime()))
|
script.presentMessage(time.strftime(format, time.localtime()))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def present_date(self, script, event=None):
|
def present_date(self, script: Any, event: Optional[Any] = None) -> bool:
|
||||||
"""Presents the current date."""
|
"""Presents the current date."""
|
||||||
|
|
||||||
format = cthulhu.cthulhuApp.settingsManager.getSetting('presentDateFormat')
|
format = cthulhu.cthulhuApp.settingsManager.getSetting('presentDateFormat')
|
||||||
@@ -114,8 +115,9 @@ class DateAndTimePresenter:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
_presenter = None
|
_presenter: Optional[DateAndTimePresenter] = None
|
||||||
def getPresenter():
|
|
||||||
|
def getPresenter() -> DateAndTimePresenter:
|
||||||
global _presenter
|
global _presenter
|
||||||
if _presenter is None:
|
if _presenter is None:
|
||||||
_presenter = DateAndTimePresenter()
|
_presenter = DateAndTimePresenter()
|
||||||
|
|||||||
+50
-40
@@ -28,6 +28,8 @@ level, which is held in the debugLevel field. All other methods take
|
|||||||
a debug level, which is compared to the current debug level to
|
a debug level, which is compared to the current debug level to
|
||||||
determine if the content should be output."""
|
determine if the content should be output."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
__id__ = "$Id$"
|
__id__ = "$Id$"
|
||||||
__version__ = "$Revision$"
|
__version__ = "$Revision$"
|
||||||
__date__ = "$Date$"
|
__date__ = "$Date$"
|
||||||
@@ -41,16 +43,19 @@ import re
|
|||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import types
|
import types
|
||||||
|
from typing import Any, Optional, TextIO, Pattern, TYPE_CHECKING
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
import gi
|
import gi
|
||||||
gi.require_version("Atspi", "2.0")
|
gi.require_version("Atspi", "2.0")
|
||||||
from gi.repository import Atspi
|
from gi.repository import Atspi
|
||||||
|
|
||||||
AXObject = None
|
if TYPE_CHECKING:
|
||||||
|
from .ax_object import AXObject as AXObjectType
|
||||||
|
|
||||||
def _get_ax_object():
|
AXObject: Optional[type[AXObjectType]] = None
|
||||||
|
|
||||||
|
def _get_ax_object() -> type[AXObjectType]:
|
||||||
global AXObject
|
global AXObject
|
||||||
if AXObject is None:
|
if AXObject is None:
|
||||||
from .ax_object import AXObject as ax_object
|
from .ax_object import AXObject as ax_object
|
||||||
@@ -117,14 +122,14 @@ LEVEL_FINEST = 400
|
|||||||
#
|
#
|
||||||
LEVEL_ALL = 0
|
LEVEL_ALL = 0
|
||||||
|
|
||||||
debugLevel = LEVEL_SEVERE
|
debugLevel: int = LEVEL_SEVERE
|
||||||
|
|
||||||
# The debug file. If this is not set, then all debug output is done
|
# The debug file. If this is not set, then all debug output is done
|
||||||
# via stdout. If this is set, then all debug output is sent to the
|
# via stdout. If this is set, then all debug output is sent to the
|
||||||
# file. This can be useful for debugging because one can pass in a
|
# file. This can be useful for debugging because one can pass in a
|
||||||
# non-buffered file to better track down hangs.
|
# non-buffered file to better track down hangs.
|
||||||
#
|
#
|
||||||
debugFile = None
|
debugFile: Optional[TextIO] = None
|
||||||
|
|
||||||
# The debug filter should be either None (which means to match all
|
# The debug filter should be either None (which means to match all
|
||||||
# events) or a compiled regular expression from the 're' module (see
|
# events) or a compiled regular expression from the 're' module (see
|
||||||
@@ -135,14 +140,14 @@ debugFile = None
|
|||||||
#
|
#
|
||||||
# debug.eventDebugFilter = rc.compile('focus:|window:activate')
|
# debug.eventDebugFilter = rc.compile('focus:|window:activate')
|
||||||
#
|
#
|
||||||
eventDebugLevel = LEVEL_FINEST
|
eventDebugLevel: int = LEVEL_FINEST
|
||||||
eventDebugFilter = None
|
eventDebugFilter: Optional[Pattern[str]] = None
|
||||||
|
|
||||||
# If True, we output debug information for the event queue. We
|
# If True, we output debug information for the event queue. We
|
||||||
# use this in addition to log level to prevent debug logic from
|
# use this in addition to log level to prevent debug logic from
|
||||||
# bogging down event handling.
|
# bogging down event handling.
|
||||||
#
|
#
|
||||||
debugEventQueue = False
|
debugEventQueue: bool = False
|
||||||
|
|
||||||
# What module(s) should be traced if traceit is being used. By default
|
# What module(s) should be traced if traceit is being used. By default
|
||||||
# we'll just attend to ourself. (And by default, we will not enable
|
# we'll just attend to ourself. (And by default, we will not enable
|
||||||
@@ -152,17 +157,17 @@ debugEventQueue = False
|
|||||||
# specific, immediate issue. Trust me. :-) Disabling braille monitor in
|
# specific, immediate issue. Trust me. :-) Disabling braille monitor in
|
||||||
# this case is also strongly advised.
|
# this case is also strongly advised.
|
||||||
#
|
#
|
||||||
TRACE_MODULES = ['cthulhu']
|
TRACE_MODULES: list[str] = ['cthulhu']
|
||||||
|
|
||||||
# Specific modules to ignore with traceit.
|
# Specific modules to ignore with traceit.
|
||||||
#
|
#
|
||||||
TRACE_IGNORE_MODULES = ['traceback', 'linecache', 'locale', 'gettext',
|
TRACE_IGNORE_MODULES: list[str] = ['traceback', 'linecache', 'locale', 'gettext',
|
||||||
'logging', 'UserDict', 'encodings', 'posixpath',
|
'logging', 'UserDict', 'encodings', 'posixpath',
|
||||||
'genericpath', 're']
|
'genericpath', 're']
|
||||||
|
|
||||||
# Specific apps to trace with traceit.
|
# Specific apps to trace with traceit.
|
||||||
#
|
#
|
||||||
TRACE_APPS = []
|
TRACE_APPS: list[str] = []
|
||||||
|
|
||||||
# What AT-SPI event(s) should be traced if traceit is being used. By
|
# What AT-SPI event(s) should be traced if traceit is being used. By
|
||||||
# default, we'll trace everything. Examples of what you might wish to
|
# default, we'll trace everything. Examples of what you might wish to
|
||||||
@@ -173,7 +178,7 @@ TRACE_APPS = []
|
|||||||
# TRACE_EVENTS = ['object:state-changed:selected']
|
# TRACE_EVENTS = ['object:state-changed:selected']
|
||||||
# (if you know the exact event type of interest)
|
# (if you know the exact event type of interest)
|
||||||
#
|
#
|
||||||
TRACE_EVENTS = []
|
TRACE_EVENTS: list[str] = []
|
||||||
|
|
||||||
# What role(s) should be traced if traceit is being used. By
|
# What role(s) should be traced if traceit is being used. By
|
||||||
# default, we'll trace everything. An example of what you might wish
|
# default, we'll trace everything. An example of what you might wish
|
||||||
@@ -181,17 +186,17 @@ TRACE_EVENTS = []
|
|||||||
#
|
#
|
||||||
# TRACE_ROLES = [Atspi.Role.PUSH_BUTTON, Atspi.Role.TOGGLE_BUTTON]
|
# TRACE_ROLES = [Atspi.Role.PUSH_BUTTON, Atspi.Role.TOGGLE_BUTTON]
|
||||||
#
|
#
|
||||||
TRACE_ROLES = []
|
TRACE_ROLES: list[Atspi.Role] = []
|
||||||
|
|
||||||
# Whether or not traceit should only trace the work being done when
|
# Whether or not traceit should only trace the work being done when
|
||||||
# processing an actual event. This is when most bad things happen.
|
# processing an actual event. This is when most bad things happen.
|
||||||
# So we'll default to True.
|
# So we'll default to True.
|
||||||
#
|
#
|
||||||
TRACE_ONLY_PROCESSING_EVENTS = True
|
TRACE_ONLY_PROCESSING_EVENTS: bool = True
|
||||||
|
|
||||||
objEvent = None
|
objEvent: Optional[Atspi.Event] = None
|
||||||
|
|
||||||
def printException(level):
|
def printException(level: int) -> None:
|
||||||
"""Prints out information regarding the current exception.
|
"""Prints out information regarding the current exception.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
@@ -203,7 +208,7 @@ def printException(level):
|
|||||||
traceback.print_exc(100, debugFile)
|
traceback.print_exc(100, debugFile)
|
||||||
println(level)
|
println(level)
|
||||||
|
|
||||||
def printStack(level):
|
def printStack(level: int) -> None:
|
||||||
"""Prints out the current stack.
|
"""Prints out the current stack.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
@@ -215,7 +220,7 @@ def printStack(level):
|
|||||||
traceback.print_stack(None, 100, debugFile)
|
traceback.print_stack(None, 100, debugFile)
|
||||||
println(level)
|
println(level)
|
||||||
|
|
||||||
def _asString(obj):
|
def _asString(obj: Any) -> str:
|
||||||
AXObject = _get_ax_object()
|
AXObject = _get_ax_object()
|
||||||
if isinstance(obj, Atspi.Accessible):
|
if isinstance(obj, Atspi.Accessible):
|
||||||
result = AXObject.get_role_name(obj)
|
result = AXObject.get_role_name(obj)
|
||||||
@@ -255,45 +260,47 @@ def _asString(obj):
|
|||||||
|
|
||||||
return str(obj)
|
return str(obj)
|
||||||
|
|
||||||
def _format_tokens(tokens):
|
def _format_tokens(tokens: list[Any]) -> str:
|
||||||
text = " ".join(map(_asString, tokens))
|
text = " ".join(map(_asString, tokens))
|
||||||
text = re.sub(r"[ \u00A0]+", " ", text)
|
text = re.sub(r"[ \u00A0]+", " ", text)
|
||||||
text = re.sub(r" (?=[,.:)])(?![\n])", "", text)
|
text = re.sub(r" (?=[,.:)])(?![\n])", "", text)
|
||||||
return text
|
return text
|
||||||
|
|
||||||
def format_log_message(prefix, message, reason=None):
|
def format_log_message(prefix: str, message: str, reason: Optional[str] = None) -> str:
|
||||||
text = f"{prefix}: {message}"
|
text = f"{prefix}: {message}"
|
||||||
if reason:
|
if reason:
|
||||||
text = f"{text} (reason={reason})"
|
text = f"{text} (reason={reason})"
|
||||||
return text
|
return text
|
||||||
|
|
||||||
def print_log(level, prefix, message, reason=None, timestamp=False, stack=False):
|
def print_log(level: int, prefix: str, message: str, reason: Optional[str] = None,
|
||||||
|
timestamp: bool = False, stack: bool = False) -> None:
|
||||||
text = format_log_message(prefix, message, reason)
|
text = format_log_message(prefix, message, reason)
|
||||||
printMessage(level, text, timestamp, stack)
|
printMessage(level, text, timestamp, stack)
|
||||||
|
|
||||||
def print_log_tokens(level, prefix, tokens, reason=None, timestamp=False, stack=False):
|
def print_log_tokens(level: int, prefix: str, tokens: list[Any], reason: Optional[str] = None,
|
||||||
|
timestamp: bool = False, stack: bool = False) -> None:
|
||||||
text = format_log_message(prefix, _format_tokens(tokens), reason)
|
text = format_log_message(prefix, _format_tokens(tokens), reason)
|
||||||
printMessage(level, text, timestamp, stack)
|
printMessage(level, text, timestamp, stack)
|
||||||
|
|
||||||
def printTokens(level, tokens, timestamp=False, stack=False):
|
def printTokens(level: int, tokens: list[Any], timestamp: bool = False, stack: bool = False) -> None:
|
||||||
if level < debugLevel:
|
if level < debugLevel:
|
||||||
return
|
return
|
||||||
|
|
||||||
println(level, _format_tokens(tokens), timestamp, stack)
|
println(level, _format_tokens(tokens), timestamp, stack)
|
||||||
|
|
||||||
def print_tokens(level, tokens, timestamp=False, stack=False):
|
def print_tokens(level: int, tokens: list[Any], timestamp: bool = False, stack: bool = False) -> None:
|
||||||
return printTokens(level, tokens, timestamp, stack)
|
return printTokens(level, tokens, timestamp, stack)
|
||||||
|
|
||||||
def printMessage(level, text, timestamp=False, stack=False):
|
def printMessage(level: int, text: str, timestamp: bool = False, stack: bool = False) -> None:
|
||||||
if level < debugLevel:
|
if level < debugLevel:
|
||||||
return
|
return
|
||||||
|
|
||||||
println(level, text, timestamp, stack)
|
println(level, text, timestamp, stack)
|
||||||
|
|
||||||
def print_message(level, text, timestamp=False, stack=False):
|
def print_message(level: int, text: str, timestamp: bool = False, stack: bool = False) -> None:
|
||||||
return printMessage(level, text, timestamp, stack)
|
return printMessage(level, text, timestamp, stack)
|
||||||
|
|
||||||
def _stackAsString(max_frames=4):
|
def _stackAsString(max_frames: int = 4) -> str:
|
||||||
callers = []
|
callers = []
|
||||||
current_module = inspect.getmodule(inspect.currentframe())
|
current_module = inspect.getmodule(inspect.currentframe())
|
||||||
stack = inspect.stack()
|
stack = inspect.stack()
|
||||||
@@ -313,7 +320,7 @@ def _stackAsString(max_frames=4):
|
|||||||
callers.reverse()
|
callers.reverse()
|
||||||
return " > ".join(map(_asString, callers))
|
return " > ".join(map(_asString, callers))
|
||||||
|
|
||||||
def println(level, text="", timestamp=False, stack=False):
|
def println(level: int, text: str = "", timestamp: bool = False, stack: bool = False) -> None:
|
||||||
"""Prints the text to stderr unless debug is enabled.
|
"""Prints the text to stderr unless debug is enabled.
|
||||||
|
|
||||||
If debug is enabled the text will be redirected to the
|
If debug is enabled the text will be redirected to the
|
||||||
@@ -357,7 +364,7 @@ def println(level, text="", timestamp=False, stack=False):
|
|||||||
sys.stderr.writelines([text, "\n"])
|
sys.stderr.writelines([text, "\n"])
|
||||||
sys.stderr.flush()
|
sys.stderr.flush()
|
||||||
|
|
||||||
def printResult(level, result=None):
|
def printResult(level: int, result: Any = None) -> None:
|
||||||
"""Prints the return result, along with information about the
|
"""Prints the return result, along with information about the
|
||||||
method, arguments, and any errors encountered."""
|
method, arguments, and any errors encountered."""
|
||||||
|
|
||||||
@@ -380,7 +387,8 @@ def printResult(level, result=None):
|
|||||||
string = f'{callString}\nRESULT: {result}'
|
string = f'{callString}\nRESULT: {result}'
|
||||||
println(level, f'{string}')
|
println(level, f'{string}')
|
||||||
|
|
||||||
def printObjectEvent(level, event, sourceInfo=None, timestamp=False):
|
def printObjectEvent(level: int, event: Atspi.Event, sourceInfo: Optional[str] = None,
|
||||||
|
timestamp: bool = False) -> None:
|
||||||
"""Prints out an Python Event object. The given level may be
|
"""Prints out an Python Event object. The given level may be
|
||||||
overridden if the eventDebugLevel is greater. Furthermore, only
|
overridden if the eventDebugLevel is greater. Furthermore, only
|
||||||
events with event types matching the eventDebugFilter regular
|
events with event types matching the eventDebugFilter regular
|
||||||
@@ -408,7 +416,7 @@ def printObjectEvent(level, event, sourceInfo=None, timestamp=False):
|
|||||||
if sourceInfo:
|
if sourceInfo:
|
||||||
println(level, f"{' ' * 18}{sourceInfo}", timestamp)
|
println(level, f"{' ' * 18}{sourceInfo}", timestamp)
|
||||||
|
|
||||||
def printInputEvent(level, string, timestamp=False):
|
def printInputEvent(level: int, string: str, timestamp: bool = False) -> None:
|
||||||
"""Prints out an input event. The given level may be overridden
|
"""Prints out an input event. The given level may be overridden
|
||||||
if the eventDebugLevel (see setEventDebugLevel) is greater.
|
if the eventDebugLevel (see setEventDebugLevel) is greater.
|
||||||
|
|
||||||
@@ -419,7 +427,8 @@ def printInputEvent(level, string, timestamp=False):
|
|||||||
|
|
||||||
println(max(level, eventDebugLevel), string, timestamp)
|
println(max(level, eventDebugLevel), string, timestamp)
|
||||||
|
|
||||||
def printDetails(level, indent, accessible, includeApp=True, timestamp=False):
|
def printDetails(level: int, indent: str, accessible: Atspi.Accessible,
|
||||||
|
includeApp: bool = True, timestamp: bool = False) -> None:
|
||||||
"""Lists the details of the given accessible with the given
|
"""Lists the details of the given accessible with the given
|
||||||
indentation.
|
indentation.
|
||||||
|
|
||||||
@@ -435,7 +444,8 @@ def printDetails(level, indent, accessible, includeApp=True, timestamp=False):
|
|||||||
getAccessibleDetails(level, accessible, indent, includeApp),
|
getAccessibleDetails(level, accessible, indent, includeApp),
|
||||||
timestamp)
|
timestamp)
|
||||||
|
|
||||||
def getAccessibleDetails(level, acc, indent="", includeApp=True):
|
def getAccessibleDetails(level: int, acc: Atspi.Accessible, indent: str = "",
|
||||||
|
includeApp: bool = True) -> str:
|
||||||
"""Returns a string, suitable for printing, that describes the
|
"""Returns a string, suitable for printing, that describes the
|
||||||
given accessible.
|
given accessible.
|
||||||
|
|
||||||
@@ -458,7 +468,7 @@ def getAccessibleDetails(level, acc, indent="", includeApp=True):
|
|||||||
#
|
#
|
||||||
import linecache
|
import linecache
|
||||||
|
|
||||||
def _getFileAndModule(frame):
|
def _getFileAndModule(frame: types.FrameType) -> tuple[Optional[str], Optional[str]]:
|
||||||
filename, module = None, None
|
filename, module = None, None
|
||||||
try:
|
try:
|
||||||
filename = frame.f_globals["__file__"]
|
filename = frame.f_globals["__file__"]
|
||||||
@@ -471,7 +481,7 @@ def _getFileAndModule(frame):
|
|||||||
|
|
||||||
return filename, module
|
return filename, module
|
||||||
|
|
||||||
def _shouldTraceIt():
|
def _shouldTraceIt() -> bool:
|
||||||
AXObject = _get_ax_object()
|
AXObject = _get_ax_object()
|
||||||
if not objEvent:
|
if not objEvent:
|
||||||
return not TRACE_ONLY_PROCESSING_EVENTS
|
return not TRACE_ONLY_PROCESSING_EVENTS
|
||||||
@@ -491,7 +501,7 @@ def _shouldTraceIt():
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def traceit(frame, event, arg):
|
def traceit(frame: types.FrameType, event: str, arg: Any) -> Optional[object]:
|
||||||
"""Line tracing utility to output all lines as they are executed by
|
"""Line tracing utility to output all lines as they are executed by
|
||||||
the interpreter. This is to be used by sys.settrace and is for
|
the interpreter. This is to be used by sys.settrace and is for
|
||||||
debugging purposes.
|
debugging purposes.
|
||||||
@@ -543,14 +553,14 @@ def traceit(frame, event, arg):
|
|||||||
|
|
||||||
return traceit
|
return traceit
|
||||||
|
|
||||||
def getOpenFDCount(pid):
|
def getOpenFDCount(pid: int) -> int:
|
||||||
procs = subprocess.check_output([ 'lsof', '-w', '-Ff', '-p', str(pid)])
|
procs = subprocess.check_output([ 'lsof', '-w', '-Ff', '-p', str(pid)])
|
||||||
procs = procs.decode('UTF-8').split('\n')
|
procs = procs.decode('UTF-8').split('\n')
|
||||||
files = list(filter(lambda s: s and s[0] == 'f' and s[1:].isdigit(), procs))
|
files = list(filter(lambda s: s and s[0] == 'f' and s[1:].isdigit(), procs))
|
||||||
|
|
||||||
return len(files)
|
return len(files)
|
||||||
|
|
||||||
def getCmdline(pid):
|
def getCmdline(pid: int) -> str:
|
||||||
try:
|
try:
|
||||||
openFile = os.popen(f'cat /proc/{pid}/cmdline')
|
openFile = os.popen(f'cat /proc/{pid}/cmdline')
|
||||||
cmdline = openFile.read()
|
cmdline = openFile.read()
|
||||||
@@ -561,7 +571,7 @@ def getCmdline(pid):
|
|||||||
|
|
||||||
return cmdline
|
return cmdline
|
||||||
|
|
||||||
def pidOf(procName):
|
def pidOf(procName: str) -> list[int]:
|
||||||
openFile = subprocess.Popen(f'pgrep {procName}',
|
openFile = subprocess.Popen(f'pgrep {procName}',
|
||||||
shell=True,
|
shell=True,
|
||||||
stdout=subprocess.PIPE).stdout
|
stdout=subprocess.PIPE).stdout
|
||||||
@@ -569,7 +579,7 @@ def pidOf(procName):
|
|||||||
openFile.close()
|
openFile.close()
|
||||||
return [int(p) for p in pids.split()]
|
return [int(p) for p in pids.split()]
|
||||||
|
|
||||||
def examineProcesses(force=False):
|
def examineProcesses(force: bool = False) -> None:
|
||||||
AXObject = _get_ax_object()
|
AXObject = _get_ax_object()
|
||||||
from .ax_utilities import AXUtilities
|
from .ax_utilities import AXUtilities
|
||||||
if force:
|
if force:
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ from gi.repository import GLib
|
|||||||
import queue
|
import queue
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
|
from typing import Optional, Dict, List, Tuple, Any
|
||||||
|
|
||||||
from . import cthulhu
|
from . import cthulhu
|
||||||
from . import debug
|
from . import debug
|
||||||
@@ -49,25 +50,25 @@ from .ax_utilities import AXUtilities
|
|||||||
|
|
||||||
class EventManager:
|
class EventManager:
|
||||||
|
|
||||||
EMBEDDED_OBJECT_CHARACTER = '\ufffc'
|
EMBEDDED_OBJECT_CHARACTER: str = '\ufffc'
|
||||||
|
|
||||||
def __init__(self, app, asyncMode=True):
|
def __init__(self, app: Any, asyncMode: bool = True) -> None: # app is CthulhuApp instance
|
||||||
debug.printMessage(debug.LEVEL_INFO, 'EVENT MANAGER: Initializing', True)
|
debug.printMessage(debug.LEVEL_INFO, 'EVENT MANAGER: Initializing', True)
|
||||||
debug.printMessage(debug.LEVEL_INFO, f'EVENT MANAGER: Async Mode is {asyncMode}', True)
|
debug.printMessage(debug.LEVEL_INFO, f'EVENT MANAGER: Async Mode is {asyncMode}', True)
|
||||||
self.app = app
|
self.app: Any = app
|
||||||
self._asyncMode = asyncMode
|
self._asyncMode: bool = asyncMode
|
||||||
self._scriptListenerCounts = {}
|
self._scriptListenerCounts: Dict[str, int] = {}
|
||||||
self._active = False
|
self._active: bool = False
|
||||||
self._enqueueCount = 0
|
self._enqueueCount: int = 0
|
||||||
self._dequeueCount = 0
|
self._dequeueCount: int = 0
|
||||||
self._cmdlineCache = {}
|
self._cmdlineCache: Dict[int, str] = {}
|
||||||
self._eventQueue = queue.Queue(0)
|
self._eventQueue: queue.Queue = queue.Queue(0)
|
||||||
self._gidleId = 0
|
self._gidleId: int = 0
|
||||||
self._gidleLock = threading.Lock()
|
self._gidleLock: threading.Lock = threading.Lock()
|
||||||
self._gilSleepTime = 0.00001
|
self._gilSleepTime: float = 0.00001
|
||||||
self._synchronousToolkits = ['VCL']
|
self._synchronousToolkits: List[str] = ['VCL']
|
||||||
self._eventsSuspended = False
|
self._eventsSuspended: bool = False
|
||||||
self._listener = Atspi.EventListener.new(self._enqueue)
|
self._listener: Atspi.EventListener = Atspi.EventListener.new(self._enqueue)
|
||||||
|
|
||||||
# Note: These must match what the scripts registered for, otherwise
|
# Note: These must match what the scripts registered for, otherwise
|
||||||
# Atspi might segfault.
|
# Atspi might segfault.
|
||||||
@@ -75,24 +76,24 @@ class EventManager:
|
|||||||
# Events we don't want to suspend include:
|
# Events we don't want to suspend include:
|
||||||
# object:text-changed:insert - marco
|
# object:text-changed:insert - marco
|
||||||
# object:property-change:accessible-name - gnome-shell issue #6925
|
# object:property-change:accessible-name - gnome-shell issue #6925
|
||||||
self._suspendableEvents = ['object:children-changed:add',
|
self._suspendableEvents: List[str] = ['object:children-changed:add',
|
||||||
'object:children-changed:remove',
|
'object:children-changed:remove',
|
||||||
'object:state-changed:sensitive',
|
'object:state-changed:sensitive',
|
||||||
'object:state-changed:showing',
|
'object:state-changed:showing',
|
||||||
'object:text-changed:delete']
|
'object:text-changed:delete']
|
||||||
self._eventsTriggeringSuspension = []
|
self._eventsTriggeringSuspension: List[Any] = [] # List of events
|
||||||
self._ignoredEvents = ['object:bounds-changed',
|
self._ignoredEvents: List[str] = ['object:bounds-changed',
|
||||||
'object:state-changed:defunct',
|
'object:state-changed:defunct',
|
||||||
'object:property-change:accessible-parent']
|
'object:property-change:accessible-parent']
|
||||||
self._parentsOfDefunctDescendants = []
|
self._parentsOfDefunctDescendants: List[Any] = [] # List[Atspi.Accessible]
|
||||||
|
|
||||||
cthulhu_state.device = None
|
cthulhu_state.device = None
|
||||||
self._keyHandlingActive = False
|
self._keyHandlingActive: bool = False
|
||||||
self._inputEventManager = None
|
self._inputEventManager: Optional[Any] = None # Optional[InputEventManager]
|
||||||
|
|
||||||
debug.printMessage(debug.LEVEL_INFO, 'Event manager initialized', True)
|
debug.printMessage(debug.LEVEL_INFO, 'Event manager initialized', True)
|
||||||
|
|
||||||
def activate(self):
|
def activate(self) -> None:
|
||||||
"""Called when this event manager is activated."""
|
"""Called when this event manager is activated."""
|
||||||
|
|
||||||
debug.printMessage(debug.LEVEL_INFO, 'EVENT MANAGER: Activating', True)
|
debug.printMessage(debug.LEVEL_INFO, 'EVENT MANAGER: Activating', True)
|
||||||
@@ -101,7 +102,7 @@ class EventManager:
|
|||||||
GLib.idle_add(self._sync_focus_on_startup)
|
GLib.idle_add(self._sync_focus_on_startup)
|
||||||
debug.printMessage(debug.LEVEL_INFO, 'EVENT MANAGER: Activated', True)
|
debug.printMessage(debug.LEVEL_INFO, 'EVENT MANAGER: Activated', True)
|
||||||
|
|
||||||
def _sync_focus_on_startup(self):
|
def _sync_focus_on_startup(self) -> bool:
|
||||||
"""Initialize active window and focus when startup missed focus events."""
|
"""Initialize active window and focus when startup missed focus events."""
|
||||||
|
|
||||||
focus = cthulhu_state.locusOfFocus
|
focus = cthulhu_state.locusOfFocus
|
||||||
@@ -127,7 +128,7 @@ class EventManager:
|
|||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _activateKeyHandling(self):
|
def _activateKeyHandling(self) -> None:
|
||||||
"""Activates keyboard handling using InputEventManager with Atspi.Device."""
|
"""Activates keyboard handling using InputEventManager with Atspi.Device."""
|
||||||
|
|
||||||
if self._keyHandlingActive:
|
if self._keyHandlingActive:
|
||||||
@@ -140,7 +141,7 @@ class EventManager:
|
|||||||
self._keyHandlingActive = True
|
self._keyHandlingActive = True
|
||||||
debug.printMessage(debug.LEVEL_INFO, 'EVENT MANAGER: Keyboard handling activated', True)
|
debug.printMessage(debug.LEVEL_INFO, 'EVENT MANAGER: Keyboard handling activated', True)
|
||||||
|
|
||||||
def _deactivateKeyHandling(self):
|
def _deactivateKeyHandling(self) -> None:
|
||||||
"""Deactivates keyboard handling."""
|
"""Deactivates keyboard handling."""
|
||||||
|
|
||||||
if not self._keyHandlingActive:
|
if not self._keyHandlingActive:
|
||||||
@@ -153,7 +154,7 @@ class EventManager:
|
|||||||
self._keyHandlingActive = False
|
self._keyHandlingActive = False
|
||||||
debug.printMessage(debug.LEVEL_INFO, 'EVENT MANAGER: Keyboard handling deactivated', True)
|
debug.printMessage(debug.LEVEL_INFO, 'EVENT MANAGER: Keyboard handling deactivated', True)
|
||||||
|
|
||||||
def deactivate(self):
|
def deactivate(self) -> None:
|
||||||
"""Called when this event manager is deactivated."""
|
"""Called when this event manager is deactivated."""
|
||||||
|
|
||||||
debug.printMessage(debug.LEVEL_INFO, 'EVENT MANAGER: Deactivating', True)
|
debug.printMessage(debug.LEVEL_INFO, 'EVENT MANAGER: Deactivating', True)
|
||||||
@@ -163,23 +164,23 @@ class EventManager:
|
|||||||
self._deactivateKeyHandling()
|
self._deactivateKeyHandling()
|
||||||
debug.printMessage(debug.LEVEL_INFO, 'EVENT MANAGER: Deactivated', True)
|
debug.printMessage(debug.LEVEL_INFO, 'EVENT MANAGER: Deactivated', True)
|
||||||
|
|
||||||
def ignoreEventTypes(self, eventTypeList):
|
def ignoreEventTypes(self, eventTypeList: List[str]) -> None:
|
||||||
for eventType in eventTypeList:
|
for eventType in eventTypeList:
|
||||||
if eventType not in self._ignoredEvents:
|
if eventType not in self._ignoredEvents:
|
||||||
self._ignoredEvents.append(eventType)
|
self._ignoredEvents.append(eventType)
|
||||||
|
|
||||||
def unignoreEventTypes(self, eventTypeList):
|
def unignoreEventTypes(self, eventTypeList: List[str]) -> None:
|
||||||
for eventType in eventTypeList:
|
for eventType in eventTypeList:
|
||||||
if eventType in self._ignoredEvents:
|
if eventType in self._ignoredEvents:
|
||||||
self._ignoredEvents.remove(eventType)
|
self._ignoredEvents.remove(eventType)
|
||||||
|
|
||||||
def _isDuplicateEvent(self, event):
|
def _isDuplicateEvent(self, event: Any) -> bool: # event: Atspi.Event
|
||||||
"""Returns True if this event is already in the event queue."""
|
"""Returns True if this event is already in the event queue."""
|
||||||
|
|
||||||
if self._inFlood() and self._prioritizeDuringFlood(event):
|
if self._inFlood() and self._prioritizeDuringFlood(event):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def isSame(x):
|
def isSame(x: Any) -> bool:
|
||||||
return x.type == event.type \
|
return x.type == event.type \
|
||||||
and x.source == event.source \
|
and x.source == event.source \
|
||||||
and x.detail1 == event.detail1 \
|
and x.detail1 == event.detail1 \
|
||||||
@@ -192,7 +193,7 @@ class EventManager:
|
|||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _getAppCmdline(self, app):
|
def _getAppCmdline(self, app: Any) -> str: # app: Atspi.Accessible
|
||||||
pid = AXObject.get_process_id(app)
|
pid = AXObject.get_process_id(app)
|
||||||
if pid == -1:
|
if pid == -1:
|
||||||
return ""
|
return ""
|
||||||
@@ -203,7 +204,7 @@ class EventManager:
|
|||||||
self._cmdlineCache[pid] = cmdline
|
self._cmdlineCache[pid] = cmdline
|
||||||
return cmdline
|
return cmdline
|
||||||
|
|
||||||
def _isSteamApp(self, app):
|
def _isSteamApp(self, app: Any) -> bool: # app: Atspi.Accessible
|
||||||
name = AXObject.get_name(app)
|
name = AXObject.get_name(app)
|
||||||
if not name:
|
if not name:
|
||||||
nameLower = ""
|
nameLower = ""
|
||||||
@@ -216,7 +217,7 @@ class EventManager:
|
|||||||
cmdline = self._getAppCmdline(app)
|
cmdline = self._getAppCmdline(app)
|
||||||
return "steamwebhelper" in cmdline
|
return "steamwebhelper" in cmdline
|
||||||
|
|
||||||
def _isSteamNotificationEvent(self, event):
|
def _isSteamNotificationEvent(self, event: Any) -> bool: # event: Atspi.Event
|
||||||
for obj in (event.any_data, event.source):
|
for obj in (event.any_data, event.source):
|
||||||
if not isinstance(obj, Atspi.Accessible):
|
if not isinstance(obj, Atspi.Accessible):
|
||||||
continue
|
continue
|
||||||
@@ -229,7 +230,7 @@ class EventManager:
|
|||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _ignore(self, event):
|
def _ignore(self, event: Any) -> bool: # event: Atspi.Event
|
||||||
"""Returns True if this event should be ignored."""
|
"""Returns True if this event should be ignored."""
|
||||||
|
|
||||||
app = AXObject.get_application(event.source)
|
app = AXObject.get_application(event.source)
|
||||||
@@ -701,7 +702,7 @@ class EventManager:
|
|||||||
|
|
||||||
return rerun
|
return rerun
|
||||||
|
|
||||||
def registerListener(self, eventType):
|
def registerListener(self, eventType: str) -> None:
|
||||||
"""Tells this module to listen for the given event type.
|
"""Tells this module to listen for the given event type.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
@@ -717,7 +718,7 @@ class EventManager:
|
|||||||
self._listener.register(eventType)
|
self._listener.register(eventType)
|
||||||
self._scriptListenerCounts[eventType] = 1
|
self._scriptListenerCounts[eventType] = 1
|
||||||
|
|
||||||
def deregisterListener(self, eventType):
|
def deregisterListener(self, eventType: str) -> None:
|
||||||
"""Tells this module to stop listening for the given event type.
|
"""Tells this module to stop listening for the given event type.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
@@ -735,7 +736,7 @@ class EventManager:
|
|||||||
self._listener.deregister(eventType)
|
self._listener.deregister(eventType)
|
||||||
del self._scriptListenerCounts[eventType]
|
del self._scriptListenerCounts[eventType]
|
||||||
|
|
||||||
def registerScriptListeners(self, script):
|
def registerScriptListeners(self, script: Any) -> None: # script: Script
|
||||||
"""Tells the event manager to start listening for all the event types
|
"""Tells the event manager to start listening for all the event types
|
||||||
of interest to the script.
|
of interest to the script.
|
||||||
|
|
||||||
@@ -749,7 +750,7 @@ class EventManager:
|
|||||||
for eventType in script.listeners.keys():
|
for eventType in script.listeners.keys():
|
||||||
self.registerListener(eventType)
|
self.registerListener(eventType)
|
||||||
|
|
||||||
def deregisterScriptListeners(self, script):
|
def deregisterScriptListeners(self, script: Any) -> None: # script: Script
|
||||||
"""Tells the event manager to stop listening for all the event types
|
"""Tells the event manager to stop listening for all the event types
|
||||||
of interest to the script.
|
of interest to the script.
|
||||||
|
|
||||||
@@ -797,7 +798,7 @@ class EventManager:
|
|||||||
debug.printMessage(debug.eventDebugLevel, msg, False)
|
debug.printMessage(debug.eventDebugLevel, msg, False)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_scriptForEvent(event):
|
def _get_scriptForEvent(event: Any) -> Optional[Any]: # Returns Optional[Script]
|
||||||
"""Returns the script associated with event."""
|
"""Returns the script associated with event."""
|
||||||
|
|
||||||
if event.type.startswith("mouse:"):
|
if event.type.startswith("mouse:"):
|
||||||
@@ -835,7 +836,7 @@ class EventManager:
|
|||||||
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
||||||
return script
|
return script
|
||||||
|
|
||||||
def _isActivatableEvent(self, event, script=None):
|
def _isActivatableEvent(self, event: Any, script: Optional[Any] = None) -> Tuple[bool, str]:
|
||||||
"""Determines if the event is one which should cause us to
|
"""Determines if the event is one which should cause us to
|
||||||
change which script is currently active.
|
change which script is currently active.
|
||||||
|
|
||||||
@@ -901,7 +902,7 @@ class EventManager:
|
|||||||
|
|
||||||
return False, "No reason found to activate a different script."
|
return False, "No reason found to activate a different script."
|
||||||
|
|
||||||
def _eventSourceIsDead(self, event):
|
def _eventSourceIsDead(self, event: Any) -> bool: # event: Atspi.Event
|
||||||
if AXObject.is_dead(event.source):
|
if AXObject.is_dead(event.source):
|
||||||
tokens = ["EVENT MANAGER: source of", event.type, "is dead"]
|
tokens = ["EVENT MANAGER: source of", event.type, "is dead"]
|
||||||
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
debug.printTokens(debug.LEVEL_INFO, tokens, True)
|
||||||
@@ -909,7 +910,7 @@ class EventManager:
|
|||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _ignoreDuringDeluge(self, event):
|
def _ignoreDuringDeluge(self, event: Any) -> bool: # event: Atspi.Event
|
||||||
"""Returns true if this event should be ignored during a deluge."""
|
"""Returns true if this event should be ignored during a deluge."""
|
||||||
|
|
||||||
if self._eventSourceIsDead(event):
|
if self._eventSourceIsDead(event):
|
||||||
@@ -936,7 +937,7 @@ class EventManager:
|
|||||||
|
|
||||||
return event.source != cthulhu_state.locusOfFocus
|
return event.source != cthulhu_state.locusOfFocus
|
||||||
|
|
||||||
def _inDeluge(self):
|
def _inDeluge(self) -> bool:
|
||||||
size = self._eventQueue.qsize()
|
size = self._eventQueue.qsize()
|
||||||
if size > 100:
|
if size > 100:
|
||||||
msg = f"EVENT MANAGER: DELUGE! Queue size is {size}"
|
msg = f"EVENT MANAGER: DELUGE! Queue size is {size}"
|
||||||
@@ -945,7 +946,7 @@ class EventManager:
|
|||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _processDuringFlood(self, event):
|
def _processDuringFlood(self, event: Any) -> bool: # event: Atspi.Event
|
||||||
"""Returns true if this event should be processed during a flood."""
|
"""Returns true if this event should be processed during a flood."""
|
||||||
|
|
||||||
if self._eventSourceIsDead(event):
|
if self._eventSourceIsDead(event):
|
||||||
@@ -972,7 +973,7 @@ class EventManager:
|
|||||||
|
|
||||||
return event.source == cthulhu_state.locusOfFocus
|
return event.source == cthulhu_state.locusOfFocus
|
||||||
|
|
||||||
def _prioritizeDuringFlood(self, event):
|
def _prioritizeDuringFlood(self, event: Any) -> bool: # event: Atspi.Event
|
||||||
"""Returns true if this event should be prioritized during a flood."""
|
"""Returns true if this event should be prioritized during a flood."""
|
||||||
|
|
||||||
if event.type.startswith("object:state-changed:focused"):
|
if event.type.startswith("object:state-changed:focused"):
|
||||||
@@ -1001,7 +1002,7 @@ class EventManager:
|
|||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _pruneEventsDuringFlood(self):
|
def _pruneEventsDuringFlood(self) -> None:
|
||||||
"""Gets rid of events we don't care about during a flood."""
|
"""Gets rid of events we don't care about during a flood."""
|
||||||
|
|
||||||
oldSize = self._eventQueue.qsize()
|
oldSize = self._eventQueue.qsize()
|
||||||
@@ -1024,7 +1025,7 @@ class EventManager:
|
|||||||
msg = f"EVENT MANAGER: {oldSize - newSize} events pruned. New size: {newSize}"
|
msg = f"EVENT MANAGER: {oldSize - newSize} events pruned. New size: {newSize}"
|
||||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||||
|
|
||||||
def _inFlood(self):
|
def _inFlood(self) -> bool:
|
||||||
size = self._eventQueue.qsize()
|
size = self._eventQueue.qsize()
|
||||||
if size > 50:
|
if size > 50:
|
||||||
msg = f"EVENT MANAGER: FLOOD? Queue size is {size}"
|
msg = f"EVENT MANAGER: FLOOD? Queue size is {size}"
|
||||||
@@ -1132,7 +1133,7 @@ class EventManager:
|
|||||||
msg = f"EVENT MANAGER: {key}: {value}"
|
msg = f"EVENT MANAGER: {key}: {value}"
|
||||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||||
|
|
||||||
def processBrailleEvent(self, brailleEvent):
|
def processBrailleEvent(self, brailleEvent: Any) -> bool: # brailleEvent: BrailleEvent
|
||||||
"""Called whenever a cursor key is pressed on the Braille display.
|
"""Called whenever a cursor key is pressed on the Braille display.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
@@ -1148,9 +1149,9 @@ class EventManager:
|
|||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
_manager = None
|
_manager: Optional[EventManager] = None
|
||||||
|
|
||||||
def getManager():
|
def getManager() -> EventManager:
|
||||||
global _manager
|
global _manager
|
||||||
if _manager is None:
|
if _manager is None:
|
||||||
_manager = cthulhu.cthulhuApp.eventManager
|
_manager = cthulhu.cthulhuApp.eventManager
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ __copyright__ = "Copyright (c) 2005-2008 Sun Microsystems Inc." \
|
|||||||
__license__ = "LGPL"
|
__license__ = "LGPL"
|
||||||
|
|
||||||
import gi
|
import gi
|
||||||
|
from typing import Optional, Dict, Callable, Any
|
||||||
|
|
||||||
gi.require_version("Gtk", "3.0")
|
gi.require_version("Gtk", "3.0")
|
||||||
from gi.repository import Gtk
|
from gi.repository import Gtk
|
||||||
@@ -55,21 +56,21 @@ _settingsManager = None # Removed - use cthulhu.cthulhuApp.settingsManager
|
|||||||
class FlatReviewPresenter:
|
class FlatReviewPresenter:
|
||||||
"""Provides access to on-screen objects via flat-review."""
|
"""Provides access to on-screen objects via flat-review."""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
self._context = None
|
self._context: Optional[flat_review.Context] = None
|
||||||
self._current_contents = ""
|
self._current_contents: str = ""
|
||||||
self._restrict = cthulhu.cthulhuApp.settingsManager.getSetting("flatReviewIsRestricted")
|
self._restrict: bool = cthulhu.cthulhuApp.settingsManager.getSetting("flatReviewIsRestricted")
|
||||||
self._handlers = self._setup_handlers()
|
self._handlers: Dict[str, Callable] = self._setup_handlers()
|
||||||
self._desktop_bindings = self._setup_desktop_bindings()
|
self._desktop_bindings: keybindings.KeyBindings = self._setup_desktop_bindings()
|
||||||
self._laptop_bindings = self._setup_laptop_bindings()
|
self._laptop_bindings: keybindings.KeyBindings = self._setup_laptop_bindings()
|
||||||
self._gui = None
|
self._gui: Optional[Any] = None # Optional[Gtk.Window]
|
||||||
|
|
||||||
def is_active(self):
|
def is_active(self) -> bool:
|
||||||
"""Returns True if the flat review presenter is active."""
|
"""Returns True if the flat review presenter is active."""
|
||||||
|
|
||||||
return self._context is not None
|
return self._context is not None
|
||||||
|
|
||||||
def get_or_create_context(self, script=None):
|
def get_or_create_context(self, script: Optional[Any] = None) -> Optional[flat_review.Context]:
|
||||||
"""Returns the flat review context, creating one if necessary."""
|
"""Returns the flat review context, creating one if necessary."""
|
||||||
|
|
||||||
# TODO - JD: Scripts should not be able to interact with the
|
# TODO - JD: Scripts should not be able to interact with the
|
||||||
|
|||||||
@@ -59,10 +59,10 @@ def _get_ax_utilities():
|
|||||||
from .ax_utilities import AXUtilities
|
from .ax_utilities import AXUtilities
|
||||||
return AXUtilities
|
return AXUtilities
|
||||||
|
|
||||||
def _log(message, reason=None, timestamp=True, stack=False):
|
def _log(message: str, reason: Optional[str] = None, timestamp: bool = True, stack: bool = False) -> None:
|
||||||
debug.print_log(debug.LEVEL_INFO, "FOCUS MANAGER", message, reason, timestamp, stack)
|
debug.print_log(debug.LEVEL_INFO, "FOCUS MANAGER", message, reason, timestamp, stack)
|
||||||
|
|
||||||
def _log_tokens(tokens, reason=None, timestamp=True, stack=False):
|
def _log_tokens(tokens: list, reason: Optional[str] = None, timestamp: bool = True, stack: bool = False) -> None:
|
||||||
debug.print_log_tokens(debug.LEVEL_INFO, "FOCUS MANAGER", tokens, reason, timestamp, stack)
|
debug.print_log_tokens(debug.LEVEL_INFO, "FOCUS MANAGER", tokens, reason, timestamp, stack)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
|||||||
+20
-19
@@ -39,6 +39,7 @@ from gi.repository import Atspi
|
|||||||
|
|
||||||
import math
|
import math
|
||||||
import time
|
import time
|
||||||
|
from typing import Optional, Any
|
||||||
from gi.repository import Gdk
|
from gi.repository import Gdk
|
||||||
from gi.repository import GLib
|
from gi.repository import GLib
|
||||||
|
|
||||||
@@ -54,26 +55,26 @@ from . import settings
|
|||||||
from .ax_object import AXObject
|
from .ax_object import AXObject
|
||||||
from .ax_utilities import AXUtilities
|
from .ax_utilities import AXUtilities
|
||||||
|
|
||||||
KEYBOARD_EVENT = "keyboard"
|
KEYBOARD_EVENT: str = "keyboard"
|
||||||
BRAILLE_EVENT = "braille"
|
BRAILLE_EVENT: str = "braille"
|
||||||
MOUSE_BUTTON_EVENT = "mouse:button"
|
MOUSE_BUTTON_EVENT: str = "mouse:button"
|
||||||
REMOTE_CONTROLLER_EVENT = "remote controller"
|
REMOTE_CONTROLLER_EVENT: str = "remote controller"
|
||||||
|
|
||||||
class InputEvent:
|
class InputEvent:
|
||||||
|
|
||||||
def __init__(self, eventType):
|
def __init__(self, eventType: str) -> None:
|
||||||
"""Creates a new KEYBOARD_EVENT, BRAILLE_EVENT, or MOUSE_BUTTON_EVENT."""
|
"""Creates a new KEYBOARD_EVENT, BRAILLE_EVENT, or MOUSE_BUTTON_EVENT."""
|
||||||
|
|
||||||
self.type = eventType
|
self.type: str = eventType
|
||||||
self.time = time.time()
|
self.time: float = time.time()
|
||||||
self._clickCount = 0
|
self._clickCount: int = 0
|
||||||
|
|
||||||
def get_click_count(self):
|
def get_click_count(self) -> int:
|
||||||
"""Return the count of the number of clicks a user has made."""
|
"""Return the count of the number of clicks a user has made."""
|
||||||
|
|
||||||
return self._clickCount
|
return self._clickCount
|
||||||
|
|
||||||
def set_click_count(self, count=None):
|
def set_click_count(self, count: Optional[int] = None) -> None:
|
||||||
"""Updates the count of the number of clicks a user has made."""
|
"""Updates the count of the number of clicks a user has made."""
|
||||||
|
|
||||||
if count is None:
|
if count is None:
|
||||||
@@ -82,21 +83,21 @@ class InputEvent:
|
|||||||
self._clickCount = count
|
self._clickCount = count
|
||||||
|
|
||||||
class KeyboardEvent(InputEvent):
|
class KeyboardEvent(InputEvent):
|
||||||
stickyKeys = False
|
stickyKeys: bool = False
|
||||||
|
|
||||||
duplicateCount = 0
|
duplicateCount: int = 0
|
||||||
cthulhuModifierPressed = False
|
cthulhuModifierPressed: bool = False
|
||||||
|
|
||||||
# Whether last press of the Cthulhu modifier was alone
|
# Whether last press of the Cthulhu modifier was alone
|
||||||
lastCthulhuModifierAlone = False
|
lastCthulhuModifierAlone: bool = False
|
||||||
lastCthulhuModifierAloneTime = None
|
lastCthulhuModifierAloneTime: Optional[float] = None
|
||||||
# Whether the current press of the Cthulhu modifier is alone
|
# Whether the current press of the Cthulhu modifier is alone
|
||||||
currentCthulhuModifierAlone = False
|
currentCthulhuModifierAlone: bool = False
|
||||||
currentCthulhuModifierAloneTime = None
|
currentCthulhuModifierAloneTime: Optional[float] = None
|
||||||
# When the second cthulhu press happened
|
# When the second cthulhu press happened
|
||||||
secondCthulhuModifierTime = None
|
secondCthulhuModifierTime: Optional[float] = None
|
||||||
# Sticky modifiers state, to be applied to the next keyboard event
|
# Sticky modifiers state, to be applied to the next keyboard event
|
||||||
cthulhuStickyModifiers = 0
|
cthulhuStickyModifiers: int = 0
|
||||||
|
|
||||||
TYPE_UNKNOWN = "unknown"
|
TYPE_UNKNOWN = "unknown"
|
||||||
TYPE_PRINTABLE = "printable"
|
TYPE_PRINTABLE = "printable"
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ __copyright__ = "Copyright (c) 2023 Igalia, S.L." \
|
|||||||
__license__ = "LGPL"
|
__license__ = "LGPL"
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
from typing import Optional, Dict, List, Any, Callable
|
||||||
|
|
||||||
import gi
|
import gi
|
||||||
gi.require_version('Gtk', '3.0')
|
gi.require_version('Gtk', '3.0')
|
||||||
@@ -52,30 +53,30 @@ from . import cthulhu_state
|
|||||||
class NotificationPresenter:
|
class NotificationPresenter:
|
||||||
"""Provides access to the notification history."""
|
"""Provides access to the notification history."""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
self._gui = None
|
self._gui: Optional[Any] = None # Optional[Gtk.Window]
|
||||||
self._handlers = self._setup_handlers()
|
self._handlers: Dict[str, Callable] = self._setup_handlers()
|
||||||
self._bindings = self._setup_bindings()
|
self._bindings: keybindings.KeyBindings = self._setup_bindings()
|
||||||
self._max_size = 55
|
self._max_size: int = 55
|
||||||
|
|
||||||
# The list is arranged with the most recent message being at the end of
|
# The list is arranged with the most recent message being at the end of
|
||||||
# the list. The current index is relative to, and used directly, with the
|
# the list. The current index is relative to, and used directly, with the
|
||||||
# python list, i.e. self._notifications[-3] would return the third-to-last
|
# python list, i.e. self._notifications[-3] would return the third-to-last
|
||||||
# notification message.
|
# notification message.
|
||||||
self._notifications = []
|
self._notifications: List[List[Any]] = [] # List of [message: str, time: float]
|
||||||
self._current_index = -1
|
self._current_index: int = -1
|
||||||
|
|
||||||
def get_bindings(self):
|
def get_bindings(self) -> keybindings.KeyBindings:
|
||||||
"""Returns the notification-presenter keybindings."""
|
"""Returns the notification-presenter keybindings."""
|
||||||
|
|
||||||
return self._bindings
|
return self._bindings
|
||||||
|
|
||||||
def get_handlers(self):
|
def get_handlers(self) -> Dict[str, Callable]:
|
||||||
"""Returns the notification-presenter handlers."""
|
"""Returns the notification-presenter handlers."""
|
||||||
|
|
||||||
return self._handlers
|
return self._handlers
|
||||||
|
|
||||||
def save_notification(self, message):
|
def save_notification(self, message: str) -> None:
|
||||||
"""Adds message to the list of notification messages."""
|
"""Adds message to the list of notification messages."""
|
||||||
|
|
||||||
tokens = ["NOTIFICATION PRESENTER: Adding '", message, "'."]
|
tokens = ["NOTIFICATION PRESENTER: Adding '", message, "'."]
|
||||||
@@ -84,7 +85,7 @@ class NotificationPresenter:
|
|||||||
self._notifications = self._notifications[to_remove:]
|
self._notifications = self._notifications[to_remove:]
|
||||||
self._notifications.append([message, time.time()])
|
self._notifications.append([message, time.time()])
|
||||||
|
|
||||||
def clear_list(self):
|
def clear_list(self) -> None:
|
||||||
"""Clears the notifications list."""
|
"""Clears the notifications list."""
|
||||||
|
|
||||||
msg = "NOTIFICATION PRESENTER: Clearing list."
|
msg = "NOTIFICATION PRESENTER: Clearing list."
|
||||||
@@ -92,7 +93,7 @@ class NotificationPresenter:
|
|||||||
self._notifications = []
|
self._notifications = []
|
||||||
self._current_index = -1
|
self._current_index = -1
|
||||||
|
|
||||||
def _setup_handlers(self):
|
def _setup_handlers(self) -> Dict[str, Callable]:
|
||||||
"""Sets up and returns the notification-presenter input event handlers."""
|
"""Sets up and returns the notification-presenter input event handlers."""
|
||||||
|
|
||||||
handlers = {}
|
handlers = {}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import re
|
|||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
from enum import IntEnum
|
from enum import IntEnum
|
||||||
|
from typing import Optional, Dict, List, Any
|
||||||
|
|
||||||
import pluggy
|
import pluggy
|
||||||
from . import dbus_service
|
from . import dbus_service
|
||||||
@@ -25,23 +26,23 @@ from . import input_event_manager
|
|||||||
from . import keybindings # Added import
|
from . import keybindings # Added import
|
||||||
|
|
||||||
# Set to True for more detailed plugin loading debug info
|
# Set to True for more detailed plugin loading debug info
|
||||||
PLUGIN_DEBUG = True
|
PLUGIN_DEBUG: bool = True
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger: logging.Logger = logging.getLogger(__name__)
|
||||||
if PLUGIN_DEBUG:
|
if PLUGIN_DEBUG:
|
||||||
logger.setLevel(logging.DEBUG)
|
logger.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
LEGACY_PLUGIN_NAME_ALIASES = {
|
LEGACY_PLUGIN_NAME_ALIASES: Dict[str, str] = {
|
||||||
"ocrdesktop": "OCR",
|
"ocrdesktop": "OCR",
|
||||||
}
|
}
|
||||||
|
|
||||||
LEGACY_PLUGIN_DIR_ALIASES = {
|
LEGACY_PLUGIN_DIR_ALIASES: Dict[str, str] = {
|
||||||
"OCRDesktop": "OCR",
|
"OCRDesktop": "OCR",
|
||||||
}
|
}
|
||||||
|
|
||||||
_manager = None
|
_manager: Optional['PluginSystemManager'] = None
|
||||||
|
|
||||||
def getManager():
|
def getManager() -> Optional['PluginSystemManager']:
|
||||||
"""Return the shared PluginSystemManager instance."""
|
"""Return the shared PluginSystemManager instance."""
|
||||||
return _manager
|
return _manager
|
||||||
|
|
||||||
@@ -50,7 +51,7 @@ class PluginType(IntEnum):
|
|||||||
SYSTEM = 1
|
SYSTEM = 1
|
||||||
USER = 2
|
USER = 2
|
||||||
|
|
||||||
def get_root_dir(self):
|
def get_root_dir(self) -> str:
|
||||||
"""Returns the directory where this type of plugins can be found."""
|
"""Returns the directory where this type of plugins can be found."""
|
||||||
if self.value == PluginType.SYSTEM:
|
if self.value == PluginType.SYSTEM:
|
||||||
current_file = inspect.getfile(inspect.currentframe())
|
current_file = inspect.getfile(inspect.currentframe())
|
||||||
@@ -63,75 +64,84 @@ class PluginType(IntEnum):
|
|||||||
class PluginInfo:
|
class PluginInfo:
|
||||||
"""Information about a plugin."""
|
"""Information about a plugin."""
|
||||||
|
|
||||||
def __init__(self, name, module_name, module_dir, metadata=None, canonical_name=None, source_id=None, origin=None):
|
def __init__(
|
||||||
self.name = name
|
self,
|
||||||
self.module_name = module_name
|
name: str,
|
||||||
self.module_dir = module_dir
|
module_name: str,
|
||||||
self.metadata = metadata or {}
|
module_dir: str,
|
||||||
self.canonical_name = canonical_name or module_name
|
metadata: Optional[Dict[str, Any]] = None,
|
||||||
self.source_id = source_id or "unknown"
|
canonical_name: Optional[str] = None,
|
||||||
self.origin = origin or "unknown"
|
source_id: Optional[str] = None,
|
||||||
self.preferred_alias = False
|
origin: Optional[str] = None
|
||||||
self.builtin = False
|
) -> None:
|
||||||
self.hidden = False
|
self.name: str = name
|
||||||
self.module = None
|
self.module_name: str = module_name
|
||||||
self.instance = None
|
self.module_dir: str = module_dir
|
||||||
self.loaded = False
|
self.metadata: Dict[str, Any] = metadata or {}
|
||||||
|
self.canonical_name: str = canonical_name or module_name
|
||||||
|
self.source_id: str = source_id or "unknown"
|
||||||
|
self.origin: str = origin or "unknown"
|
||||||
|
self.preferred_alias: bool = False
|
||||||
|
self.builtin: bool = False
|
||||||
|
self.hidden: bool = False
|
||||||
|
self.module: Optional[Any] = None
|
||||||
|
self.instance: Optional[Any] = None
|
||||||
|
self.loaded: bool = False
|
||||||
|
|
||||||
def get_module_name(self):
|
def get_module_name(self) -> str:
|
||||||
return self.module_name
|
return self.module_name
|
||||||
|
|
||||||
def get_canonical_name(self):
|
def get_canonical_name(self) -> str:
|
||||||
return self.canonical_name
|
return self.canonical_name
|
||||||
|
|
||||||
def get_name(self):
|
def get_name(self) -> str:
|
||||||
return self.metadata.get('name', self.name)
|
return self.metadata.get('name', self.name)
|
||||||
|
|
||||||
def get_version(self):
|
def get_version(self) -> str:
|
||||||
return self.metadata.get('version', '0.0.0')
|
return self.metadata.get('version', '0.0.0')
|
||||||
|
|
||||||
def get_description(self):
|
def get_description(self) -> str:
|
||||||
return self.metadata.get('description', '')
|
return self.metadata.get('description', '')
|
||||||
|
|
||||||
def get_source_id(self):
|
def get_source_id(self) -> str:
|
||||||
return self.source_id
|
return self.source_id
|
||||||
|
|
||||||
def get_origin(self):
|
def get_origin(self) -> str:
|
||||||
return self.origin
|
return self.origin
|
||||||
|
|
||||||
def get_source_label(self):
|
def get_source_label(self) -> str:
|
||||||
if self.origin == "sources":
|
if self.origin == "sources":
|
||||||
return self.source_id
|
return self.source_id
|
||||||
return self.origin
|
return self.origin
|
||||||
|
|
||||||
def get_module_dir(self):
|
def get_module_dir(self) -> str:
|
||||||
return self.module_dir
|
return self.module_dir
|
||||||
|
|
||||||
|
|
||||||
class PluginSystemManager:
|
class PluginSystemManager:
|
||||||
"""Cthulhu Plugin Manager using pluggy."""
|
"""Cthulhu Plugin Manager using pluggy."""
|
||||||
|
|
||||||
def __init__(self, app):
|
def __init__(self, app: Any) -> None: # app is CthulhuApp instance
|
||||||
global _manager
|
global _manager
|
||||||
self.app = app
|
self.app: Any = app
|
||||||
logger.info("Initializing PluginSystemManager")
|
logger.info("Initializing PluginSystemManager")
|
||||||
_manager = self
|
_manager = self
|
||||||
|
|
||||||
# Initialize plugin manager
|
# Initialize plugin manager
|
||||||
logger.info("Setting up plugin manager")
|
logger.info("Setting up plugin manager")
|
||||||
self.plugin_manager = pluggy.PluginManager("cthulhu")
|
self.plugin_manager: pluggy.PluginManager = pluggy.PluginManager("cthulhu")
|
||||||
|
|
||||||
# Define hook specifications
|
# Define hook specifications
|
||||||
hook_spec = pluggy.HookspecMarker("cthulhu")
|
hook_spec = pluggy.HookspecMarker("cthulhu")
|
||||||
|
|
||||||
class CthulhuHookSpecs:
|
class CthulhuHookSpecs:
|
||||||
@hook_spec
|
@hook_spec
|
||||||
def activate(self, plugin=None):
|
def activate(self, plugin: Optional[Any] = None) -> None:
|
||||||
"""Called when the plugin is activated."""
|
"""Called when the plugin is activated."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@hook_spec
|
@hook_spec
|
||||||
def deactivate(self, plugin=None):
|
def deactivate(self, plugin: Optional[Any] = None) -> None:
|
||||||
"""Called when the plugin is deactivated."""
|
"""Called when the plugin is deactivated."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -139,13 +149,13 @@ class PluginSystemManager:
|
|||||||
self.plugin_manager.add_hookspecs(CthulhuHookSpecs)
|
self.plugin_manager.add_hookspecs(CthulhuHookSpecs)
|
||||||
|
|
||||||
# Plugin storage
|
# Plugin storage
|
||||||
self._plugins = {} # module_name -> PluginInfo
|
self._plugins: Dict[str, PluginInfo] = {} # module_name -> PluginInfo
|
||||||
self._plugin_name_index = {} # canonical_name -> [module_name]
|
self._plugin_name_index: Dict[str, List[str]] = {} # canonical_name -> [module_name]
|
||||||
self._active_plugins = []
|
self._active_plugins: List[str] = []
|
||||||
self._plugin_keybindings = {} # plugin_name -> [KeyBinding]
|
self._plugin_keybindings: Dict[str, List[Any]] = {} # plugin_name -> [KeyBinding]
|
||||||
self._global_keybindings = keybindings.KeyBindings()
|
self._global_keybindings: keybindings.KeyBindings = keybindings.KeyBindings()
|
||||||
self._global_bindings = []
|
self._global_bindings: List[Any] = [] # List[KeyBinding]
|
||||||
self._last_active_script = None
|
self._last_active_script: Optional[Any] = None # Optional[Script]
|
||||||
|
|
||||||
# Create plugin directories
|
# Create plugin directories
|
||||||
self._setup_plugin_dirs()
|
self._setup_plugin_dirs()
|
||||||
@@ -153,12 +163,12 @@ class PluginSystemManager:
|
|||||||
# Log available plugins directory paths
|
# Log available plugins directory paths
|
||||||
logger.info(f"System plugins directory: {PluginType.SYSTEM.get_root_dir()}")
|
logger.info(f"System plugins directory: {PluginType.SYSTEM.get_root_dir()}")
|
||||||
logger.info(f"User plugins directory: {PluginType.USER.get_root_dir()}")
|
logger.info(f"User plugins directory: {PluginType.USER.get_root_dir()}")
|
||||||
|
|
||||||
# Connect to active-script-changed signal
|
# Connect to active-script-changed signal
|
||||||
self.app.getSignalManager().connectSignal(
|
self.app.getSignalManager().connectSignal(
|
||||||
'active-script-changed', self._on_active_script_changed, 'default')
|
'active-script-changed', self._on_active_script_changed, 'default')
|
||||||
|
|
||||||
def add_keybinding(self, plugin_name, binding, global_binding=False):
|
def add_keybinding(self, plugin_name: str, binding: Any, global_binding: bool = False) -> None: # binding: KeyBinding
|
||||||
"""Add a keybinding associated with a specific plugin."""
|
"""Add a keybinding associated with a specific plugin."""
|
||||||
if plugin_name not in self._plugin_keybindings:
|
if plugin_name not in self._plugin_keybindings:
|
||||||
self._plugin_keybindings[plugin_name] = []
|
self._plugin_keybindings[plugin_name] = []
|
||||||
@@ -174,14 +184,14 @@ class PluginSystemManager:
|
|||||||
else:
|
else:
|
||||||
logger.warning(f"Failed to create global key grab for {binding.keysymstring}")
|
logger.warning(f"Failed to create global key grab for {binding.keysymstring}")
|
||||||
|
|
||||||
def activate_keybindings_for_plugin(self, plugin_name):
|
def activate_keybindings_for_plugin(self, plugin_name: str) -> None:
|
||||||
"""Activates keybindings for a single plugin with the active script."""
|
"""Activates keybindings for a single plugin with the active script."""
|
||||||
plugin_info = self._plugins.get(plugin_name)
|
plugin_info = self._plugins.get(plugin_name)
|
||||||
if not plugin_info or not plugin_info.loaded:
|
if not plugin_info or not plugin_info.loaded:
|
||||||
return
|
return
|
||||||
self._activate_plugin_keybindings(plugin_info)
|
self._activate_plugin_keybindings(plugin_info)
|
||||||
|
|
||||||
def remove_keybinding(self, plugin_name, binding):
|
def remove_keybinding(self, plugin_name: str, binding: Any) -> None: # binding: KeyBinding
|
||||||
"""Remove a keybinding associated with a specific plugin."""
|
"""Remove a keybinding associated with a specific plugin."""
|
||||||
if plugin_name in self._plugin_keybindings:
|
if plugin_name in self._plugin_keybindings:
|
||||||
if binding in self._plugin_keybindings[plugin_name]:
|
if binding in self._plugin_keybindings[plugin_name]:
|
||||||
@@ -204,7 +214,7 @@ class PluginSystemManager:
|
|||||||
active_script.getKeyBindings().remove(binding)
|
active_script.getKeyBindings().remove(binding)
|
||||||
input_event_manager.get_manager().remove_grabs_for_keybinding(binding)
|
input_event_manager.get_manager().remove_grabs_for_keybinding(binding)
|
||||||
|
|
||||||
def _activate_plugin_keybindings(self, plugin_info):
|
def _activate_plugin_keybindings(self, plugin_info: PluginInfo) -> None:
|
||||||
"""Activates all keybindings for a given plugin with the active script."""
|
"""Activates all keybindings for a given plugin with the active script."""
|
||||||
from . import cthulhu_state # Import here to avoid circular dependency
|
from . import cthulhu_state # Import here to avoid circular dependency
|
||||||
if not cthulhu_state.activeScript:
|
if not cthulhu_state.activeScript:
|
||||||
@@ -227,7 +237,7 @@ class PluginSystemManager:
|
|||||||
logger.warning(f"Failed to create key grab for {binding.keysymstring} for plugin {plugin_name}")
|
logger.warning(f"Failed to create key grab for {binding.keysymstring} for plugin {plugin_name}")
|
||||||
logger.debug(f"Activated keybinding '{binding.asString()}' for plugin '{plugin_name}'")
|
logger.debug(f"Activated keybinding '{binding.asString()}' for plugin '{plugin_name}'")
|
||||||
|
|
||||||
def _deactivate_plugin_keybindings(self, plugin_info):
|
def _deactivate_plugin_keybindings(self, plugin_info: PluginInfo) -> None:
|
||||||
"""Deactivates all keybindings for a given plugin from the active script."""
|
"""Deactivates all keybindings for a given plugin from the active script."""
|
||||||
from . import cthulhu_state # Import here to avoid circular dependency
|
from . import cthulhu_state # Import here to avoid circular dependency
|
||||||
if not cthulhu_state.activeScript:
|
if not cthulhu_state.activeScript:
|
||||||
@@ -252,16 +262,16 @@ class PluginSystemManager:
|
|||||||
input_manager.remove_grabs_for_keybinding(binding)
|
input_manager.remove_grabs_for_keybinding(binding)
|
||||||
logger.debug(f"Deactivated keybinding '{binding.asString()}' for plugin '{plugin_name}'")
|
logger.debug(f"Deactivated keybinding '{binding.asString()}' for plugin '{plugin_name}'")
|
||||||
|
|
||||||
def refresh_active_script_keybindings(self):
|
def refresh_active_script_keybindings(self) -> None:
|
||||||
"""Public method to refresh keybindings for the currently active script."""
|
"""Public method to refresh keybindings for the currently active script."""
|
||||||
from . import cthulhu_state
|
from . import cthulhu_state
|
||||||
if cthulhu_state.activeScript:
|
if cthulhu_state.activeScript:
|
||||||
self._on_active_script_changed(self.app, cthulhu_state.activeScript)
|
self._on_active_script_changed(self.app, cthulhu_state.activeScript)
|
||||||
|
|
||||||
def get_global_keybindings(self):
|
def get_global_keybindings(self) -> keybindings.KeyBindings:
|
||||||
return self._global_keybindings
|
return self._global_keybindings
|
||||||
|
|
||||||
def _on_active_script_changed(self, app, new_script):
|
def _on_active_script_changed(self, app: Any, new_script: Optional[Any]) -> None:
|
||||||
"""Called when the active script changes. Re-applies keybindings for all active plugins."""
|
"""Called when the active script changes. Re-applies keybindings for all active plugins."""
|
||||||
logger.info(f"Active script changed to {new_script.name if new_script else 'None'}. Re-applying plugin keybindings.")
|
logger.info(f"Active script changed to {new_script.name if new_script else 'None'}. Re-applying plugin keybindings.")
|
||||||
|
|
||||||
@@ -287,28 +297,28 @@ class PluginSystemManager:
|
|||||||
if plugin_info and plugin_info.loaded:
|
if plugin_info and plugin_info.loaded:
|
||||||
self._activate_plugin_keybindings(plugin_info)
|
self._activate_plugin_keybindings(plugin_info)
|
||||||
|
|
||||||
def _setup_plugin_dirs(self):
|
def _setup_plugin_dirs(self) -> None:
|
||||||
"""Ensure plugin directories exist."""
|
"""Ensure plugin directories exist."""
|
||||||
os.makedirs(PluginType.SYSTEM.get_root_dir(), exist_ok=True)
|
os.makedirs(PluginType.SYSTEM.get_root_dir(), exist_ok=True)
|
||||||
os.makedirs(PluginType.USER.get_root_dir(), exist_ok=True)
|
os.makedirs(PluginType.USER.get_root_dir(), exist_ok=True)
|
||||||
os.makedirs(self._get_plugin_sources_root(), exist_ok=True)
|
os.makedirs(self._get_plugin_sources_root(), exist_ok=True)
|
||||||
|
|
||||||
def _get_plugin_sources_root(self):
|
def _get_plugin_sources_root(self) -> str:
|
||||||
return os.path.expanduser('~/.local/share/cthulhu/plugin-sources')
|
return os.path.expanduser('~/.local/share/cthulhu/plugin-sources')
|
||||||
|
|
||||||
def _get_additional_plugin_dirs(self):
|
def _get_additional_plugin_dirs(self) -> List[str]:
|
||||||
return [os.path.expanduser('~/.local/share/plugins')]
|
return [os.path.expanduser('~/.local/share/plugins')]
|
||||||
|
|
||||||
def _path_under_root(self, path, root):
|
def _path_under_root(self, path: str, root: str) -> bool:
|
||||||
try:
|
try:
|
||||||
return os.path.commonpath([os.path.abspath(path), os.path.abspath(root)]) == os.path.abspath(root)
|
return os.path.commonpath([os.path.abspath(path), os.path.abspath(root)]) == os.path.abspath(root)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _sanitize_source_id(self, source_id):
|
def _sanitize_source_id(self, source_id: str) -> str:
|
||||||
return re.sub(r'[^a-zA-Z0-9._-]+', '-', source_id).strip('-') or "source"
|
return re.sub(r'[^a-zA-Z0-9._-]+', '-', source_id).strip('-') or "source"
|
||||||
|
|
||||||
def _get_origin_info(self, plugin_dir):
|
def _get_origin_info(self, plugin_dir: str) -> tuple[str, str]:
|
||||||
system_root = PluginType.SYSTEM.get_root_dir()
|
system_root = PluginType.SYSTEM.get_root_dir()
|
||||||
user_root = PluginType.USER.get_root_dir()
|
user_root = PluginType.USER.get_root_dir()
|
||||||
local_root = os.path.expanduser('~/.local/share/plugins')
|
local_root = os.path.expanduser('~/.local/share/plugins')
|
||||||
|
|||||||
@@ -30,41 +30,46 @@ __copyright__ = "Copyright (c) 2011. Cthulhu Team."
|
|||||||
__license__ = "LGPL"
|
__license__ = "LGPL"
|
||||||
|
|
||||||
import importlib
|
import importlib
|
||||||
|
from typing import Optional, Dict, Any
|
||||||
|
|
||||||
from . import debug
|
from . import debug
|
||||||
from . import cthulhu_state
|
from . import cthulhu_state
|
||||||
from .ax_object import AXObject
|
from .ax_object import AXObject
|
||||||
from .scripts import apps, toolkits
|
from .scripts import apps, toolkits
|
||||||
|
|
||||||
|
# Forward references to avoid circular imports
|
||||||
|
# Script is defined in script.py
|
||||||
|
# Atspi.Accessible comes from AT-SPI
|
||||||
|
|
||||||
def _get_ax_utilities():
|
def _get_ax_utilities():
|
||||||
# Avoid circular import with ax_utilities -> ax_utilities_event -> focus_manager -> braille -> settings_manager -> script_manager.
|
# Avoid circular import with ax_utilities -> ax_utilities_event -> focus_manager -> braille -> settings_manager -> script_manager.
|
||||||
from .ax_utilities import AXUtilities
|
from .ax_utilities import AXUtilities
|
||||||
return AXUtilities
|
return AXUtilities
|
||||||
|
|
||||||
def _log(message, reason=None, timestamp=True, stack=False):
|
def _log(message: str, reason: Optional[str] = None, timestamp: bool = True, stack: bool = False) -> None:
|
||||||
debug.print_log(debug.LEVEL_INFO, "SCRIPT MANAGER", message, reason, timestamp, stack)
|
debug.print_log(debug.LEVEL_INFO, "SCRIPT MANAGER", message, reason, timestamp, stack)
|
||||||
|
|
||||||
def _log_tokens(tokens, reason=None, timestamp=True, stack=False):
|
def _log_tokens(tokens: list, reason: Optional[str] = None, timestamp: bool = True, stack: bool = False) -> None:
|
||||||
debug.print_log_tokens(debug.LEVEL_INFO, "SCRIPT MANAGER", tokens, reason, timestamp, stack)
|
debug.print_log_tokens(debug.LEVEL_INFO, "SCRIPT MANAGER", tokens, reason, timestamp, stack)
|
||||||
|
|
||||||
class ScriptManager:
|
class ScriptManager:
|
||||||
|
|
||||||
def __init__(self, app): # Added app argument
|
def __init__(self, app: Any) -> None: # app is the CthulhuApp instance
|
||||||
_log("Initializing")
|
_log("Initializing")
|
||||||
self.app = app # Store app instance
|
self.app: Any = app # Store app instance
|
||||||
self.appScripts = {}
|
self.appScripts: Dict[Any, Any] = {} # Dict[Atspi.Accessible, Script]
|
||||||
self.toolkitScripts = {}
|
self.toolkitScripts: Dict[Any, Dict[str, Any]] = {} # Dict[Atspi.Accessible, Dict[str, Script]]
|
||||||
self.customScripts = {}
|
self.customScripts: Dict[Any, Dict[str, Any]] = {} # Dict[Atspi.Accessible, Dict[str, Script]]
|
||||||
self._sleepModeScripts = {}
|
self._sleepModeScripts: Dict[Any, Any] = {} # Dict[Atspi.Accessible, Script]
|
||||||
self._appModules = apps.__all__
|
self._appModules: list = apps.__all__
|
||||||
self._toolkitModules = toolkits.__all__
|
self._toolkitModules: list = toolkits.__all__
|
||||||
self._defaultScript = None
|
self._defaultScript: Optional[Any] = None # Optional[Script]
|
||||||
self._scriptPackages = \
|
self._scriptPackages: list[str] = \
|
||||||
["cthulhu-scripts",
|
["cthulhu-scripts",
|
||||||
"cthulhu.scripts",
|
"cthulhu.scripts",
|
||||||
"cthulhu.scripts.apps",
|
"cthulhu.scripts.apps",
|
||||||
"cthulhu.scripts.toolkits"]
|
"cthulhu.scripts.toolkits"]
|
||||||
self._appNames = \
|
self._appNames: Dict[str, str] = \
|
||||||
{'Icedove': 'Thunderbird',
|
{'Icedove': 'Thunderbird',
|
||||||
'Nereid': 'Banshee',
|
'Nereid': 'Banshee',
|
||||||
'gnome-calculator': 'gcalctool',
|
'gnome-calculator': 'gcalctool',
|
||||||
@@ -75,14 +80,14 @@ class ScriptManager:
|
|||||||
'metacity': 'switcher',
|
'metacity': 'switcher',
|
||||||
'pluma': 'gedit',
|
'pluma': 'gedit',
|
||||||
}
|
}
|
||||||
self._toolkitNames = \
|
self._toolkitNames: Dict[str, str] = \
|
||||||
{'WebKitGTK': 'WebKitGtk', 'GTK': 'gtk'}
|
{'WebKitGTK': 'WebKitGtk', 'GTK': 'gtk'}
|
||||||
|
|
||||||
self.set_active_script(None, "lifecycle: init")
|
self.set_active_script(None, "lifecycle: init")
|
||||||
self._active = False
|
self._active: bool = False
|
||||||
_log("Initialized")
|
_log("Initialized")
|
||||||
|
|
||||||
def activate(self):
|
def activate(self) -> None:
|
||||||
"""Called when this script manager is activated."""
|
"""Called when this script manager is activated."""
|
||||||
|
|
||||||
_log("Activating")
|
_log("Activating")
|
||||||
@@ -92,7 +97,7 @@ class ScriptManager:
|
|||||||
self._active = True
|
self._active = True
|
||||||
_log("Activated")
|
_log("Activated")
|
||||||
|
|
||||||
def deactivate(self):
|
def deactivate(self) -> None:
|
||||||
"""Called when this script manager is deactivated."""
|
"""Called when this script manager is deactivated."""
|
||||||
|
|
||||||
_log("Deactivating")
|
_log("Deactivating")
|
||||||
@@ -106,7 +111,7 @@ class ScriptManager:
|
|||||||
self._active = False
|
self._active = False
|
||||||
_log("Deactivated")
|
_log("Deactivated")
|
||||||
|
|
||||||
def get_module_name(self, app):
|
def get_module_name(self, app: Optional[Any]) -> Optional[str]: # app: Optional[Atspi.Accessible]
|
||||||
"""Returns the module name of the script to use for application app."""
|
"""Returns the module name of the script to use for application app."""
|
||||||
|
|
||||||
if app is None:
|
if app is None:
|
||||||
@@ -143,19 +148,19 @@ class ScriptManager:
|
|||||||
_log_tokens(["Mapped", app, "to", name])
|
_log_tokens(["Mapped", app, "to", name])
|
||||||
return name
|
return name
|
||||||
|
|
||||||
def _toolkit_for_object(self, obj):
|
def _toolkit_for_object(self, obj: Optional[Any]) -> str: # obj: Optional[Atspi.Accessible]
|
||||||
"""Returns the name of the toolkit associated with obj."""
|
"""Returns the name of the toolkit associated with obj."""
|
||||||
|
|
||||||
name = AXObject.get_attribute(obj, 'toolkit')
|
name = AXObject.get_attribute(obj, 'toolkit')
|
||||||
return self._toolkitNames.get(name, name)
|
return self._toolkitNames.get(name, name)
|
||||||
|
|
||||||
def _script_for_role(self, obj):
|
def _script_for_role(self, obj: Optional[Any]) -> str: # obj: Optional[Atspi.Accessible]
|
||||||
if _get_ax_utilities().is_terminal(obj):
|
if _get_ax_utilities().is_terminal(obj):
|
||||||
return 'terminal'
|
return 'terminal'
|
||||||
|
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
def _new_named_script(self, app, name):
|
def _new_named_script(self, app: Optional[Any], name: Optional[str]) -> Optional[Any]: # Returns Optional[Script]
|
||||||
"""Attempts to locate and load the named module. If successful, returns
|
"""Attempts to locate and load the named module. If successful, returns
|
||||||
a script based on this module."""
|
a script based on this module."""
|
||||||
|
|
||||||
@@ -184,7 +189,7 @@ class ScriptManager:
|
|||||||
|
|
||||||
return script
|
return script
|
||||||
|
|
||||||
def _create_script(self, app, obj=None):
|
def _create_script(self, app: Optional[Any], obj: Optional[Any] = None) -> Any: # Returns Script
|
||||||
"""For the given application, create a new script instance."""
|
"""For the given application, create a new script instance."""
|
||||||
|
|
||||||
moduleName = self.get_module_name(app)
|
moduleName = self.get_module_name(app)
|
||||||
@@ -207,7 +212,7 @@ class ScriptManager:
|
|||||||
|
|
||||||
return script
|
return script
|
||||||
|
|
||||||
def get_default_script(self, app=None):
|
def get_default_script(self, app: Optional[Any] = None) -> Any: # Returns Script
|
||||||
if not app and self._defaultScript:
|
if not app and self._defaultScript:
|
||||||
return self._defaultScript
|
return self._defaultScript
|
||||||
|
|
||||||
@@ -219,7 +224,7 @@ class ScriptManager:
|
|||||||
|
|
||||||
return script
|
return script
|
||||||
|
|
||||||
def sanity_check_script(self, script):
|
def sanity_check_script(self, script: Any) -> Any: # Returns Script
|
||||||
if not self._active:
|
if not self._active:
|
||||||
return script
|
return script
|
||||||
|
|
||||||
@@ -233,7 +238,7 @@ class ScriptManager:
|
|||||||
_log_tokens(["Failed to get a replacement script for", script.app], "replacement-missing")
|
_log_tokens(["Failed to get a replacement script for", script.app], "replacement-missing")
|
||||||
return script
|
return script
|
||||||
|
|
||||||
def get_script_for_mouse_button_event(self, event):
|
def get_script_for_mouse_button_event(self, event: Any) -> Any: # Returns Script
|
||||||
isActive = _get_ax_utilities().is_active(cthulhu_state.activeWindow)
|
isActive = _get_ax_utilities().is_active(cthulhu_state.activeWindow)
|
||||||
_log_tokens([cthulhu_state.activeWindow, "is active:", isActive])
|
_log_tokens([cthulhu_state.activeWindow, "is active:", isActive])
|
||||||
|
|
||||||
@@ -251,10 +256,10 @@ class ScriptManager:
|
|||||||
|
|
||||||
return self.get_script(AXObject.get_application(activeWindow), activeWindow)
|
return self.get_script(AXObject.get_application(activeWindow), activeWindow)
|
||||||
|
|
||||||
def get_active_script(self):
|
def get_active_script(self) -> Optional[Any]: # Returns Optional[Script]
|
||||||
return cthulhu_state.activeScript
|
return cthulhu_state.activeScript
|
||||||
|
|
||||||
def get_script(self, app, obj=None, sanity_check=False):
|
def get_script(self, app: Optional[Any], obj: Optional[Any] = None, sanity_check: bool = False) -> Any: # Returns Script
|
||||||
"""Get a script for an app (and make it if necessary). This is used
|
"""Get a script for an app (and make it if necessary). This is used
|
||||||
instead of a simple calls to Script's constructor.
|
instead of a simple calls to Script's constructor.
|
||||||
|
|
||||||
@@ -312,19 +317,19 @@ class ScriptManager:
|
|||||||
|
|
||||||
return appScript
|
return appScript
|
||||||
|
|
||||||
def get_or_create_sleep_mode_script(self, app):
|
def get_or_create_sleep_mode_script(self, app: Any) -> Any: # Returns Script
|
||||||
"""Gets or creates the sleep mode script."""
|
"""Gets or creates the sleep mode script."""
|
||||||
script = self._sleepModeScripts.get(app)
|
script = self._sleepModeScripts.get(app)
|
||||||
if script is not None:
|
if script is not None:
|
||||||
return script
|
return script
|
||||||
|
|
||||||
# Import sleepmode dynamically to avoid circular imports
|
# Import sleepmode dynamically to avoid circular imports
|
||||||
from .scripts import sleepmode
|
from .scripts import sleepmode
|
||||||
script = sleepmode.Script(app)
|
script = sleepmode.Script(app)
|
||||||
self._sleepModeScripts[app] = script
|
self._sleepModeScripts[app] = script
|
||||||
return script
|
return script
|
||||||
|
|
||||||
def set_active_script(self, newScript, reason=None):
|
def set_active_script(self, newScript: Optional[Any], reason: Optional[str] = None) -> None: # newScript: Optional[Script]
|
||||||
"""Set the new active script.
|
"""Set the new active script.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
@@ -343,20 +348,20 @@ class ScriptManager:
|
|||||||
return
|
return
|
||||||
|
|
||||||
newScript.activate()
|
newScript.activate()
|
||||||
|
|
||||||
# Emit signal that active script has changed, so PluginSystemManager can update keybindings
|
# Emit signal that active script has changed, so PluginSystemManager can update keybindings
|
||||||
from . import cthulhu
|
from . import cthulhu
|
||||||
cthulhu.cthulhuApp.getSignalManager().emitSignal('active-script-changed', newScript)
|
cthulhu.cthulhuApp.getSignalManager().emitSignal('active-script-changed', newScript)
|
||||||
|
|
||||||
_log_tokens(["Setting active script to", newScript], reason)
|
_log_tokens(["Setting active script to", newScript], reason)
|
||||||
self._log_active_state(reason)
|
self._log_active_state(reason)
|
||||||
|
|
||||||
def activate_script_for_context(self, app, obj, reason=None):
|
def activate_script_for_context(self, app: Optional[Any], obj: Optional[Any], reason: Optional[str] = None) -> Any: # Returns Script
|
||||||
script = self.get_script(app, obj)
|
script = self.get_script(app, obj)
|
||||||
self.set_active_script(script, reason)
|
self.set_active_script(script, reason)
|
||||||
return script
|
return script
|
||||||
|
|
||||||
def _log_active_state(self, reason=None):
|
def _log_active_state(self, reason: Optional[str] = None) -> None:
|
||||||
_log_tokens(
|
_log_tokens(
|
||||||
["Active state:", "window", cthulhu_state.activeWindow,
|
["Active state:", "window", cthulhu_state.activeWindow,
|
||||||
"focus", cthulhu_state.locusOfFocus,
|
"focus", cthulhu_state.locusOfFocus,
|
||||||
@@ -364,7 +369,7 @@ class ScriptManager:
|
|||||||
reason
|
reason
|
||||||
)
|
)
|
||||||
|
|
||||||
def _get_script_for_app_replicant(self, app):
|
def _get_script_for_app_replicant(self, app: Any) -> Optional[Any]: # Returns Optional[Script]
|
||||||
if not self._active:
|
if not self._active:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -384,7 +389,7 @@ class ScriptManager:
|
|||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def reclaim_scripts(self):
|
def reclaim_scripts(self) -> None:
|
||||||
"""Compares the list of known scripts to the list of known apps,
|
"""Compares the list of known scripts to the list of known apps,
|
||||||
deleting any scripts as necessary.
|
deleting any scripts as necessary.
|
||||||
"""
|
"""
|
||||||
@@ -432,8 +437,9 @@ class ScriptManager:
|
|||||||
|
|
||||||
del app
|
del app
|
||||||
|
|
||||||
_manager = None
|
_manager: Optional[ScriptManager] = None
|
||||||
def get_manager():
|
|
||||||
|
def get_manager() -> ScriptManager:
|
||||||
"""Returns the Script Manager"""
|
"""Returns the Script Manager"""
|
||||||
|
|
||||||
global _manager
|
global _manager
|
||||||
|
|||||||
+508
-525
File diff suppressed because it is too large
Load Diff
@@ -36,6 +36,7 @@ from gi.repository import Atspi
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from cthulhu import debug
|
from cthulhu import debug
|
||||||
|
from cthulhu import cthulhu
|
||||||
from cthulhu import keybindings
|
from cthulhu import keybindings
|
||||||
from cthulhu import cthulhu_state
|
from cthulhu import cthulhu_state
|
||||||
from cthulhu import script_utilities
|
from cthulhu import script_utilities
|
||||||
|
|||||||
@@ -25,19 +25,27 @@
|
|||||||
|
|
||||||
import gi
|
import gi
|
||||||
from gi.repository import GObject
|
from gi.repository import GObject
|
||||||
|
from typing import Optional, Any, Callable, Tuple
|
||||||
|
|
||||||
from cthulhu import resource_manager
|
from cthulhu import resource_manager
|
||||||
|
|
||||||
class SignalManager():
|
class SignalManager:
|
||||||
def __init__(self, app):
|
def __init__(self, app: Any) -> None: # app is CthulhuApp instance
|
||||||
self.app = app
|
self.app: Any = app
|
||||||
self.resourceManager = self.app.getResourceManager()
|
self.resourceManager: Any = self.app.getResourceManager() # ResourceManager
|
||||||
|
|
||||||
def registerSignal(self, signalName, signalFlag = GObject.SignalFlags.RUN_LAST, closure = GObject.TYPE_NONE, accumulator=(), contextName = None):
|
def registerSignal(
|
||||||
|
self,
|
||||||
|
signalName: str,
|
||||||
|
signalFlag: GObject.SignalFlags = GObject.SignalFlags.RUN_LAST,
|
||||||
|
closure: Any = GObject.TYPE_NONE,
|
||||||
|
accumulator: Tuple = (),
|
||||||
|
contextName: Optional[str] = None
|
||||||
|
) -> bool:
|
||||||
# register signal
|
# register signal
|
||||||
ok = False
|
ok = False
|
||||||
if not self.signalExist(signalName):
|
if not self.signalExist(signalName):
|
||||||
GObject.signal_new(signalName, self.app, signalFlag, closure,accumulator)
|
GObject.signal_new(signalName, self.app, signalFlag, closure, accumulator)
|
||||||
ok = True
|
ok = True
|
||||||
resourceContext = self.resourceManager.getResourceContext(contextName)
|
resourceContext = self.resourceManager.getResourceContext(contextName)
|
||||||
if resourceContext:
|
if resourceContext:
|
||||||
@@ -45,9 +53,17 @@ class SignalManager():
|
|||||||
resourceContext.addSignal(signalName, resourceEntry)
|
resourceContext.addSignal(signalName, resourceEntry)
|
||||||
return ok
|
return ok
|
||||||
|
|
||||||
def signalExist(self, signalName):
|
def signalExist(self, signalName: str) -> bool:
|
||||||
return GObject.signal_lookup(signalName, self.app) != 0
|
return GObject.signal_lookup(signalName, self.app) != 0
|
||||||
def connectSignal(self, signalName, function, profile, param = None, contextName = None):
|
|
||||||
|
def connectSignal(
|
||||||
|
self,
|
||||||
|
signalName: str,
|
||||||
|
function: Callable,
|
||||||
|
profile: Any,
|
||||||
|
param: Optional[Any] = None,
|
||||||
|
contextName: Optional[str] = None
|
||||||
|
) -> Optional[int]:
|
||||||
signalID = None
|
signalID = None
|
||||||
try:
|
try:
|
||||||
if self.signalExist(signalName):
|
if self.signalExist(signalName):
|
||||||
@@ -63,7 +79,8 @@ class SignalManager():
|
|||||||
print(e)
|
print(e)
|
||||||
|
|
||||||
return signalID
|
return signalID
|
||||||
def disconnectSignalByFunction(self, function, contextName = None):
|
|
||||||
|
def disconnectSignalByFunction(self, function: Callable, contextName: Optional[str] = None) -> bool:
|
||||||
ok = False
|
ok = False
|
||||||
try:
|
try:
|
||||||
self.app.disconnect_by_func(function)
|
self.app.disconnect_by_func(function)
|
||||||
@@ -74,7 +91,8 @@ class SignalManager():
|
|||||||
if resourceContext:
|
if resourceContext:
|
||||||
resourceContext.removeSubscriptionByFunction(function)
|
resourceContext.removeSubscriptionByFunction(function)
|
||||||
return ok
|
return ok
|
||||||
def emitSignal(self, signalName, *args):
|
|
||||||
|
def emitSignal(self, signalName: str, *args: Any) -> None:
|
||||||
# emit a signal with optional arguments
|
# emit a signal with optional arguments
|
||||||
try:
|
try:
|
||||||
self.app.emit(signalName, *args)
|
self.app.emit(signalName, *args)
|
||||||
|
|||||||
+15
-14
@@ -33,12 +33,13 @@ __license__ = "LGPL"
|
|||||||
|
|
||||||
import gi
|
import gi
|
||||||
from gi.repository import GLib
|
from gi.repository import GLib
|
||||||
|
from typing import Optional, Any
|
||||||
|
|
||||||
try:
|
try:
|
||||||
gi.require_version('Gst', '1.0')
|
gi.require_version('Gst', '1.0')
|
||||||
from gi.repository import Gst
|
from gi.repository import Gst
|
||||||
except Exception:
|
except Exception:
|
||||||
_gstreamerAvailable = False
|
_gstreamerAvailable: bool = False
|
||||||
else:
|
else:
|
||||||
_gstreamerAvailable, args = Gst.init_check(None)
|
_gstreamerAvailable, args = Gst.init_check(None)
|
||||||
|
|
||||||
@@ -48,12 +49,12 @@ from .sound_generator import Icon, Tone
|
|||||||
class Player:
|
class Player:
|
||||||
"""Plays Icons and Tones."""
|
"""Plays Icons and Tones."""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
self._initialized = False
|
self._initialized: bool = False
|
||||||
self._source = None
|
self._source: Optional[Any] = None # Optional[Gst.Element]
|
||||||
self._sink = None
|
self._sink: Optional[Any] = None # Optional[Gst.Element]
|
||||||
self._player = None
|
self._player: Optional[Any] = None # Optional[Gst.Element]
|
||||||
self._pipeline = None
|
self._pipeline: Optional[Any] = None # Optional[Gst.Pipeline]
|
||||||
|
|
||||||
if not _gstreamerAvailable:
|
if not _gstreamerAvailable:
|
||||||
msg = 'SOUND ERROR: Gstreamer is not available'
|
msg = 'SOUND ERROR: Gstreamer is not available'
|
||||||
@@ -62,7 +63,7 @@ class Player:
|
|||||||
|
|
||||||
self.init()
|
self.init()
|
||||||
|
|
||||||
def _onPlayerMessage(self, bus, message):
|
def _onPlayerMessage(self, bus: Any, message: Any) -> None: # bus: Gst.Bus, message: Gst.Message
|
||||||
if message.type == Gst.MessageType.EOS:
|
if message.type == Gst.MessageType.EOS:
|
||||||
self._player.set_state(Gst.State.NULL)
|
self._player.set_state(Gst.State.NULL)
|
||||||
elif message.type == Gst.MessageType.ERROR:
|
elif message.type == Gst.MessageType.ERROR:
|
||||||
@@ -71,7 +72,7 @@ class Player:
|
|||||||
msg = f'SOUND ERROR: {error}'
|
msg = f'SOUND ERROR: {error}'
|
||||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||||
|
|
||||||
def _onPipelineMessage(self, bus, message):
|
def _onPipelineMessage(self, bus: Any, message: Any) -> None: # bus: Gst.Bus, message: Gst.Message
|
||||||
if message.type == Gst.MessageType.EOS:
|
if message.type == Gst.MessageType.EOS:
|
||||||
self._pipeline.set_state(Gst.State.NULL)
|
self._pipeline.set_state(Gst.State.NULL)
|
||||||
elif message.type == Gst.MessageType.ERROR:
|
elif message.type == Gst.MessageType.ERROR:
|
||||||
@@ -80,11 +81,11 @@ class Player:
|
|||||||
msg = f'SOUND ERROR: {error}'
|
msg = f'SOUND ERROR: {error}'
|
||||||
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
debug.printMessage(debug.LEVEL_INFO, msg, True)
|
||||||
|
|
||||||
def _onTimeout(self, element):
|
def _onTimeout(self, element: Any) -> bool: # element: Gst.Element
|
||||||
element.set_state(Gst.State.NULL)
|
element.set_state(Gst.State.NULL)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _playIcon(self, icon, interrupt=True):
|
def _playIcon(self, icon: Icon, interrupt: bool = True) -> None:
|
||||||
"""Plays a sound icon, interrupting the current play first unless specified."""
|
"""Plays a sound icon, interrupting the current play first unless specified."""
|
||||||
|
|
||||||
if interrupt:
|
if interrupt:
|
||||||
@@ -93,7 +94,7 @@ class Player:
|
|||||||
self._player.set_property('uri', f'file://{icon.path}')
|
self._player.set_property('uri', f'file://{icon.path}')
|
||||||
self._player.set_state(Gst.State.PLAYING)
|
self._player.set_state(Gst.State.PLAYING)
|
||||||
|
|
||||||
def _playIconAndWait(self, icon, interrupt=True, timeout_seconds=10):
|
def _playIconAndWait(self, icon: Icon, interrupt: bool = True, timeout_seconds: Optional[int] = 10) -> bool:
|
||||||
"""Plays a sound icon and waits for completion."""
|
"""Plays a sound icon and waits for completion."""
|
||||||
|
|
||||||
if interrupt:
|
if interrupt:
|
||||||
@@ -123,7 +124,7 @@ class Player:
|
|||||||
self._player.set_state(Gst.State.NULL)
|
self._player.set_state(Gst.State.NULL)
|
||||||
return message is not None and message.type == Gst.MessageType.EOS
|
return message is not None and message.type == Gst.MessageType.EOS
|
||||||
|
|
||||||
def _playTone(self, tone, interrupt=True):
|
def _playTone(self, tone: Tone, interrupt: bool = True) -> None:
|
||||||
"""Plays a tone, interrupting the current play first unless specified."""
|
"""Plays a tone, interrupting the current play first unless specified."""
|
||||||
|
|
||||||
if interrupt:
|
if interrupt:
|
||||||
@@ -136,7 +137,7 @@ class Player:
|
|||||||
duration = int(1000 * tone.duration)
|
duration = int(1000 * tone.duration)
|
||||||
GLib.timeout_add(duration, self._onTimeout, self._pipeline)
|
GLib.timeout_add(duration, self._onTimeout, self._pipeline)
|
||||||
|
|
||||||
def init(self):
|
def init(self) -> None:
|
||||||
"""(Re)Initializes the Player."""
|
"""(Re)Initializes the Player."""
|
||||||
|
|
||||||
if self._initialized:
|
if self._initialized:
|
||||||
|
|||||||
Reference in New Issue
Block a user