# Orca
#
# Copyright 2005-2008 Sun Microsystems Inc.
# Copyright 2011-2016 Igalia, S.L.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the
# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
# Boston MA 02110-1301 USA.
# pylint: disable=broad-exception-caught
# pylint: disable=wrong-import-position
# pylint: disable=too-many-public-methods
# pylint: disable=too-many-instance-attributes
"""Provides support for handling input events."""
# This has to be the first non-docstring line in the module to make linters happy.
from __future__ import annotations
__id__ = "$Id$"
__version__ = "$Revision$"
__date__ = "$Date$"
__copyright__ = "Copyright (c) 2005-2008 Sun Microsystems Inc." \
"Copyright (c) 2011-2016 Igalia, S.L."
__license__ = "LGPL"
import inspect
import math
import time
from typing import Any, Callable, Optional, TYPE_CHECKING, cast
import gi
gi.require_version("Atspi", "2.0")
gi.require_version("Gdk", "3.0")
from gi.repository import Atspi
from gi.repository import Gdk
from gi.repository import GLib
from . import debug
from . import focus_manager
from . import keybindings
from . import keynames
from . import messages
from . import orca_modifier_manager
from . import script_manager
from . import settings
from .ax_utilities import AXUtilities
if TYPE_CHECKING:
from .scripts import default
KEYBOARD_EVENT = "keyboard"
BRAILLE_EVENT = "braille"
MOUSE_BUTTON_EVENT = "mouse:button"
class InputEvent:
"""Provides support for handling input events."""
def __init__(self, event_type: str) -> None:
"""Creates a new KEYBOARD_EVENT, BRAILLE_EVENT, or MOUSE_BUTTON_EVENT."""
self.type: str = event_type
self.time: float = time.time()
self._click_count: int = 0
def get_click_count(self) -> int:
"""Return the count of the number of clicks a user has made."""
return self._click_count
def set_click_count(self, count: int) -> None:
"""Updates the count of the number of clicks a user has made."""
self._click_count = count
def as_single_line_string(self) -> str:
"""Returns a single-line string representation of this event."""
return f"{self.type}"
class KeyboardEvent(InputEvent):
"""Provides support for handling keyboard events."""
# pylint:disable=too-many-arguments
# pylint:disable=too-many-positional-arguments
def __init__(
self,
pressed: bool,
keycode: int,
keysym: int,
modifiers: int,
text: str
) -> None:
"""Creates a new InputEvent of type KEYBOARD_EVENT.
Arguments:
- pressed: True if this is a key press, False for a release.
- keycode: the hardware keycode.
- keysym: the translated keysym.
- modifiers: a bitflag giving the active modifiers.
- text: the text that would be inserted if this key is pressed.
"""
super().__init__(KEYBOARD_EVENT)
self.id: int = keysym
self.type: Atspi.EventType = (
Atspi.EventType.KEY_PRESSED_EVENT if pressed else Atspi.EventType.KEY_RELEASED_EVENT
)
self.hw_code: int = keycode
self._text: str = text
self.modifiers: int = modifiers & Gdk.ModifierType.MODIFIER_MASK
if modifiers & (1 << Atspi.ModifierType.NUMLOCK):
self.modifiers |= (1 << Atspi.ModifierType.NUMLOCK)
self.keyval_name: str = Gdk.keyval_name(keysym)
self.timestamp: float = time.time()
self._script: Optional[default.Script] = None
self._window: Optional[Atspi.Accessible] = None
self._obj: Optional[Atspi.Accessible] = None
self._handler: Optional[InputEventHandler] = None
self._consumer: Optional[Callable[..., bool]] = None
self._is_kp_with_numlock: bool = False
# Some implementors don't include numlock in the modifiers. Unfortunately,
# trying to heuristically hack around this just by looking at the event
# is not reliable. Ditto regarding asking Gdk for the numlock state.
if self.keyval_name.startswith("KP"):
if self.modifiers & (1 << Atspi.ModifierType.NUMLOCK):
self._is_kp_with_numlock = True
modifier_manager = orca_modifier_manager.get_manager()
if self.is_orca_modifier():
modifier_manager.set_pressed_state(pressed)
if modifier_manager.get_pressed_state():
self.modifiers |= keybindings.ORCA_MODIFIER_MASK
# pylint:enable=too-many-arguments
# pylint:enable=too-many-positional-arguments
def __eq__(self, other: object) -> bool:
if not isinstance(other, KeyboardEvent):
return False
if self.type == other.type and self.hw_code == other.hw_code:
return self.timestamp == other.timestamp
return False
def __str__(self) -> str:
if self._should_obscure():
keyid = hw_code = modifiers = text = keyval_name = "*"
else:
keyid = str(self.id)
hw_code = str(self.hw_code)
modifiers = str(self.modifiers)
keyval_name = self.keyval_name
text = self._text
return f"KEYBOARD_EVENT: type={self.type.value_name.upper()}\n" \
+ f" id={keyid}\n" \
+ f" hw_code={hw_code}\n" \
+ f" modifiers={modifiers}\n" \
+ f" text='{text}'\n" \
+ f" keyval_name='{keyval_name}'\n" \
+ f" timestamp={self.timestamp}\n" \
+ f" clickCount={self._click_count}"
def as_single_line_string(self) -> str:
"""Returns a single-line string representation of this event."""
if self._should_obscure():
return "(obscured)"
return (
f"'{self.keyval_name}' ({self.hw_code}) mods: {self.modifiers} "
f"{self.type.value_nick}"
)
def _should_obscure(self) -> bool:
"""Returns True if we should obscure the details of this event."""
if not AXUtilities.is_password_text(self._obj):
return False
if not self.is_printable_key():
return False
if self.modifiers & keybindings.CTRL_MODIFIER_MASK \
or self.modifiers & keybindings.ALT_MODIFIER_MASK \
or self.modifiers & keybindings.ORCA_MODIFIER_MASK:
return False
return True
def is_navigation_key(self) -> bool:
"""Return True if this is a navigation key."""
keys = [
Gdk.KEY_Down,
Gdk.KEY_End,
Gdk.KEY_Home,
Gdk.KEY_Left,
Gdk.KEY_Right,
Gdk.KEY_Up,
]
return self.id in keys
def is_action_key(self) -> bool:
"""Return True if this is an action key."""
keys = [
Gdk.KEY_BackSpace,
Gdk.KEY_Delete,
Gdk.KEY_Escape,
Gdk.KEY_Page_Down,
Gdk.KEY_Page_Up,
Gdk.KEY_Return,
Gdk.KEY_Tab,
]
return self.id in keys
def is_alphabetic_key(self) -> bool:
"""Return True if this is an alphabetic key."""
name = self.get_key_name()
if not len(name) == 1:
return False
return name.isalpha()
def is_diacritical_key(self) -> bool:
"""Return True if this is a non-spacing diacritical key."""
keys = [
Gdk.KEY_dead_A,
Gdk.KEY_dead_a,
Gdk.KEY_dead_abovecomma,
Gdk.KEY_dead_abovedot,
Gdk.KEY_dead_abovereversedcomma,
Gdk.KEY_dead_abovering,
Gdk.KEY_dead_aboveverticalline,
Gdk.KEY_dead_acute,
Gdk.KEY_dead_belowbreve,
Gdk.KEY_dead_belowcircumflex,
Gdk.KEY_dead_belowcomma,
Gdk.KEY_dead_belowdiaeresis,
Gdk.KEY_dead_belowdot,
Gdk.KEY_dead_belowmacron,
Gdk.KEY_dead_belowring,
Gdk.KEY_dead_belowtilde,
Gdk.KEY_dead_belowverticalline,
Gdk.KEY_dead_breve,
Gdk.KEY_dead_capital_schwa,
Gdk.KEY_dead_caron,
Gdk.KEY_dead_cedilla,
Gdk.KEY_dead_circumflex,
Gdk.KEY_dead_currency,
Gdk.KEY_dead_dasia,
Gdk.KEY_dead_diaeresis,
Gdk.KEY_dead_doubleacute,
Gdk.KEY_dead_doublegrave,
Gdk.KEY_dead_E,
Gdk.KEY_dead_e,
Gdk.KEY_dead_grave,
Gdk.KEY_dead_greek,
Gdk.KEY_dead_hook,
Gdk.KEY_dead_horn,
Gdk.KEY_dead_I,
Gdk.KEY_dead_i,
Gdk.KEY_dead_invertedbreve,
Gdk.KEY_dead_iota,
Gdk.KEY_dead_longsolidusoverlay,
Gdk.KEY_dead_lowline,
Gdk.KEY_dead_macron,
Gdk.KEY_dead_O,
Gdk.KEY_dead_o,
Gdk.KEY_dead_ogonek,
Gdk.KEY_dead_perispomeni,
Gdk.KEY_dead_psili,
Gdk.KEY_dead_semivoiced_sound,
Gdk.KEY_dead_small_schwa,
Gdk.KEY_dead_stroke,
Gdk.KEY_dead_tilde,
Gdk.KEY_dead_U,
Gdk.KEY_dead_u,
Gdk.KEY_dead_voiced_sound,
]
return self.id in keys
def is_function_key(self) -> bool:
"""Return True if this is a function key."""
keys = [
Gdk.KEY_F1,
Gdk.KEY_F2,
Gdk.KEY_F3,
Gdk.KEY_F4,
Gdk.KEY_F5,
Gdk.KEY_F6,
Gdk.KEY_F7,
Gdk.KEY_F8,
Gdk.KEY_F9,
Gdk.KEY_F10,
Gdk.KEY_F11,
Gdk.KEY_F12,
]
return self.id in keys
def is_locking_key(self) -> bool:
"""Return True if this is a locking key."""
if self.is_orca_modifier():
return self._click_count == 2
keys = [
Gdk.KEY_Caps_Lock,
Gdk.KEY_Num_Lock,
Gdk.KEY_Scroll_Lock,
Gdk.KEY_Shift_Lock,
]
return self.id in keys
def is_modifier_key(self) -> bool:
"""Return True if this is a modifier key."""
keys = [
Gdk.KEY_Alt_L,
Gdk.KEY_Alt_R,
Gdk.KEY_Control_L,
Gdk.KEY_Control_R,
Gdk.KEY_Meta_L,
Gdk.KEY_Meta_R,
Gdk.KEY_Shift_L,
Gdk.KEY_Shift_R,
Gdk.KEY_ISO_Level3_Shift,
]
return self.id in keys or self.is_orca_modifier()
def is_numeric_key(self) -> bool:
"""Return True if this is a numeric key."""
keys = [
Gdk.KEY_0,
Gdk.KEY_1,
Gdk.KEY_2,
Gdk.KEY_3,
Gdk.KEY_4,
Gdk.KEY_5,
Gdk.KEY_6,
Gdk.KEY_7,
Gdk.KEY_8,
Gdk.KEY_9,
]
return self.id in keys
def is_orca_modifier(self) -> bool:
"""Return True if this is the Orca modifier key."""
if self.id == Gdk.KEY_KP_0 and self.modifiers & keybindings.SHIFT_MODIFIER_MASK:
return orca_modifier_manager.get_manager().is_orca_modifier("KP_Insert")
return orca_modifier_manager.get_manager().is_orca_modifier(self.keyval_name)
def is_orca_modified(self) -> bool:
"""Return True if this key is Orca modified."""
if self.is_orca_modifier():
return False
return bool(self.modifiers & keybindings.ORCA_MODIFIER_MASK)
def is_keypad_key_with_numlock_on(self) -> bool:
"""Return True if this is a key pad key with numlock on."""
return self._is_kp_with_numlock
def is_printable_key(self) -> bool:
"""Return True if this is a printable key."""
name = self.get_key_name()
if not len(name) == 1:
return False
return name.isprintable()
def is_pressed_key(self) -> bool:
"""Returns True if the key is pressed"""
return self.type == Atspi.EventType.KEY_PRESSED_EVENT
def is_punctuation_key(self) -> bool:
"""Return True if this is a punctuation key."""
keys = [
Gdk.KEY_acute,
Gdk.KEY_ampersand,
Gdk.KEY_apostrophe,
Gdk.KEY_asciicircum,
Gdk.KEY_asciitilde,
Gdk.KEY_asterisk,
Gdk.KEY_at,
Gdk.KEY_backslash,
Gdk.KEY_bar,
Gdk.KEY_braceleft,
Gdk.KEY_braceright,
Gdk.KEY_bracketleft,
Gdk.KEY_bracketright,
Gdk.KEY_brokenbar,
Gdk.KEY_cedilla,
Gdk.KEY_cent,
Gdk.KEY_colon,
Gdk.KEY_comma,
Gdk.KEY_copyright,
Gdk.KEY_currency,
Gdk.KEY_degree,
Gdk.KEY_diaeresis,
Gdk.KEY_dollar,
Gdk.KEY_EuroSign,
Gdk.KEY_equal,
Gdk.KEY_exclam,
Gdk.KEY_exclamdown,
Gdk.KEY_grave,
Gdk.KEY_greater,
Gdk.KEY_guillemotleft,
Gdk.KEY_guillemotright,
Gdk.KEY_hyphen,
Gdk.KEY_less,
Gdk.KEY_macron,
Gdk.KEY_minus,
Gdk.KEY_notsign,
Gdk.KEY_numbersign,
Gdk.KEY_paragraph,
Gdk.KEY_parenleft,
Gdk.KEY_parenright,
Gdk.KEY_percent,
Gdk.KEY_period,
Gdk.KEY_periodcentered,
Gdk.KEY_plus,
Gdk.KEY_plusminus,
Gdk.KEY_question,
Gdk.KEY_questiondown,
Gdk.KEY_quotedbl,
Gdk.KEY_quoteleft,
Gdk.KEY_quoteright,
Gdk.KEY_registered,
Gdk.KEY_section,
Gdk.KEY_semicolon,
Gdk.KEY_slash,
Gdk.KEY_sterling,
Gdk.KEY_underscore,
Gdk.KEY_yen,
]
return self.id in keys
def is_space(self) -> bool:
"""Return True if this is the space key."""
return self.id == Gdk.KEY_space
def is_character_echoable(self) -> bool:
"""Returns True if the script will echo this event as part of character echo."""
if not settings.enableEchoByCharacter:
msg = "KEYBOARD EVENT: Not character echoable, setting disabled."
debug.print_message(debug.LEVEL_INFO, msg, True)
return False
if not self.is_printable_key():
msg = "KEYBOARD EVENT: Not character echoable, is not printable key."
debug.print_message(debug.LEVEL_INFO, msg, True)
return False
# TODO - JD: What is this check handling specifically?
if self.modifiers & keybindings.ORCA_CTRL_MODIFIER_MASK:
msg = "KEYBOARD EVENT: Not character echoable due to modifier mask."
debug.print_message(debug.LEVEL_INFO, msg, True)
return False
if AXUtilities.is_password_text(self._obj):
msg = "KEYBOARD EVENT: Not character echoable, is password text."
debug.print_message(debug.LEVEL_INFO, msg, True)
return False
if AXUtilities.is_editable(self._obj) or AXUtilities.is_terminal(self._obj):
msg = "KEYBOARD EVENT: Character echoable, is editable or terminal."
debug.print_message(debug.LEVEL_INFO, msg, True)
return True
msg = "KEYBOARD EVENT: Not character echoable, no reason to echo."
debug.print_message(debug.LEVEL_INFO, msg, True)
return False
def get_locking_state(self) -> Optional[bool]:
"""Returns True if the event locked a locking key, False if the event unlocked it, and None
if not a locking key."""
if not self.is_locking_key():
return None
if self.id == Gdk.KEY_Caps_Lock:
mod = Atspi.ModifierType.SHIFTLOCK
elif self.id == Gdk.KEY_Shift_Lock:
mod = Atspi.ModifierType.SHIFT
elif self.id == Gdk.KEY_Num_Lock:
mod = Atspi.ModifierType.NUMLOCK
else:
return None
return not self.modifiers & (1 << mod)
def get_locking_state_string(self) -> str:
"""Returns the string reflecting the locking state."""
locked = self.get_locking_state()
if locked is None:
return ""
if not locked:
return messages.LOCKING_KEY_STATE_OFF
return messages.LOCKING_KEY_STATE_ON
def get_key_name(self) -> str:
"""Returns the string to be used for presenting the key."""
if self._text.strip() and self._text.isprintable():
return self._text
name = keynames.get_key_name(self.keyval_name)
if name is not None:
return name
unicode_codepoint = Gdk.keyval_to_unicode(self.id)
if unicode_codepoint:
char = chr(unicode_codepoint)
if char.isprintable():
return char
return self.keyval_name
def get_object(self) -> Optional[Atspi.Accessible]:
"""Returns the object believed to be associated with this key event."""
return self._obj
def set_object(self, obj: Optional[Atspi.Accessible]) -> None:
"""Sets the object believed to be associated with this key event."""
module_name = inspect.getmodulename(inspect.stack()[1].filename)
if not (module_name and module_name.startswith("input_event")):
raise PermissionError("Unauthorized setter of input event property")
self._obj = obj
def get_window(self) -> Optional[Atspi.Accessible]:
"""Returns the window believed to be associated with this key event."""
return self._window
def set_window(self, window: Optional[Atspi.Accessible]) -> None:
"""Sets the window believed to be associated with this key event."""
module_name = inspect.getmodulename(inspect.stack()[1].filename)
if not (module_name and module_name.startswith("input_event")):
raise PermissionError("Unauthorized setter of input event property")
self._window = window
def get_script(self) -> Optional[default.Script]:
"""Returns the script believed to be associated with this key event."""
return self._script
def set_script(self, script: Optional[default.Script]) -> None:
"""Sets the script believed to be associated with this key event."""
module_name = inspect.getmodulename(inspect.stack()[1].filename)
if not (module_name and module_name.startswith("input_event")):
raise PermissionError("Unauthorized setter of input event property")
self._script = script
def get_handler(self) -> Optional[InputEventHandler]:
"""Returns the handler associated with this key event."""
return self._handler
def _get_user_handler(self) -> Optional[InputEventHandler]:
# TODO - JD: This should go away once plugin support is in place.
try:
bindings = settings.keyBindingsMap.get(self._script.__module__)
except Exception:
bindings = None
if not bindings:
try:
bindings = settings.keyBindingsMap.get("default")
except Exception:
bindings = None
if bindings is None:
return None
try:
handler = bindings.get_input_handler(self)
except Exception:
handler = None
return handler
def _present(self) -> bool:
if not self._script:
return False
if self.is_pressed_key():
self._script.presentationInterrupt()
if self._script.get_learn_mode_presenter().is_active():
return False
return self._script.presentKeyboardEvent(self)
# pylint:disable=too-many-branches
# pylint:disable=too-many-return-statements
def should_echo(self) -> bool:
"""Returns True if this input event should be echoed."""
if not (self.is_pressed_key() or AXUtilities.is_terminal(self._obj)):
return False
if self.is_locking_key():
if settings.presentLockingKeys is None:
return not settings.onlySpeakDisplayedText
return settings.presentLockingKeys
if not settings.enableKeyEcho:
return False
if self.is_navigation_key():
return settings.enableNavigationKeys
if self.is_action_key():
return settings.enableActionKeys
if self.is_modifier_key():
return settings.enableModifierKeys
if self.is_function_key():
return settings.enableFunctionKeys
if self.is_diacritical_key():
if settings.enableDiacriticalKeys is None:
return not settings.onlySpeakDisplayedText
return settings.enableDiacriticalKeys
if self.is_alphabetic_key():
return settings.enableAlphabeticKeys or settings.enableEchoByCharacter
if self.is_numeric_key():
return settings.enableNumericKeys or settings.enableEchoByCharacter
if self.is_punctuation_key():
return settings.enablePunctuationKeys or settings.enableEchoByCharacter
if self.is_space():
return settings.enableSpace or settings.enableEchoByCharacter
return False
# pylint:enable=too-many-branches
# pylint:enable=too-many-return-statements
def process(self) -> None:
"""Processes this input event."""
start_time = time.time()
if not self._should_obscure():
data = f"'{self.keyval_name}' ({self.hw_code})"
else:
data = "(obscured)"
debug.print_message(debug.LEVEL_INFO, f"\n{self}")
msg = f'\nvvvvv PROCESS {self.type.value_name.upper()}: {data} vvvvv'
debug.print_message(debug.LEVEL_INFO, msg, False)
tokens = ["SCRIPT:", self._script]
debug.print_tokens(debug.LEVEL_INFO, tokens, True)
tokens = ["WINDOW:", self._window]
debug.print_tokens(debug.LEVEL_INFO, tokens, True)
tokens = ["LOCATION:", self._obj]
debug.print_tokens(debug.LEVEL_INFO, tokens, True)
if self._script:
self._handler = self._get_user_handler() \
or self._script.key_bindings.get_input_handler(self)
tokens = ["HANDLER:", cast(Any, self._handler)]
debug.print_tokens(debug.LEVEL_INFO, tokens, True)
if self._script.get_learn_mode_presenter().is_active():
self._consumer = self._script.get_learn_mode_presenter().handle_event
tokens = ["CONSUMER:", cast(Any, self._consumer)]
debug.print_tokens(debug.LEVEL_INFO, tokens, True)
if self.is_orca_modifier() and self._click_count == 2:
orca_modifier_manager.get_manager().toggle_modifier(self)
self._present()
if self.is_pressed_key() and (self._consumer \
or (self._handler and self._handler.function is not None \
and self._handler.is_enabled())):
GLib.timeout_add(1, self._consume)
msg = f"TOTAL PROCESSING TIME: {time.time() - start_time:.4f}"
debug.print_message(debug.LEVEL_INFO, msg, True)
msg = f"^^^^^ PROCESS {self.type.value_name.upper()}: {data} ^^^^^\n"
debug.print_message(debug.LEVEL_INFO, msg, False)
def _consume(self) -> bool:
"""Consumes this input event after a timeout. Returns False to stop the timeout."""
start_time = time.time()
data = f"'{self.keyval_name}' ({self.hw_code})"
msg = f"\nvvvvv CONSUME {self.type.value_name.upper()}: {data} vvvvv"
debug.print_message(debug.LEVEL_INFO, msg, False)
if self._consumer:
msg = f"KEYBOARD EVENT: Consumer is {self._consumer.__name__}"
debug.print_message(debug.LEVEL_INFO, msg, True)
self._consumer(self)
elif self._handler and self._handler.function is not None and self._handler.is_enabled():
msg = f"KEYBOARD EVENT: Handler is {self._handler}"
debug.print_message(debug.LEVEL_INFO, msg, True)
self._handler.function(self._script, self)
else:
msg = "KEYBOARD EVENT: No enabled handler or consumer"
debug.print_message(debug.LEVEL_INFO, msg, True)
msg = f"TOTAL PROCESSING TIME: {time.time() - start_time:.4f}"
debug.print_message(debug.LEVEL_INFO, msg, True)
msg = f"^^^^^ CONSUME {self.type.value_name.upper()}: {data} ^^^^^\n"
debug.print_message(debug.LEVEL_INFO, msg, False)
return False
class BrailleEvent(InputEvent):
"""Provides support for handling braille events."""
def __init__(self, event: dict) -> None:
super().__init__(BRAILLE_EVENT)
self.event: dict = event
self._script: Optional[default.Script] = script_manager.get_manager().get_active_script()
def __str__(self) -> str:
return f"{self.type.upper()} {self.event}"
def get_handler(self) -> Optional[InputEventHandler]:
"""Returns the handler associated with this event."""
try:
assert self._script is not None
except AssertionError:
tokens = ["BRAILLE EVENT: No active script found for", self]
debug.print_tokens(debug.LEVEL_INFO, tokens, True)
return None
command: str = self.event["command"]
user_bindings: Optional[dict] = None
user_bindings_map: dict = settings.brailleBindingsMap
if self._script.name in user_bindings_map:
user_bindings = user_bindings_map[self._script.name]
else:
user_bindings = user_bindings_map.get("default")
if user_bindings and command in user_bindings:
handler: InputEventHandler = user_bindings[command]
tokens = [f"BRAILLE EVENT: User handler for command {command} is", handler]
debug.print_tokens(debug.LEVEL_INFO, tokens, True)
return handler
handler = self._script.braille_bindings.get(command)
tokens = [f"BRAILLE EVENT: Handler for command {command} is", handler]
debug.print_tokens(debug.LEVEL_INFO, tokens, True)
return handler
def process(self):
"""Processes this event."""
tokens = ["\nvvvvv PROCESS", self, "vvvvv"]
debug.print_tokens(debug.LEVEL_INFO, tokens, False)
start_time = time.time()
result = self._process()
msg = f"TOTAL PROCESSING TIME: {time.time() - start_time:.4f}"
debug.print_message(debug.LEVEL_INFO, msg, False)
tokens = ["^^^^^ PROCESS", self, "^^^^^"]
debug.print_tokens(debug.LEVEL_INFO, tokens, False)
return result
def _process(self):
handler = self.get_handler()
if not handler:
if self._script.get_learn_mode_presenter().is_active():
tokens = ["BRAILLE EVENT: Learn mode presenter handles", self]
debug.print_tokens(debug.LEVEL_INFO, tokens, True)
return True
tokens = ["BRAILLE EVENT: No handler found for", self]
debug.print_tokens(debug.LEVEL_INFO, tokens, True)
return False
if handler.function:
tokens = ["BRAILLE EVENT: Handler is:", handler]
debug.print_tokens(debug.LEVEL_INFO, tokens, True)
handler.function(self._script, self)
return True
class MouseButtonEvent(InputEvent):
"""Provides support for handling mouse button events."""
try:
display = Gdk.Display.get_default()
seat = Gdk.Display.get_default_seat(display)
_pointer = seat.get_pointer()
except Exception:
_pointer = None
def __init__(self, event):
super().__init__(MOUSE_BUTTON_EVENT)
self.x = event.detail1
self.y = event.detail2
self.pressed = event.type.endswith('p')
self.button = event.type[len("mouse:button:"):-1]
self._script = script_manager.get_manager().get_active_script()
self.window = focus_manager.get_manager().get_active_window()
self.app = None
if self.pressed:
self._validate_coordinates()
if not self._script:
return
if not AXUtilities.can_be_active_window(self.window):
self.window = AXUtilities.find_active_window()
if not self.window:
return
self.app = AXUtilities.get_application(self.window)
def _validate_coordinates(self):
if not self._pointer:
return
x, y = self._pointer.get_position()[1:]
if math.sqrt((self.x - x)**2 + (self.y - y)**2) < 25:
return
msg = (
f"WARNING: Event coordinates ({self.x}, {self.y}) may be bogus. "
f"Updating to ({x}, {y})"
)
debug.print_message(debug.LEVEL_INFO, msg, True)
self.x, self.y = x, y
class InputEventHandler:
"""A handler for an input event."""
def __init__(
self,
function: Callable[..., bool],
description: str,
learn_mode_enabled: bool = True,
enabled: bool = True
) -> None:
self.function: Callable[..., bool] = function
self.description: str = description
self.learn_mode_enabled: bool = learn_mode_enabled
self._enabled: bool = enabled
def __eq__(self, other: object) -> bool:
if not isinstance(other, InputEventHandler):
return False
return self.function == other.function
def __str__(self) -> str:
return f"{self.description} (enabled: {self._enabled})"
def is_enabled(self) -> bool:
"""Returns True if this handler is enabled."""
msg = f"INPUT EVENT HANDLER: {self.description} is enabled: {self._enabled}"
debug.print_message(debug.LEVEL_INFO, msg, True)
return self._enabled
def set_enabled(self, enabled: bool) -> None:
"""Sets enabled state of this handler."""
self._enabled = enabled