%PDF- %PDF-
| Direktori : /lib/python3/dist-packages/orca/ |
| Current File : //lib/python3/dist-packages/orca/input_event.py |
# 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.
"""Provides support for handling input events."""
__id__ = "$Id$"
__version__ = "$Revision$"
__date__ = "$Date$"
__copyright__ = "Copyright (c) 2005-2008 Sun Microsystems Inc." \
"Copyright (c) 2011-2016 Igalia, S.L."
__license__ = "LGPL"
import gi
gi.require_version("Atspi", "2.0")
from gi.repository import Atspi
import math
import time
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_state
from . import script_manager
from . import settings
from .ax_object import AXObject
from .ax_utilities import AXUtilities
KEYBOARD_EVENT = "keyboard"
BRAILLE_EVENT = "braille"
MOUSE_BUTTON_EVENT = "mouse:button"
class InputEvent:
def __init__(self, eventType):
"""Creates a new KEYBOARD_EVENT, BRAILLE_EVENT, or MOUSE_BUTTON_EVENT."""
self.type = eventType
self.time = time.time()
self._clickCount = 0
def getClickCount(self):
"""Return the count of the number of clicks a user has made."""
return self._clickCount
def setClickCount(self):
"""Updates the count of the number of clicks a user has made."""
pass
def asSingleLineString(self):
"""Returns a single-line string representation of this event."""
return f"{self.type}"
def _getXkbStickyKeysState():
from subprocess import check_output
try:
output = check_output(['xkbset', 'q'])
for line in output.decode('ASCII', errors='ignore').split('\n'):
if line.startswith('Sticky-Keys = '):
return line.endswith('On')
except Exception:
pass
return False
class KeyboardEvent(InputEvent):
stickyKeys = _getXkbStickyKeysState()
duplicateCount = 0
orcaModifierPressed = False
# Whether last press of the Orca modifier was alone
lastOrcaModifierAlone = False
lastOrcaModifierAloneTime = None
# Whether the current press of the Orca modifier is alone
currentOrcaModifierAlone = False
currentOrcaModifierAloneTime = None
# When the second orca press happened
secondOrcaModifierTime = None
# Sticky modifiers state, to be applied to the next keyboard event
orcaStickyModifiers = 0
TYPE_UNKNOWN = "unknown"
TYPE_PRINTABLE = "printable"
TYPE_MODIFIER = "modifier"
TYPE_LOCKING = "locking"
TYPE_FUNCTION = "function"
TYPE_ACTION = "action"
TYPE_NAVIGATION = "navigation"
TYPE_DIACRITICAL = "diacritical"
TYPE_ALPHABETIC = "alphabetic"
TYPE_NUMERIC = "numeric"
TYPE_PUNCTUATION = "punctuation"
TYPE_SPACE = "space"
GDK_PUNCTUATION_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]
GDK_ACCENTED_LETTER_KEYS = [Gdk.KEY_Aacute,
Gdk.KEY_aacute,
Gdk.KEY_Acircumflex,
Gdk.KEY_acircumflex,
Gdk.KEY_Adiaeresis,
Gdk.KEY_adiaeresis,
Gdk.KEY_Agrave,
Gdk.KEY_agrave,
Gdk.KEY_Aring,
Gdk.KEY_aring,
Gdk.KEY_Atilde,
Gdk.KEY_atilde,
Gdk.KEY_Ccedilla,
Gdk.KEY_ccedilla,
Gdk.KEY_Eacute,
Gdk.KEY_eacute,
Gdk.KEY_Ecircumflex,
Gdk.KEY_ecircumflex,
Gdk.KEY_Ediaeresis,
Gdk.KEY_ediaeresis,
Gdk.KEY_Egrave,
Gdk.KEY_egrave,
Gdk.KEY_Iacute,
Gdk.KEY_iacute,
Gdk.KEY_Icircumflex,
Gdk.KEY_icircumflex,
Gdk.KEY_Idiaeresis,
Gdk.KEY_idiaeresis,
Gdk.KEY_Igrave,
Gdk.KEY_igrave,
Gdk.KEY_Ntilde,
Gdk.KEY_ntilde,
Gdk.KEY_Oacute,
Gdk.KEY_oacute,
Gdk.KEY_Ocircumflex,
Gdk.KEY_ocircumflex,
Gdk.KEY_Odiaeresis,
Gdk.KEY_odiaeresis,
Gdk.KEY_Ograve,
Gdk.KEY_ograve,
Gdk.KEY_Ooblique,
Gdk.KEY_ooblique,
Gdk.KEY_Otilde,
Gdk.KEY_otilde,
Gdk.KEY_Uacute,
Gdk.KEY_uacute,
Gdk.KEY_Ucircumflex,
Gdk.KEY_ucircumflex,
Gdk.KEY_Udiaeresis,
Gdk.KEY_udiaeresis,
Gdk.KEY_Ugrave,
Gdk.KEY_ugrave,
Gdk.KEY_Yacute,
Gdk.KEY_yacute]
def __init__(self, pressed, keycode, keysym, modifiers, text):
"""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 = keysym
if pressed:
self.type = Atspi.EventType.KEY_PRESSED_EVENT
else:
self.type = Atspi.EventType.KEY_RELEASED_EVENT
self.hw_code = keycode
self.modifiers = modifiers & Gdk.ModifierType.MODIFIER_MASK
if modifiers & (1 << Atspi.ModifierType.NUMLOCK):
self.modifiers |= (1 << Atspi.ModifierType.NUMLOCK)
self.event_string = text
self.keyval_name = Gdk.keyval_name(keysym)
if self.event_string == "" or self.event_string == " ":
self.event_string = self.keyval_name
self.timestamp = time.time()
self.is_duplicate = self in [orca_state.lastInputEvent,
orca_state.lastNonModifierKeyEvent]
self._script = None
self._app = None
self._window = None
self._obj = None
self._obj_after_consuming = None
self._handler = None
self._consumer = None
self._should_consume = None
self._consume_reason = None
self._did_consume = None
self._result_reason = None
self._bypassOrca = None
self._is_kp_with_numlock = False
# Some implementors don't populate this field at all. More often than not,
# the event_string and the keyval_name coincide for input events.
if not self.event_string:
self.event_string = self.keyval_name
# Some implementors do populate the field, but with the keyname rather than
# the printable character. This messes us up with punctuation and other symbols.
if len(self.event_string) > 1 \
and (self.id in KeyboardEvent.GDK_PUNCTUATION_KEYS or \
self.id in KeyboardEvent.GDK_ACCENTED_LETTER_KEYS):
self.event_string = chr(self.id)
# 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
# We typically do little to nothing in the case of a key release. Therefore skip doing
# this work.
if pressed:
self._script = script_manager.getManager().getActiveScript()
self._window = focus_manager.getManager().get_active_window()
if self._script:
self._app = self._script.app
if not focus_manager.getManager().can_be_active_window(self._window):
self._window = focus_manager.getManager().find_active_window()
tokens = ["INPUT EVENT: Updating window and active window to", self._window]
debug.printTokens(debug.LEVEL_INFO, tokens, True)
focus_manager.getManager().set_active_window(self._window)
# We set this after getting the window because changing the window can cause focus to
# be updated if the current locus of focus is not in the window we just set as active.
self._obj = focus_manager.getManager().get_locus_of_focus()
if self._window and self._app != AXObject.get_application(self._window):
self._script = script_manager.getManager().getScript(
AXObject.get_application(self._window))
self._app = self._script.app
tokens = ["INPUT EVENT: Updated script to", self._script]
debug.printTokens(debug.LEVEL_INFO, tokens, True)
elif self._isReleaseForLastNonModifierKeyEvent():
self._script = orca_state.lastNonModifierKeyEvent._script
self._window = orca_state.lastNonModifierKeyEvent._window
self._obj = orca_state.lastNonModifierKeyEvent._obj
self._obj_after_consuming = orca_state.lastNonModifierKeyEvent._obj_after_consuming
else:
self._script = script_manager.getManager().getActiveScript()
self._window = focus_manager.getManager().get_active_window()
self._obj = focus_manager.getManager().get_locus_of_focus()
self._obj_after_consuming = self._obj
if self.is_duplicate:
KeyboardEvent.duplicateCount += 1
else:
KeyboardEvent.duplicateCount = 0
self.keyType = None
role = AXObject.get_role(self._obj)
_mayEcho = pressed or role == Atspi.Role.TERMINAL
if KeyboardEvent.stickyKeys and not self.isOrcaModifier() \
and not KeyboardEvent.lastOrcaModifierAlone:
doubleEvent = self._getDoubleClickCandidate()
if doubleEvent and \
doubleEvent.modifiers & keybindings.ORCA_MODIFIER_MASK:
# this is the second event of a double-click, and sticky Orca
# affected the first, so copy over the modifiers to the second
KeyboardEvent.orcaStickyModifiers = doubleEvent.modifiers
if not self.isOrcaModifier():
if KeyboardEvent.orcaModifierPressed:
KeyboardEvent.currentOrcaModifierAlone = False
KeyboardEvent.currentOrcaModifierAloneTime = None
else:
KeyboardEvent.lastOrcaModifierAlone = False
KeyboardEvent.lastOrcaModifierAloneTime = None
if self.isNavigationKey():
self.keyType = KeyboardEvent.TYPE_NAVIGATION
self.shouldEcho = _mayEcho and settings.enableNavigationKeys
elif self.isActionKey():
self.keyType = KeyboardEvent.TYPE_ACTION
self.shouldEcho = _mayEcho and settings.enableActionKeys
elif self.isModifierKey():
self.keyType = KeyboardEvent.TYPE_MODIFIER
self.shouldEcho = _mayEcho and settings.enableModifierKeys
if self.isOrcaModifier() and not self.is_duplicate:
now = time.time()
if KeyboardEvent.lastOrcaModifierAlone:
if pressed:
KeyboardEvent.secondOrcaModifierTime = now
if (KeyboardEvent.secondOrcaModifierTime <
KeyboardEvent.lastOrcaModifierAloneTime + 0.5):
# double-orca, let the real action happen
self._bypassOrca = True
if not pressed:
KeyboardEvent.lastOrcaModifierAlone = False
KeyboardEvent.lastOrcaModifierAloneTime = False
else:
KeyboardEvent.orcaModifierPressed = pressed
if pressed:
KeyboardEvent.currentOrcaModifierAlone = True
KeyboardEvent.currentOrcaModifierAloneTime = now
else:
KeyboardEvent.lastOrcaModifierAlone = \
KeyboardEvent.currentOrcaModifierAlone
KeyboardEvent.lastOrcaModifierAloneTime = \
KeyboardEvent.currentOrcaModifierAloneTime
elif self.isFunctionKey():
self.keyType = KeyboardEvent.TYPE_FUNCTION
self.shouldEcho = _mayEcho and settings.enableFunctionKeys
elif self.isDiacriticalKey():
self.keyType = KeyboardEvent.TYPE_DIACRITICAL
self.shouldEcho = _mayEcho and settings.enableDiacriticalKeys
elif self.isLockingKey():
self.keyType = KeyboardEvent.TYPE_LOCKING
self.shouldEcho = settings.presentLockingKeys
if self.shouldEcho is None:
self.shouldEcho = not settings.onlySpeakDisplayedText
self.shouldEcho = self.shouldEcho and pressed
elif self.isAlphabeticKey():
self.keyType = KeyboardEvent.TYPE_ALPHABETIC
self.shouldEcho = _mayEcho \
and (settings.enableAlphabeticKeys or settings.enableEchoByCharacter)
elif self.isNumericKey():
self.keyType = KeyboardEvent.TYPE_NUMERIC
self.shouldEcho = _mayEcho \
and (settings.enableNumericKeys or settings.enableEchoByCharacter)
elif self.isPunctuationKey():
self.keyType = KeyboardEvent.TYPE_PUNCTUATION
self.shouldEcho = _mayEcho \
and (settings.enablePunctuationKeys or settings.enableEchoByCharacter)
elif self.isSpace():
self.keyType = KeyboardEvent.TYPE_SPACE
self.shouldEcho = _mayEcho \
and (settings.enableSpace or settings.enableEchoByCharacter)
else:
self.keyType = KeyboardEvent.TYPE_UNKNOWN
self.shouldEcho = False
if not self.isLockingKey():
self.shouldEcho = self.shouldEcho and settings.enableKeyEcho
if not self.isModifierKey():
self.setClickCount()
if orca_state.bypassNextCommand and pressed:
KeyboardEvent.orcaModifierPressed = False
if KeyboardEvent.orcaModifierPressed:
self.modifiers |= keybindings.ORCA_MODIFIER_MASK
if KeyboardEvent.stickyKeys:
# apply all recorded sticky modifiers
self.modifiers |= KeyboardEvent.orcaStickyModifiers
if self.isModifierKey():
# add this modifier to the sticky ones
KeyboardEvent.orcaStickyModifiers |= self.modifiers
else:
# Non-modifier key, so clear the sticky modifiers. If the user
# actually double-presses that key, the modifiers of this event
# will be copied over to the second event, see earlier in this
# function.
KeyboardEvent.orcaStickyModifiers = 0
self._should_consume, self._consume_reason = self.shouldConsume()
def _getDoubleClickCandidate(self):
lastEvent = orca_state.lastNonModifierKeyEvent
if isinstance(lastEvent, KeyboardEvent) \
and lastEvent.event_string == self.event_string \
and self.time - lastEvent.time <= settings.doubleClickTimeout:
return lastEvent
return None
def setClickCount(self):
"""Updates the count of the number of clicks a user has made."""
doubleEvent = self._getDoubleClickCandidate()
if not doubleEvent:
self._clickCount = 1
return
self._clickCount = doubleEvent.getClickCount()
if self.is_duplicate:
return
if self.type == Atspi.EventType.KEY_RELEASED_EVENT:
return
if self._clickCount < 3:
if doubleEvent._obj != doubleEvent._obj_after_consuming:
tokens = ["KEYBOARD EVENT: Resetting click count due to focus change from",
doubleEvent._obj, "to", doubleEvent._obj_after_consuming]
debug.printTokens(debug.LEVEL_INFO, tokens, True)
self._clickCount = 1
return
self._clickCount += 1
return
self._clickCount = 1
def __eq__(self, other):
if not other:
return False
if self.type == other.type and self.hw_code == other.hw_code:
return self.timestamp == other.timestamp
return False
def __str__(self):
if self._shouldObscure():
keyid = hw_code = modifiers = event_string = keyval_name = key_type = "*"
else:
keyid = self.id
hw_code = self.hw_code
modifiers = self.modifiers
event_string = self.event_string
keyval_name = self.keyval_name
key_type = self.keyType
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" event_string=({event_string})\n" \
+ f" keyval_name=({keyval_name})\n" \
+ (" timestamp=%d\n" % self.timestamp) \
+ f" time={time.time():f}\n" \
+ f" keyType={key_type}\n" \
+ f" clickCount={self._clickCount}\n" \
+ f" shouldEcho={self.shouldEcho}\n"
def asSingleLineString(self):
"""Returns a single-line string representation of this event."""
if self._shouldObscure():
return "(obscured)"
return (
f"'{self.event_string}' ({self.keyval_name}) mods: {self.modifiers} "
f"{self.type.value_nick}"
)
def _shouldObscure(self):
if not AXUtilities.is_password_text(self._obj):
return False
if not self.isPrintableKey():
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 _isReleaseForLastNonModifierKeyEvent(self):
last = orca_state.lastNonModifierKeyEvent
if not last:
return False
if not last.isPressedKey() or self.isPressedKey():
return False
if self.id == last.id and self.hw_code == last.hw_code:
return self.modifiers == last.modifiers
return False
def isReleaseFor(self, other):
"""Return True if this is the release event for other."""
if not other:
return False
if not other.isPressedKey() or self.isPressedKey():
return False
return self.id == other.id \
and self.hw_code == other.hw_code \
and self.modifiers == other.modifiers \
and self.event_string == other.event_string \
and self.keyval_name == other.keyval_name \
and self.keyType == other.keyType \
and self._clickCount == other._clickCount
def isNavigationKey(self):
"""Return True if this is a navigation key."""
if self.keyType:
return self.keyType == KeyboardEvent.TYPE_NAVIGATION
return self.event_string in \
["Left", "Right", "Up", "Down", "Home", "End"]
def isActionKey(self):
"""Return True if this is an action key."""
if self.keyType:
return self.keyType == KeyboardEvent.TYPE_ACTION
return self.event_string in \
["Return", "Escape", "Tab", "BackSpace", "Delete",
"Page_Up", "Page_Down"]
def isAlphabeticKey(self):
"""Return True if this is an alphabetic key."""
if self.keyType:
return self.keyType == KeyboardEvent.TYPE_ALPHABETIC
if not len(self.event_string) == 1:
return False
return self.event_string.isalpha()
def isDiacriticalKey(self):
"""Return True if this is a non-spacing diacritical key."""
if self.keyType:
return self.keyType == KeyboardEvent.TYPE_DIACRITICAL
return self.event_string.startswith("dead_")
def isFunctionKey(self):
"""Return True if this is a function key."""
if self.keyType:
return self.keyType == KeyboardEvent.TYPE_FUNCTION
return self.event_string in \
["F1", "F2", "F3", "F4", "F5", "F6",
"F7", "F8", "F9", "F10", "F11", "F12"]
def isLockingKey(self):
"""Return True if this is a locking key."""
if self.keyType:
return self.keyType in KeyboardEvent.TYPE_LOCKING
lockingKeys = ["Caps_Lock", "Shift_Lock", "Num_Lock", "Scroll_Lock"]
if self.event_string not in lockingKeys:
return False
if not orca_state.bypassNextCommand and not self._bypassOrca:
return self.event_string not in settings.orcaModifierKeys
return True
def isModifierKey(self):
"""Return True if this is a modifier key."""
if self.keyType:
return self.keyType == KeyboardEvent.TYPE_MODIFIER
if self.isOrcaModifier():
return True
return self.event_string in \
['Alt_L', 'Alt_R', 'Control_L', 'Control_R',
'Shift_L', 'Shift_R', 'Meta_L', 'Meta_R',
'ISO_Level3_Shift']
def isNumericKey(self):
"""Return True if this is a numeric key."""
if self.keyType:
return self.keyType == KeyboardEvent.TYPE_NUMERIC
if not len(self.event_string) == 1:
return False
return self.event_string.isnumeric()
def isOrcaModifier(self, checkBypassMode=True):
"""Return True if this is the Orca modifier key."""
if checkBypassMode and orca_state.bypassNextCommand:
return False
if self.event_string in settings.orcaModifierKeys:
return True
if self.keyval_name == "KP_0" \
and "KP_Insert" in settings.orcaModifierKeys \
and self.modifiers & keybindings.SHIFT_MODIFIER_MASK:
return True
return False
def isOrcaModified(self):
"""Return True if this key is Orca modified."""
if orca_state.bypassNextCommand:
return False
return self.modifiers & keybindings.ORCA_MODIFIER_MASK
def isKeyPadKeyWithNumlockOn(self):
"""Return True if this is a key pad key with numlock on."""
return self._is_kp_with_numlock
def isPrintableKey(self):
"""Return True if this is a printable key."""
if self.event_string in ["space", " "]:
return True
if not len(self.event_string) == 1:
return False
return self.event_string.isprintable()
def isPressedKey(self):
"""Returns True if the key is pressed"""
return self.type == Atspi.EventType.KEY_PRESSED_EVENT
def isPunctuationKey(self):
"""Return True if this is a punctuation key."""
if self.keyType:
return self.keyType == KeyboardEvent.TYPE_PUNCTUATION
if not len(self.event_string) == 1:
return False
if self.isAlphabeticKey() or self.isNumericKey():
return False
return self.event_string.isprintable() and not self.event_string.isspace()
def isSpace(self):
"""Return True if this is the space key."""
if self.keyType:
return self.keyType == KeyboardEvent.TYPE_SPACE
return self.event_string in ["space", " "]
def isFromApplication(self, app):
"""Return True if this is associated with the specified app."""
return self._app == app
def isCharacterEchoable(self):
"""Returns True if the script will echo this event as part of
character echo. We do this to not double-echo a given printable
character."""
if not self.isPrintableKey():
return False
script = script_manager.getManager().getActiveScript()
return script and script.utilities.willEchoCharacter(self)
def getLockingState(self):
"""Returns True if the event locked a locking key, False if the
event unlocked a locking key, and None if we do not know or this
is not a locking key."""
if not self.isLockingKey():
return None
if self.event_string == "Caps_Lock":
mod = Atspi.ModifierType.SHIFTLOCK
elif self.event_string == "Shift_Lock":
mod = Atspi.ModifierType.SHIFT
elif self.event_string == "Num_Lock":
mod = Atspi.ModifierType.NUMLOCK
else:
return None
return not self.modifiers & (1 << mod)
def getLockingStateString(self):
"""Returns the string which reflects the locking state we wish to
include when presenting a locking key."""
locked = self.getLockingState()
if locked is None:
return ''
if not locked:
return messages.LOCKING_KEY_STATE_OFF
return messages.LOCKING_KEY_STATE_ON
def getKeyName(self):
"""Returns the string to be used for presenting the key to the user."""
return keynames.getKeyName(self.event_string)
def getObject(self):
"""Returns the object believed to be associated with this key event."""
return self._obj
def getHandler(self):
"""Returns the handler associated with this key event."""
return self._handler
def _getUserHandler(self):
# 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
try:
handler = bindings.getInputHandler(self)
except Exception:
handler = None
return handler
def shouldConsume(self):
"""Returns True if this event should be consumed."""
if not self.timestamp:
return False, 'No timestamp'
if not self._script:
return False, 'No active script when received'
if self.is_duplicate:
return False, 'Is duplicate'
if orca_state.capturingKeys:
return False, 'Capturing keys'
if orca_state.bypassNextCommand:
return False, 'Bypass next command'
self._handler = self._getUserHandler() \
or self._script.keyBindings.getInputHandler(self)
scriptConsumes = self._handler is not None and self._handler.is_enabled()
if self._isReleaseForLastNonModifierKeyEvent():
return scriptConsumes, 'Is release for last non-modifier keyevent'
if self._script.learnModePresenter.is_active():
self._consumer = self._script.learnModePresenter.handle_event
return True, 'In Learn Mode'
if self.isModifierKey():
if not self.isOrcaModifier():
return False, 'Non-Orca modifier not in Learn Mode'
return True, 'Orca modifier'
if not self._handler:
return False, 'No handler'
if not self._handler.is_enabled():
return False, 'Handler is disabled'
return scriptConsumes, 'Script indication'
def isHandledBy(self, method):
if not self._handler:
return False
return method.__func__ == self._handler.function
def _present(self, inputEvent=None):
if self.isPressedKey():
self._script.presentationInterrupt()
if self._script.learnModePresenter.is_active():
return False
return self._script.presentKeyboardEvent(self)
def process(self):
"""Processes this input event."""
startTime = time.time()
if not self._shouldObscure():
data = "'%s' (%d)" % (self.event_string, self.hw_code)
else:
data = "(obscured)"
if self.is_duplicate:
data = '%s DUPLICATE EVENT #%i' % (data, KeyboardEvent.duplicateCount)
msg = f'\nvvvvv PROCESS {self.type.value_name.upper()}: {data} vvvvv'
debug.printMessage(debug.LEVEL_INFO, msg, False)
tokens = ["HOST_APP:", self._app]
debug.printTokens(debug.LEVEL_INFO, tokens, True)
tokens = ["WINDOW:", self._window]
debug.printTokens(debug.LEVEL_INFO, tokens, True)
tokens = ["LOCATION:", self._obj_after_consuming or self._obj]
debug.printTokens(debug.LEVEL_INFO, tokens, True)
tokens = ["CONSUME:", self._should_consume, self._consume_reason]
debug.printTokens(debug.LEVEL_INFO, tokens, True)
self._did_consume, self._result_reason = self._process()
tokens = ["CONSUMED:", self._did_consume, self._result_reason]
debug.printTokens(debug.LEVEL_INFO, tokens, True)
script = script_manager.getManager().getActiveScript()
if debug.LEVEL_INFO >= debug.debugLevel and script is not None:
attributes = script.getTransferableAttributes()
for key, value in attributes.items():
msg = f"INPUT EVENT: {key}: {value}"
debug.printMessage(debug.LEVEL_INFO, msg, True)
msg = f"TOTAL PROCESSING TIME: {time.time() - startTime:.4f}"
debug.printMessage(debug.LEVEL_INFO, msg, True)
msg = f"^^^^^ PROCESS {self.type.value_name.upper()}: {data} ^^^^^\n"
debug.printMessage(debug.LEVEL_INFO, msg, False)
return self._did_consume
def _process(self):
"""Processes this input event."""
if self._bypassOrca:
if (self.event_string == "Caps_Lock" \
or self.event_string == "Shift_Lock") \
and self.type == Atspi.EventType.KEY_PRESSED_EVENT:
self._lock_mod()
self.keyType = KeyboardEvent.TYPE_LOCKING
self._present()
return False, 'Bypassed orca modifier'
orca_state.lastInputEvent = self
if not self.isModifierKey():
orca_state.lastNonModifierKeyEvent = self
if not self._script:
return False, 'No active script'
if self.is_duplicate:
return False, 'Is duplicate'
self._present()
if not self.isPressedKey():
return self._should_consume, 'Consumed based on handler'
if orca_state.capturingKeys:
return False, 'Capturing keys'
if self.isOrcaModifier():
return True, 'Orca modifier'
if orca_state.bypassNextCommand:
if not self.isModifierKey():
orca_state.bypassNextCommand = False
self._script.addKeyGrabs("bypassed next command")
return False, 'Bypass next command'
if not self._should_consume:
return False, 'Should not consume'
if not (self._consumer or self._handler):
return False, 'No consumer or handler'
if self._consumer or self._handler.function:
GLib.timeout_add(1, self._consume)
return True, 'Will be consumed'
return False, 'Unaddressed case'
def _lock_mod(self):
def lock_mod(modifiers, modifier):
def lockit():
try:
if modifiers & modifier:
lock = Atspi.KeySynthType.UNLOCKMODIFIERS
debug.printMessage(debug.LEVEL_INFO, "Unlocking capslock", True)
else:
lock = Atspi.KeySynthType.LOCKMODIFIERS
debug.printMessage(debug.LEVEL_INFO, "Locking capslock", True)
Atspi.generate_keyboard_event(modifier, "", lock)
debug.printMessage(debug.LEVEL_INFO, "Done with capslock", True)
except Exception:
debug.printMessage(debug.LEVEL_INFO, "Could not trigger capslock, " \
"at-spi2-core >= 2.32 is needed for triggering capslock", True)
pass
return lockit
if self.event_string == "Caps_Lock":
modifier = 1 << Atspi.ModifierType.SHIFTLOCK
elif self.event_string == "Shift_Lock":
modifier = 1 << Atspi.ModifierType.SHIFT
else:
tokens = ["Unknown locking key", self.event_string]
debug.printTokens(debug.LEVEL_WARNING, tokens, True)
return
debug.printMessage(debug.LEVEL_INFO, "Scheduling capslock", True)
GLib.timeout_add(1, lock_mod(self.modifiers, modifier))
def _consume(self):
startTime = time.time()
data = "'%s' (%d)" % (self.event_string, self.hw_code)
msg = f'\nvvvvv CONSUME {self.type.value_name.upper()}: {data} vvvvv'
debug.printMessage(debug.LEVEL_INFO, msg, False)
if self._consumer:
msg = f'KEYBOARD EVENT: Consumer is {self._consumer.__name__}'
debug.printMessage(debug.LEVEL_INFO, msg, True)
self._consumer(self)
elif self._handler.function:
msg = f'KEYBOARD EVENT: Handler is {self._handler}'
debug.printMessage(debug.LEVEL_INFO, msg, True)
self._handler.function(self._script, self)
else:
msg = 'KEYBOARD EVENT: No handler or consumer'
debug.printMessage(debug.LEVEL_INFO, msg, True)
self._obj_after_consuming = focus_manager.getManager().get_locus_of_focus()
if (self._obj != self._obj_after_consuming):
tokens = ["KEYBOARD EVENT: Consumer changed focus from", self._obj, "to",
self._obj_after_consuming]
debug.printTokens(debug.LEVEL_INFO, tokens, True)
msg = f'TOTAL PROCESSING TIME: {time.time() - startTime:.4f}'
debug.printMessage(debug.LEVEL_INFO, msg, True)
msg = f'^^^^^ CONSUME {self.type.value_name.upper()}: {data} ^^^^^\n'
debug.printMessage(debug.LEVEL_INFO, msg, False)
return False
class BrailleEvent(InputEvent):
def __init__(self, event):
"""Creates a new InputEvent of type BRAILLE_EVENT.
Arguments:
- event: the integer BrlTTY command for this event.
"""
super().__init__(BRAILLE_EVENT)
self.event = event
self._script = script_manager.getManager().getActiveScript()
def __str__(self):
return f"{self.type.upper()} {self.event}"
def getHandler(self):
"""Returns the handler associated with this event."""
command = self.event["command"]
user_bindings = None
user_bindings_map = settings.brailleBindingsMap
if self._script.name in user_bindings_map:
user_bindings = user_bindings_map[self._script.name]
elif "default" in user_bindings_map:
user_bindings = user_bindings_map["default"]
if user_bindings and command in user_bindings:
handler = user_bindings[command]
tokens = [f"BRAILLE EVENT: User handler for command {command} is", handler]
debug.printTokens(debug.LEVEL_INFO, tokens, True)
return handler
handler = self._script.brailleBindings.get(command)
tokens = [f"BRAILLE EVENT: Handler for command {command} is", handler]
debug.printTokens(debug.LEVEL_INFO, tokens, True)
return handler
def process(self):
tokens = ["\nvvvvv PROCESS", self, "vvvvv"]
debug.printTokens(debug.LEVEL_INFO, tokens, False)
start_time = time.time()
result = self._process()
msg = f"TOTAL PROCESSING TIME: {time.time() - start_time:.4f}"
debug.printMessage(debug.LEVEL_INFO, msg, False)
tokens = ["^^^^^ PROCESS", self, "^^^^^"]
debug.printTokens(debug.LEVEL_INFO, tokens, False)
return result
def _process(self):
handler = self.getHandler()
if not handler:
if self._script.learnModePresenter.is_active():
tokens = ["BRAILLE EVENT: Learn mode presenter handles", self]
debug.printTokens(debug.LEVEL_INFO, tokens, True)
return True
tokens = ["BRAILLE EVENT: No handler found for", self]
debug.printTokens(debug.LEVEL_INFO, tokens, True)
return False
try:
handler.processInputEvent(self._script, self)
except Exception as error:
tokens = ["BRAILLE EVENT: Exception processing:", self, f": {error}"]
debug.printTokens(debug.LEVEL_WARNING, tokens, True)
return True
class MouseButtonEvent(InputEvent):
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):
"""Creates a new InputEvent of type MOUSE_BUTTON_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.getManager().getActiveScript()
self.window = focus_manager.getManager().get_active_window()
self.app = None
if self.pressed:
self._validateCoordinates()
if not self._script:
return
if not focus_manager.getManager().can_be_active_window(self.window):
self.window = focus_manager.getManager().find_active_window()
if not self.window:
return
self.app = AXObject.get_application(self.window)
def _validateCoordinates(self):
if not self._pointer:
return
screen, x, y = self._pointer.get_position()
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.printMessage(debug.LEVEL_INFO, msg, True)
self.x, self.y = x, y
def setClickCount(self):
"""Updates the count of the number of clicks a user has made."""
if not self.pressed:
return
lastInputEvent = orca_state.lastInputEvent
if not isinstance(lastInputEvent, MouseButtonEvent):
self._clickCount = 1
return
if self.time - lastInputEvent.time < settings.doubleClickTimeout \
and lastInputEvent.button == self.button:
if self._clickCount < 2:
self._clickCount += 1
return
self._clickCount = 1
class InputEventHandler:
def __init__(self, function, description, learnModeEnabled=True, enabled=True):
"""Creates a new InputEventHandler instance. All bindings
(e.g., key bindings and braille bindings) will be handled
by an instance of an InputEventHandler.
Arguments:
- function: the function to call with an InputEvent instance as its
sole argument. The function is expected to return True
if it consumes the event; otherwise it should return
False
- description: a localized string describing what this InputEvent
does
- learnModeEnabled: if True, the description will be spoken and
brailled if learn mode is enabled. If False,
the function will be called no matter what.
- enabled: Whether this hander can be used, i.e. based on mode, the
feature being enabled/active, etc.
"""
self.function = function
self.description = description
self.learnModeEnabled = learnModeEnabled
self._enabled = enabled
def __eq__(self, other):
"""Compares one input handler to another."""
if not other:
return False
return (self.function == other.function)
def __str__(self):
return f"{self.description} (enabled: {self._enabled})"
def is_enabled(self):
"""Returns True if this handler is enabled."""
msg = f"INPUT EVENT HANDLER: {self.description} is enabled: {self._enabled}"
debug.printMessage(debug.LEVEL_INFO, msg, True)
return self._enabled
def set_enabled(self, enabled):
"""Sets this handler's enabled state."""
self._enabled = enabled
def processInputEvent(self, script, inputEvent):
"""Processes an input event.
This function is expected to return True if it consumes the
event; otherwise it is expected to return False.
Arguments:
- script: the script (if any) associated with this event
- inputEvent: the input event to pass to the function bound
to this InputEventHandler instance.
"""
if not self._enabled:
return False
consumed = False
try:
consumed = self.function(script, inputEvent)
except Exception:
debug.printException(debug.LEVEL_SEVERE)
return consumed