# Orca
#
# Copyright 2024 Igalia, S.L.
# Copyright 2024 GNOME Foundation Inc.
# Author: Joanmarie Diggs <[email protected]>
#
# 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=wrong-import-position
# pylint: disable=too-many-public-methods
"""Provides utilities for managing 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) 2024 Igalia, S.L." \
"Copyright (c) 2024 GNOME Foundation Inc."
__license__ = "LGPL"
from typing import TYPE_CHECKING
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 . import debug
from . import focus_manager
from . import input_event
from . import script_manager
from . import settings
from .ax_object import AXObject
from .ax_utilities import AXUtilities
if TYPE_CHECKING:
from . import keybindings
class InputEventManager:
"""Provides utilities for managing input events."""
def __init__(self) -> None:
self._last_input_event: input_event.InputEvent | None = None
self._last_non_modifier_key_event: input_event.KeyboardEvent | None = None
self._device: Atspi.Device | None = None
def start_key_watcher(self) -> None:
"""Starts the watcher for keyboard input events."""
msg = "INPUT EVENT MANAGER: Starting key watcher."
debug.print_message(debug.LEVEL_INFO, msg, True)
if Atspi.get_version() >= (2, 55, 90):
self._device = Atspi.Device.new_full("org.gnome.Orca")
else:
self._device = Atspi.Device.new()
self._device.add_key_watcher(self.process_keyboard_event)
def stop_key_watcher(self) -> None:
"""Starts the watcher for keyboard input events."""
msg = "INPUT EVENT MANAGER: Stopping key watcher."
debug.print_message(debug.LEVEL_INFO, msg, True)
self._device = None
def add_grabs_for_keybinding(self, binding: keybindings.KeyBinding) -> list[int]:
"""Adds grabs for binding if it is enabled, returns grab IDs."""
if not (binding.is_enabled() and binding.is_bound()):
return []
if binding.has_grabs():
tokens = ["INPUT EVENT MANAGER:", binding, "already has grabs."]
debug.print_tokens(debug.LEVEL_INFO, tokens, True)
return []
if self._device is None:
tokens = ["INPUT EVENT MANAGER: No device to add grab for", binding]
debug.print_tokens(debug.LEVEL_INFO, tokens, True)
return []
grab_ids = []
for kd in binding.key_definitions():
grab_ids.append(self._device.add_key_grab(kd, None))
return grab_ids
def remove_grabs_for_keybinding(self, binding: keybindings.KeyBinding) -> None:
"""Removes grabs for binding."""
if self._device is None:
tokens = ["INPUT EVENT MANAGER: No device to remove grab from", binding]
debug.print_tokens(debug.LEVEL_INFO, tokens, True)
return
grab_ids = binding.get_grab_ids()
if not grab_ids:
tokens = ["INPUT EVENT MANAGER:", binding, "doesn't have grabs to remove."]
debug.print_tokens(debug.LEVEL_INFO, tokens, True)
return
for grab_id in grab_ids:
self._device.remove_key_grab(grab_id)
def map_keycode_to_modifier(self, keycode: int) -> int:
"""Maps keycode as a modifier, returns the newly-mapped modifier."""
if self._device is None:
msg = f"INPUT EVENT MANAGER: No device to map keycode {keycode} to modifier"
debug.print_message(debug.LEVEL_INFO, msg, True)
return 0
return self._device.map_modifier(keycode)
def map_keysym_to_modifier(self, keysym: int) -> int:
"""Maps keysym as a modifier, returns the newly-mapped modifier."""
if self._device is None:
msg = f"INPUT EVENT MANAGER: No device to map keysym {keysym} to modifier"
debug.print_message(debug.LEVEL_INFO, msg, True)
return 0
return self._device.map_keysym_modifier(keysym)
def add_grab_for_modifier(self, modifier: str, keysym: int, keycode: int) -> int:
"""Adds grab for modifier, returns grab id."""
if self._device is None:
msg = f"INPUT EVENT MANAGER: No device to add grab for {modifier}"
debug.print_message(debug.LEVEL_INFO, msg, True)
return -1
kd = Atspi.KeyDefinition()
kd.keysym = keysym
kd.keycode = keycode
kd.modifiers = 0
grab_id = self._device.add_key_grab(kd)
msg = f"INPUT EVENT MANAGER: Grab id for {modifier}: {grab_id}"
debug.print_message(debug.LEVEL_INFO, msg, True)
return grab_id
def remove_grab_for_modifier(self, modifier: str, grab_id: int) -> None:
"""Removes grab for modifier."""
if self._device is None:
msg = f"INPUT EVENT MANAGER: No device to remove grab from {modifier}"
debug.print_message(debug.LEVEL_INFO, msg, True)
return
self._device.remove_key_grab(grab_id)
msg = f"INPUT EVENT MANAGER: Grab id removed for {modifier}: {grab_id}"
debug.print_message(debug.LEVEL_INFO, msg, True)
def grab_keyboard(self, reason: str = "") -> None:
"""Grabs the keyboard, e.g. when entering learn mode."""
msg = "INPUT EVENT MANAGER: Grabbing keyboard"
if reason:
msg += f" Reason: {reason}"
debug.print_message(debug.LEVEL_INFO, msg, True)
Atspi.Device.grab_keyboard(self._device)
def ungrab_keyboard(self, reason: str = "") -> None:
"""Removes keyboard grab, e.g. when exiting learn mode."""
msg = "INPUT EVENT MANAGER: Ungrabbing keyboard"
if reason:
msg += f" Reason: {reason}"
debug.print_message(debug.LEVEL_INFO, msg, True)
Atspi.Device.ungrab_keyboard(self._device)
def process_braille_event(self, event: Atspi.Event) -> bool:
"""Processes this Braille event."""
braille_event = input_event.BrailleEvent(event)
result = braille_event.process()
self._last_input_event = braille_event
self._last_non_modifier_key_event = None
return result
def process_mouse_button_event(self, event: Atspi.Event) -> None:
"""Processes this Mouse event."""
mouse_event = input_event.MouseButtonEvent(event)
mouse_event.set_click_count(self._determine_mouse_event_click_count(mouse_event))
self._last_input_event = mouse_event
# pylint: disable=too-many-arguments
# pylint: disable=too-many-positional-arguments
def process_keyboard_event(
self,
_device: Atspi.Device,
pressed: bool,
keycode: int,
keysym: int,
modifiers: int,
text: str
) -> bool:
"""Processes this Atspi keyboard event."""
event = input_event.KeyboardEvent(pressed, keycode, keysym, modifiers, text)
if event in [self._last_input_event, self._last_non_modifier_key_event]:
msg = "INPUT EVENT MANAGER: Received duplicate event."
debug.print_message(debug.LEVEL_INFO, msg, True)
return False
manager = focus_manager.get_manager()
if pressed:
window = manager.get_active_window()
if not AXUtilities.can_be_active_window(window):
new_window = AXUtilities.find_active_window()
if new_window is not None:
window = new_window
tokens = ["INPUT EVENT MANAGER: Updating window and active window to", window]
debug.print_tokens(debug.LEVEL_INFO, tokens, True)
manager.set_active_window(window)
else:
# One example: Brave's popup menus live in frames which lack the active state.
tokens = ["WARNING:", window, "cannot be active window. No alternative found."]
debug.print_tokens(debug.LEVEL_WARNING, tokens, True)
event.set_window(window)
event.set_object(manager.get_locus_of_focus())
event.set_script(script_manager.get_manager().get_active_script())
elif self.last_event_was_keyboard():
assert isinstance(self._last_input_event, input_event.KeyboardEvent)
event.set_window(self._last_input_event.get_window())
event.set_object(self._last_input_event.get_object())
event.set_script(self._last_input_event.get_script())
else:
event.set_window(manager.get_active_window())
event.set_object(manager.get_locus_of_focus())
event.set_script(script_manager.get_manager().get_active_script())
event.set_click_count(self._determine_keyboard_event_click_count(event))
event.process()
if event.is_modifier_key():
if self.is_release_for(event, self._last_input_event):
msg = "INPUT EVENT MANAGER: Clearing last non modifier key event"
debug.print_message(debug.LEVEL_INFO, msg, True)
self._last_non_modifier_key_event = None
else:
self._last_non_modifier_key_event = event
self._last_input_event = event
return True
# pylint: enable=too-many-arguments
# pylint: enable=too-many-positional-arguments
def _determine_keyboard_event_click_count(self, event: input_event.KeyboardEvent) -> int:
"""Determines the click count of event."""
if not self.last_event_was_keyboard():
return 1
if event.is_modifier_key():
last_event = self._last_input_event
else:
last_event = self._last_non_modifier_key_event or self._last_input_event
assert isinstance(last_event, input_event.KeyboardEvent)
if (event.time - last_event.time > settings.doubleClickTimeout) or \
(event.keyval_name != last_event.keyval_name) or \
(event.get_object() != last_event.get_object()):
return 1
last_count = last_event.get_click_count()
if not event.is_pressed_key():
return last_count
if last_event.is_pressed_key():
return last_count
if (event.is_modifier_key() and last_count == 2) or last_count == 3:
return 1
return last_count + 1
def _determine_mouse_event_click_count(self, event: input_event.MouseButtonEvent) -> int:
"""Determines the click count of event."""
if not self.last_event_was_mouse_button():
return 1
assert isinstance(self._last_input_event, input_event.MouseButtonEvent)
if not event.pressed:
return self._last_input_event.get_click_count()
if self._last_input_event.button != event.button:
return 1
if event.time - self._last_input_event.time > settings.doubleClickTimeout:
return 1
return self._last_input_event.get_click_count() + 1
def last_event_was_keyboard(self) -> bool:
"""Returns True if the last event is a keyboard event."""
return isinstance(self._last_input_event, input_event.KeyboardEvent)
def last_event_was_mouse_button(self) -> bool:
"""Returns True if the last event is a mouse button event."""
return isinstance(self._last_input_event, input_event.MouseButtonEvent)
def is_release_for(self, event1, event2):
"""Returns True if event1 is a release for event2."""
if event1 is None or event2 is None:
return False
if not isinstance(event1, input_event.KeyboardEvent) \
or not isinstance(event2, input_event.KeyboardEvent):
return False
if event1.is_pressed_key() or not event2.is_pressed_key():
return False
result = event1.id == event2.id \
and event1.hw_code == event2.hw_code \
and event1.keyval_name == event2.keyval_name
if result and not event1.is_modifier_key:
result = event1.modifiers == event2.modifiers
msg = (
f"INPUT EVENT MANAGER: {event1.as_single_line_string()} "
f"is release for {event2.as_single_line_string()}: {result}"
)
debug.print_message(debug.LEVEL_INFO, msg, True)
return result
def last_event_equals_or_is_release_for_event(self, event):
"""Returns True if the last non-modifier event equals, or is the release for, event."""
if self._last_non_modifier_key_event is None:
return False
if event == self._last_non_modifier_key_event:
return True
return self.is_release_for(self._last_non_modifier_key_event, event)
def _last_key_and_modifiers(self):
"""Returns the last keyval name and modifiers"""
if self._last_non_modifier_key_event is None:
return "", 0
if not self.last_event_was_keyboard():
return "", 0
return self._last_non_modifier_key_event.keyval_name, self._last_input_event.modifiers
def _last_keycode_and_modifiers(self):
"""Returns the last keycode and modifiers"""
if self._last_non_modifier_key_event is None:
return 0, 0
if not isinstance(self._last_input_event, input_event.KeyboardEvent):
return 0, 0
return self._last_non_modifier_key_event.hw_code, self._last_input_event.modifiers
@staticmethod
def _all_names_for_key_code(keycode):
"""Returns all the possible names associated with keycode."""
keymap = Gdk.Keymap.get_default()
entries = keymap.get_entries_for_keycode(keycode)[-1]
return list(map(Gdk.keyval_name, set(entries)))
def last_event_was_command(self):
"""Returns True if the last event is believed to be a command."""
if bool(self._last_key_and_modifiers()[1] & 1 << Atspi.ModifierType.CONTROL):
msg = "INPUT EVENT MANAGER: Last event was command."
debug.print_message(debug.LEVEL_INFO, msg, True)
return True
return False
def last_event_was_shortcut_for(self, obj):
"""Returns True if the last event is believed to be a shortcut key for obj."""
string = self._last_key_and_modifiers()[0]
if not string:
return False
rv = False
keys = AXObject.get_action_key_binding(obj, 0).split(";")
for key in keys:
if key.endswith(string.upper()):
rv = True
break
if rv:
tokens = ["INPUT EVENT MANAGER: Last event was shortcut for", obj]
debug.print_tokens(debug.LEVEL_INFO, tokens, True)
return rv
def last_event_was_printable_key(self):
"""Returns True if the last event is believed to be a printable key."""
if not self.last_event_was_keyboard():
return False
if self._last_input_event.is_printable_key():
msg = "INPUT EVENT MANAGER: Last event was printable key"
debug.print_message(debug.LEVEL_INFO, msg, True)
return True
return False
def last_event_was_caret_navigation(self):
"""Returns True if the last event is believed to be caret navigation."""
return self.last_event_was_character_navigation() \
or self.last_event_was_word_navigation() \
or self.last_event_was_line_navigation() \
or self.last_event_was_line_boundary_navigation() \
or self.last_event_was_file_boundary_navigation() \
or self.last_event_was_page_navigation()
def last_event_was_caret_selection(self):
"""Returns True if the last event is believed to be caret selection."""
string, mods = self._last_key_and_modifiers()
if string not in ["Home", "End", "Up", "Down", "Left", "Right"]:
rv = False
else:
rv = bool(mods & 1 << Atspi.ModifierType.SHIFT)
if rv:
msg = "INPUT EVENT MANAGER: Last event was caret selection"
debug.print_message(debug.LEVEL_INFO, msg, True)
return rv
def last_event_was_backward_caret_navigation(self):
"""Returns True if the last event is believed to be backward caret navigation."""
string, mods = self._last_key_and_modifiers()
if string not in ["Up", "Left"]:
rv = False
else:
rv = not mods & 1 << Atspi.ModifierType.SHIFT
if rv:
msg = "INPUT EVENT MANAGER: Last event was backward caret navigation"
debug.print_message(debug.LEVEL_INFO, msg, True)
return rv
def last_event_was_forward_caret_navigation(self):
"""Returns True if the last event is believed to be forward caret navigation."""
string, mods = self._last_key_and_modifiers()
if string not in ["Down", "Right"]:
rv = False
else:
rv = not mods & 1 << Atspi.ModifierType.SHIFT
if rv:
msg = "INPUT EVENT MANAGER: Last event was forward caret navigation"
debug.print_message(debug.LEVEL_INFO, msg, True)
return rv
def last_event_was_forward_caret_selection(self):
"""Returns True if the last event is believed to be forward caret selection."""
string, mods = self._last_key_and_modifiers()
if string not in ["Down", "Right"]:
rv = False
else:
rv = bool(mods & 1 << Atspi.ModifierType.SHIFT)
if rv:
msg = "INPUT EVENT MANAGER: Last event was forward caret selection"
debug.print_message(debug.LEVEL_INFO, msg, True)
return rv
def last_event_was_character_navigation(self):
"""Returns True if the last event is believed to be character navigation."""
string, mods = self._last_key_and_modifiers()
if string not in ["Left", "Right"]:
rv = False
elif mods & 1 << Atspi.ModifierType.CONTROL or mods & 1 << Atspi.ModifierType.ALT:
rv = False
else:
rv = True
if rv:
msg = "INPUT EVENT MANAGER: Last event was character navigation"
debug.print_message(debug.LEVEL_INFO, msg, True)
return rv
def last_event_was_word_navigation(self):
"""Returns True if the last event is believed to be word navigation."""
string, mods = self._last_key_and_modifiers()
if string not in ["Left", "Right"]:
rv = False
else:
rv = bool(mods & 1 << Atspi.ModifierType.CONTROL)
if rv:
msg = "INPUT EVENT MANAGER: Last event was word navigation"
debug.print_message(debug.LEVEL_INFO, msg, True)
return rv
def last_event_was_previous_word_navigation(self):
"""Returns True if the last event is believed to be previous-word navigation."""
string, mods = self._last_key_and_modifiers()
if string != "Left":
rv = False
else:
rv = bool(mods & 1 << Atspi.ModifierType.CONTROL)
if rv:
msg = "INPUT EVENT MANAGER: Last event was previous-word navigation"
debug.print_message(debug.LEVEL_INFO, msg, True)
return rv
def last_event_was_next_word_navigation(self):
"""Returns True if the last event is believed to be next-word navigation."""
string, mods = self._last_key_and_modifiers()
if string != "Right":
rv = False
else:
rv = bool(mods & 1 << Atspi.ModifierType.CONTROL)
if rv:
msg = "INPUT EVENT MANAGER: Last event was next-word navigation"
debug.print_message(debug.LEVEL_INFO, msg, True)
return rv
def last_event_was_line_navigation(self):
"""Returns True if the last event is believed to be line navigation."""
string, mods = self._last_key_and_modifiers()
if string not in ["Up", "Down"]:
rv = False
elif mods & 1 << Atspi.ModifierType.CONTROL:
rv = False
else:
focus = focus_manager.get_manager().get_locus_of_focus()
if AXUtilities.is_single_line(focus):
rv = False
else:
rv = not AXUtilities.is_widget_controlled_by_line_navigation(focus)
if rv:
msg = "INPUT EVENT MANAGER: Last event was line navigation"
debug.print_message(debug.LEVEL_INFO, msg, True)
return rv
def last_event_was_paragraph_navigation(self):
"""Returns True if the last event is believed to be paragraph navigation."""
string, mods = self._last_key_and_modifiers()
if not (string in ["Up", "Down"] and mods & 1 << Atspi.ModifierType.CONTROL):
rv = False
else:
rv = not mods & 1 << Atspi.ModifierType.SHIFT
if rv:
msg = "INPUT EVENT MANAGER: Last event was paragraph navigation"
debug.print_message(debug.LEVEL_INFO, msg, True)
return rv
def last_event_was_line_boundary_navigation(self):
"""Returns True if the last event is believed to be navigation to start/end of line."""
string, mods = self._last_key_and_modifiers()
if string not in ["Home", "End"]:
rv = False
else:
rv = not mods & 1 << Atspi.ModifierType.CONTROL
if rv:
msg = "INPUT EVENT MANAGER: Last event was line boundary navigation"
debug.print_message(debug.LEVEL_INFO, msg, True)
return rv
def last_event_was_file_boundary_navigation(self):
"""Returns True if the last event is believed to be navigation to top/bottom of file."""
string, mods = self._last_key_and_modifiers()
if string not in ["Home", "End"]:
rv = False
else:
rv = bool(mods & 1 << Atspi.ModifierType.CONTROL)
if rv:
msg = "INPUT EVENT MANAGER: Last event was file boundary navigation"
debug.print_message(debug.LEVEL_INFO, msg, True)
return rv
def last_event_was_page_navigation(self):
"""Returns True if the last event is believed to be page navigation."""
string, mods = self._last_key_and_modifiers()
if string not in ["Page_Up", "Page_Down"]:
rv = False
elif mods & 1 << Atspi.ModifierType.CONTROL:
rv = False
else:
focus = focus_manager.get_manager().get_locus_of_focus()
if AXUtilities.is_single_line(focus):
rv = False
else:
rv = not AXUtilities.is_widget_controlled_by_line_navigation(focus)
if rv:
msg = "INPUT EVENT MANAGER: Last event was page navigation"
debug.print_message(debug.LEVEL_INFO, msg, True)
return rv
def last_event_was_page_switch(self):
"""Returns True if the last event is believed to be a page switch."""
string, mods = self._last_key_and_modifiers()
if string.isnumeric():
rv = bool(mods & 1 << Atspi.ModifierType.ALT)
elif string in ["Page_Up", "Page_Down"]:
rv = bool(mods & 1 << Atspi.ModifierType.CONTROL)
else:
rv = False
if rv:
msg = "INPUT EVENT MANAGER: Last event was page switch"
debug.print_message(debug.LEVEL_INFO, msg, True)
return rv
def last_event_was_tab_navigation(self):
"""Returns True if the last event is believed to be Tab navigation."""
string, mods = self._last_key_and_modifiers()
if string not in ["Tab", "ISO_Left_Tab"]:
rv = False
elif mods & 1 << Atspi.ModifierType.CONTROL or mods & 1 << Atspi.ModifierType.ALT:
rv = False
else:
rv = True
if rv:
msg = "INPUT EVENT MANAGER: Last event was Tab navigation"
debug.print_message(debug.LEVEL_INFO, msg, True)
return rv
def last_event_was_table_sort(self):
"""Returns True if the last event is believed to be a table sort."""
focus = focus_manager.get_manager().get_locus_of_focus()
if not AXUtilities.is_table_header(focus):
rv = False
elif self.last_event_was_mouse_button():
rv = self.last_event_was_primary_click()
elif self.last_event_was_keyboard():
rv = self.last_event_was_return_or_space()
else:
rv = False
if rv:
msg = "INPUT EVENT MANAGER: Last event was table sort"
debug.print_message(debug.LEVEL_INFO, msg, True)
return rv
def last_event_was_unmodified_arrow(self):
"""Returns True if the last event is an unmodified arrow."""
string, mods = self._last_key_and_modifiers()
if string not in ["Left", "Right", "Up", "Down"]:
return False
if mods & 1 << Atspi.ModifierType.CONTROL \
or mods & 1 << Atspi.ModifierType.SHIFT \
or mods & 1 << Atspi.ModifierType.ALT:
return False
# TODO: JD - 8 is the value of keybindings.MODIFIER_ORCA, but we need to
# avoid a circular import.
if mods & 1 << 8:
return False
return True
def last_event_was_alt_modified(self):
"""Returns True if the last event was alt-modified."""
mods = self._last_key_and_modifiers()[-1]
return mods & 1 << Atspi.ModifierType.ALT
def last_event_was_backspace(self):
"""Returns True if the last event is BackSpace."""
return self._last_key_and_modifiers()[0] == "BackSpace"
def last_event_was_down(self):
"""Returns True if the last event is Down."""
return self._last_key_and_modifiers()[0] == "Down"
def last_event_was_f1(self):
"""Returns True if the last event is F1."""
return self._last_key_and_modifiers()[0] == "F1"
def last_event_was_left(self):
"""Returns True if the last event is Left."""
return self._last_key_and_modifiers()[0] == "Left"
def last_event_was_left_or_right(self):
"""Returns True if the last event is Left or Right."""
return self._last_key_and_modifiers()[0] in ["Left", "Right"]
def last_event_was_page_up_or_page_down(self):
"""Returns True if the last event is Page_Up or Page_Down."""
return self._last_key_and_modifiers()[0] in ["Page_Up", "Page_Down"]
def last_event_was_right(self):
"""Returns True if the last event is Right."""
return self._last_key_and_modifiers()[0] == "Right"
def last_event_was_return(self):
"""Returns True if the last event is Return."""
return self._last_key_and_modifiers()[0] == "Return"
def last_event_was_return_or_space(self):
"""Returns True if the last event is Return or space."""
return self._last_key_and_modifiers()[0] in ["Return", "space", " "]
def last_event_was_return_tab_or_space(self):
"""Returns True if the last event is Return, Tab, or space."""
return self._last_key_and_modifiers()[0] in ["Return", "Tab", "space", " "]
def last_event_was_space(self):
"""Returns True if the last event is space."""
return self._last_key_and_modifiers()[0] in [" ", "space"]
def last_event_was_tab(self):
"""Returns True if the last event is Tab."""
return self._last_key_and_modifiers()[0] == "Tab"
def last_event_was_up(self):
"""Returns True if the last event is Up."""
return self._last_key_and_modifiers()[0] == "Up"
def last_event_was_up_or_down(self):
"""Returns True if the last event is Up or Down."""
return self._last_key_and_modifiers()[0] in ["Up", "Down"]
def last_event_was_delete(self):
"""Returns True if the last event is believed to be delete."""
keycode, mods = self._last_keycode_and_modifiers()
keynames = self._all_names_for_key_code(keycode)
if "Delete" in keynames or "KP_Delete" in keynames:
rv = True
elif "d" in keynames:
rv = bool(mods & 1 << Atspi.ModifierType.CONTROL)
else:
rv = False
if rv:
msg = "INPUT EVENT MANAGER: Last event was delete"
debug.print_message(debug.LEVEL_INFO, msg, True)
return rv
def last_event_was_cut(self):
"""Returns True if the last event is believed to be the cut command."""
keycode, mods = self._last_keycode_and_modifiers()
keynames = self._all_names_for_key_code(keycode)
if "x" not in keynames:
return False
if mods & 1 << Atspi.ModifierType.CONTROL and not mods & 1 << Atspi.ModifierType.SHIFT:
msg = "INPUT EVENT MANAGER: Last event was cut"
debug.print_message(debug.LEVEL_INFO, msg, True)
return True
return False
def last_event_was_copy(self):
"""Returns True if the last event is believed to be the copy command."""
keycode, mods = self._last_keycode_and_modifiers()
keynames = self._all_names_for_key_code(keycode)
if "c" not in keynames or not mods & 1 << Atspi.ModifierType.CONTROL:
rv = False
elif AXUtilities.is_terminal(self._last_input_event.get_object()):
rv = mods & 1 << Atspi.ModifierType.SHIFT
else:
rv = not mods & 1 << Atspi.ModifierType.SHIFT
if rv:
msg = "INPUT EVENT MANAGER: Last event was copy"
debug.print_message(debug.LEVEL_INFO, msg, True)
return rv
def last_event_was_paste(self):
"""Returns True if the last event is believed to be the paste command."""
keycode, mods = self._last_keycode_and_modifiers()
keynames = self._all_names_for_key_code(keycode)
if "v" not in keynames or not mods & 1 << Atspi.ModifierType.CONTROL:
rv = False
elif AXUtilities.is_terminal(self._last_input_event.get_object()):
rv = mods & 1 << Atspi.ModifierType.SHIFT
else:
rv = not mods & 1 << Atspi.ModifierType.SHIFT
if rv:
msg = "INPUT EVENT MANAGER: Last event was paste"
debug.print_message(debug.LEVEL_INFO, msg, True)
return rv
def last_event_was_undo(self):
"""Returns True if the last event is believed to be the undo command."""
keycode, mods = self._last_keycode_and_modifiers()
keynames = self._all_names_for_key_code(keycode)
if "z" not in keynames:
return False
if mods & 1 << Atspi.ModifierType.CONTROL and not mods & 1 << Atspi.ModifierType.SHIFT:
msg = "INPUT EVENT MANAGER: Last event was undo"
debug.print_message(debug.LEVEL_INFO, msg, True)
return True
return False
def last_event_was_redo(self):
"""Returns True if the last event is believed to be the redo command."""
keycode, mods = self._last_keycode_and_modifiers()
keynames = self._all_names_for_key_code(keycode)
if "z" in keynames:
rv = mods & 1 << Atspi.ModifierType.CONTROL and mods & 1 << Atspi.ModifierType.SHIFT
elif "y" in keynames:
# LibreOffice
rv = mods & 1 << Atspi.ModifierType.CONTROL \
and not mods & 1 << Atspi.ModifierType.SHIFT
else:
rv = False
if rv:
msg = "INPUT EVENT MANAGER: Last event was redo"
debug.print_message(debug.LEVEL_INFO, msg, True)
return rv
def last_event_was_select_all(self):
"""Returns True if the last event is believed to be the select all command."""
keycode, mods = self._last_keycode_and_modifiers()
keynames = self._all_names_for_key_code(keycode)
if "a" not in keynames:
return False
if (mods & 1 << Atspi.ModifierType.CONTROL and not mods & 1 << Atspi.ModifierType.SHIFT):
msg = "INPUT EVENT MANAGER: Last event was select all"
debug.print_message(debug.LEVEL_INFO, msg, True)
return True
return False
def last_event_was_primary_click(self):
"""Returns True if the last event is a primary mouse click."""
if not self.last_event_was_mouse_button():
return False
if self._last_input_event.button == "1" and self._last_input_event.pressed:
msg = "INPUT EVENT MANAGER: Last event was primary click"
debug.print_message(debug.LEVEL_INFO, msg, True)
return True
return False
def last_event_was_primary_release(self):
"""Returns True if the last event is a primary mouse release."""
if not self.last_event_was_mouse_button():
return False
if self._last_input_event.button == "1" and not self._last_input_event.pressed:
msg = "INPUT EVENT MANAGER: Last event was primary release"
debug.print_message(debug.LEVEL_INFO, msg, True)
return True
return False
def last_event_was_primary_click_or_release(self):
"""Returns True if the last event is a primary mouse click or release."""
if not self.last_event_was_mouse_button():
return False
if self._last_input_event.button == "1":
msg = "INPUT EVENT MANAGER: Last event was primary click or release"
debug.print_message(debug.LEVEL_INFO, msg, True)
return True
return False
def last_event_was_middle_click(self):
"""Returns True if the last event is a middle mouse click."""
if not self.last_event_was_mouse_button():
return False
if self._last_input_event.button == "2" and self._last_input_event.pressed:
msg = "INPUT EVENT MANAGER: Last event was middle click"
debug.print_message(debug.LEVEL_INFO, msg, True)
return True
return False
def last_event_was_middle_release(self):
"""Returns True if the last event is a middle mouse release."""
if not self.last_event_was_mouse_button():
return False
if self._last_input_event.button == "2" and not self._last_input_event.pressed:
msg = "INPUT EVENT MANAGER: Last event was middle release"
debug.print_message(debug.LEVEL_INFO, msg, True)
return True
return False
def last_event_was_secondary_click(self):
"""Returns True if the last event is a secondary mouse click."""
if not self.last_event_was_mouse_button():
return False
if self._last_input_event.button == "3" and self._last_input_event.pressed:
msg = "INPUT EVENT MANAGER: Last event was secondary click"
debug.print_message(debug.LEVEL_INFO, msg, True)
return True
return False
def last_event_was_secondary_release(self):
"""Returns True if the last event is a secondary mouse release."""
if not self.last_event_was_mouse_button():
return False
if self._last_input_event.button == "3" and not self._last_input_event.pressed:
msg = "INPUT EVENT MANAGER: Last event was secondary release"
debug.print_message(debug.LEVEL_INFO, msg, True)
return True
return False
_manager = InputEventManager()
def get_manager():
"""Returns the Input Event Manager singleton."""
return _manager