%PDF- %PDF-
| Direktori : /lib/python3/dist-packages/orca/ |
| Current File : //lib/python3/dist-packages/orca/event_manager.py |
# Orca
#
# Copyright 2011. Orca Team.
# Author: Joanmarie Diggs <joanmarie.diggs@gmail.com>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the
# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
# Boston MA 02110-1301 USA.
__id__ = "$Id$"
__version__ = "$Revision$"
__date__ = "$Date$"
__copyright__ = "Copyright (c) 2011. Orca Team."
__license__ = "LGPL"
import gi
gi.require_version('Atspi', '2.0')
from gi.repository import Atspi
from gi.repository import GLib
import queue
import threading
import time
from . import debug
from . import focus_manager
from . import input_event
from . import orca_modifier_manager
from . import orca_state
from . import script_manager
from . import settings
from .ax_object import AXObject
from .ax_utilities import AXUtilities
class EventManager:
def __init__(self):
debug.printMessage(debug.LEVEL_INFO, 'EVENT MANAGER: Initializing', True)
self._scriptListenerCounts = {}
self._active = False
self._paused = False
self._eventQueue = queue.Queue(0)
self._gidleId = 0
self._gidleLock = threading.Lock()
self._listener = Atspi.EventListener.new(self._enqueue_object_event)
orca_state.device = None
self.bypassedKey = None
debug.printMessage(debug.LEVEL_INFO, 'Event manager initialized', True)
def activate(self):
"""Called when this event manager is activated."""
debug.printMessage(debug.LEVEL_INFO, 'EVENT MANAGER: Activating', True)
orca_state.device = Atspi.Device.new()
orca_state.device.event_count = 0
orca_state.device.key_watcher = \
orca_state.device.add_key_watcher(self._processKeyboardEvent)
self._active = True
debug.printMessage(debug.LEVEL_INFO, 'EVENT MANAGER: Activated', True)
def deactivate(self):
"""Called when this event manager is deactivated."""
debug.printMessage(debug.LEVEL_INFO, 'EVENT MANAGER: Deactivating', True)
self._active = False
self._eventQueue = queue.Queue(0)
self._scriptListenerCounts = {}
orca_state.device = None
debug.printMessage(debug.LEVEL_INFO, 'EVENT MANAGER: Deactivated', True)
def pauseQueuing(self, pause=True, clearQueue=False, reason=""):
"""Pauses/unpauses event queuing."""
msg = f"EVENT MANAGER: Pause queueing: {pause}. Clear queue: {clearQueue}. {reason}"
debug.printMessage(debug.LEVEL_INFO, msg, True)
self._paused = pause
if clearQueue:
self._eventQueue = queue.Queue(0)
def _isObsoletedBy(self, event):
"""Returns the event which renders this one no longer worthy of being processed."""
def isSame(x):
return x.type == event.type \
and x.source == event.source \
and x.detail1 == event.detail1 \
and x.detail2 == event.detail2 \
and x.any_data == event.any_data
def obsoletesIfSameTypeAndObject(x):
skippable = {
"document:page-changed",
"object:active-descendant-changed",
"object:children-changed",
"object:property-change",
"object:state-changed",
"object:selection-changed",
"object:text-caret-moved",
"object:text-selection-changed",
"window",
}
if not any(x.type.startswith(etype) for etype in skippable):
return False
return x.source == event.source and x.type == event.type
def obsoletesIfSameTypeInSibling(x):
if x.type != event.type or x.detail1 != event.detail1 or x.detail2 != event.detail2 \
or x.any_data != event.any_data:
return False
skippable = {
"focus",
"object:state-changed:focused",
}
if not any(x.type.startswith(etype) for etype in skippable):
return False
return AXObject.get_parent(x.source) == AXObject.get_parent(event.source)
def obsoletesWindowEvent(x):
skippable = {
"window:activate",
"window:deactivate",
}
if not any(x.type.startswith(etype) for etype in skippable):
return False
if not any(event.type.startswith(etype) for etype in skippable):
return False
if x.source == event.source:
return True
return False
self._eventQueue.mutex.acquire()
try:
events = list(reversed(self._eventQueue.queue))
except Exception as error:
msg = f"EVENT MANAGER: Exception in _isObsoletedBy: {error}"
debug.printMessage(debug.LEVEL_INFO, msg, True)
events = []
finally:
self._eventQueue.mutex.release()
for e in events:
if e == event:
return None
if isSame(e):
tokens = ["EVENT MANAGER:", event, "obsoleted by", e,
"more recent duplicate"]
debug.printTokens(debug.LEVEL_INFO, tokens, True)
return e
if obsoletesIfSameTypeAndObject(e):
tokens = ["EVENT MANAGER:", event, "obsoleted by", e,
"more recent event of same type for same object"]
debug.printTokens(debug.LEVEL_INFO, tokens, True)
return e
if obsoletesIfSameTypeInSibling(e):
tokens = ["EVENT MANAGER:", event, "obsoleted by", e,
"more recent event of same type from sibling"]
debug.printTokens(debug.LEVEL_INFO, tokens, True)
return e
if obsoletesWindowEvent(e):
tokens = ["EVENT MANAGER:", event, "obsoleted by", e,
"more recent window (de)activation event"]
debug.printTokens(debug.LEVEL_INFO, tokens, True)
return e
tokens = ["EVENT MANAGER:", event, "is not obsoleted"]
debug.printTokens(debug.LEVEL_INFO, tokens, True)
return None
def _ignore(self, event):
"""Returns True if this event should be ignored."""
debug.printMessage(debug.LEVEL_INFO, '')
tokens = ["EVENT MANAGER:", event]
debug.printTokens(debug.LEVEL_INFO, tokens, True)
if not self._active or self._paused:
msg = 'EVENT MANAGER: Ignoring because manager is not active or queueing is paused'
debug.printMessage(debug.LEVEL_INFO, msg, True)
return True
event_type = event.type
if event_type.startswith('window') or event_type.startswith('mouse:button'):
return False
if self._inDeluge() and self._ignoreDuringDeluge(event):
msg = 'EVENT MANAGER: Ignoring event type due to deluge'
debug.printMessage(debug.LEVEL_INFO, msg, True)
return True
# Keep these checks early in the process so we can assume them throughout
# the rest of our checks.
focus = focus_manager.getManager().get_locus_of_focus()
if focus == event.source or AXUtilities.is_focused(event.source):
return False
if focus == event.any_data:
return False
if event_type.startswith("object:children-changed"):
child = event.any_data
if child is None:
msg = 'EVENT_MANAGER: Ignoring due to lack of event.any_data'
debug.printMessage(debug.LEVEL_INFO, msg, True)
return True
if "remove" in event_type and AXObject.is_dead(focus):
return False
if AXObject.is_dead(child):
msg = 'EVENT_MANAGER: Ignoring due to dead event.any_data'
debug.printMessage(debug.LEVEL_INFO, msg, True)
return True
if AXUtilities.is_menu_related(child) or AXUtilities.is_image(child):
msg = 'EVENT_MANAGER: Ignoring due to role of event.any_data'
debug.printMessage(debug.LEVEL_INFO, msg, True)
return True
app = AXObject.get_application(event.source)
app_name = AXObject.get_name(app).lower()
if "remove" in event_type and app_name == "gnome-shell":
msg = "EVENT MANAGER: Ignoring event based on type and app"
debug.printMessage(debug.LEVEL_INFO, msg, True)
return True
if event_type.endswith("system") and app_name == "thunderbird":
msg = "EVENT MANAGER: Ignoring event based on type and app"
debug.printMessage(debug.LEVEL_INFO, msg, True)
return True
script = script_manager.getManager().getActiveScript()
if script is None:
msg = "EVENT MANAGER: Ignoring because there is no active script"
debug.printMessage(debug.LEVEL_INFO, msg, True)
return True
if script.app != AXObject.get_application(event.source):
msg = "EVENT MANAGER: Ignoring because event is not from active app"
debug.printMessage(debug.LEVEL_INFO, msg, True)
return True
if event_type.startswith("object:property-change"):
role = AXObject.get_role(event.source)
if "name" in event_type:
if role in [Atspi.Role.CANVAS,
Atspi.Role.CHECK_BOX, # TeamTalk5 spam
Atspi.Role.ICON,
Atspi.Role.IMAGE, # Thunderbird spam
Atspi.Role.LIST, # Web app spam
Atspi.Role.LIST_ITEM, # Web app spam
Atspi.Role.MENU,
Atspi.Role.MENU_ITEM,
Atspi.Role.PANEL, # TeamTalk5 spam
Atspi.Role.RADIO_BUTTON, # TeamTalk5 spam
Atspi.Role.SECTION, # Web app spam
Atspi.Role.TABLE_ROW, # Thunderbird spam
Atspi.Role.TABLE_CELL, # Thunderbird spam
Atspi.Role.TREE_ITEM]: # Thunderbird spam
msg = "EVENT MANAGER: Ignoring event type due to role of unfocused source"
debug.printMessage(debug.LEVEL_INFO, msg, True)
return True
return False
if "value" in event_type:
if role in [Atspi.Role.SPLIT_PANE, Atspi.Role.SCROLL_BAR]:
msg = "EVENT MANAGER: Ignoring event type due to role of unfocused source"
debug.printMessage(debug.LEVEL_INFO, msg, True)
return True
return False
if event_type.startswith('object:selection-changed'):
if AXObject.is_dead(event.source):
msg = "EVENT MANAGER: Ignoring event from dead source"
debug.printMessage(debug.LEVEL_INFO, msg, True)
return True
return False
if event_type.startswith("object:state-changed"):
role = AXObject.get_role(event.source)
if event_type.endswith("system"):
# Thunderbird spams us with these when a message list thread is expanded/collapsed.
if role in [Atspi.Role.TABLE,
Atspi.Role.TABLE_CELL,
Atspi.Role.TABLE_ROW,
Atspi.Role.TREE,
Atspi.Role.TREE_ITEM,
Atspi.Role.TREE_TABLE]:
msg = 'EVENT MANAGER: Ignoring system event based on role'
debug.printMessage(debug.LEVEL_INFO, msg, True)
return True
if "checked" in event_type:
# Gtk 3 apps. See https://gitlab.gnome.org/GNOME/gtk/-/issues/6449
if not AXUtilities.is_showing(event.source):
msg = "EVENT MANAGER: Ignoring event type of unfocused, non-showing source"
debug.printMessage(debug.LEVEL_INFO, msg, True)
return True
return False
if "selected" in event_type:
if not event.detail1 and role in [Atspi.Role.PUSH_BUTTON]:
msg = "EVENT MANAGER: Ignoring event type due to role of source and detail1"
debug.printMessage(debug.LEVEL_INFO, msg, True)
return True
return False
if "sensitive" in event_type:
# The Gedit and Thunderbird scripts pay attention to this event for spellcheck.
if role not in [Atspi.Role.TEXT, Atspi.Role.ENTRY]:
msg = "EVENT MANAGER: Ignoring event type due to role of unfocused source"
debug.printMessage(debug.LEVEL_INFO, msg, True)
return True
return False
if "showing" in event_type:
if role not in [Atspi.Role.ALERT,
Atspi.Role.ANIMATION,
Atspi.Role.DIALOG,
Atspi.Role.INFO_BAR,
Atspi.Role.MENU,
Atspi.Role.NOTIFICATION,
Atspi.Role.STATUS_BAR,
Atspi.Role.TOOL_TIP]:
msg = "EVENT MANAGER: Ignoring event type due to role"
debug.printMessage(debug.LEVEL_INFO, msg, True)
return True
return False
if event_type.startswith('object:text-caret-moved'):
role = AXObject.get_role(event.source)
if role in [Atspi.Role.LABEL]:
msg = "EVENT MANAGER: Ignoring event type due to role of unfocused source"
debug.printMessage(debug.LEVEL_INFO, msg, True)
return True
return False
if event_type.startswith('object:text-changed'):
if "\ufffc" in event.any_data and not event.any_data.replace("\ufffc", ""):
msg = "EVENT MANAGER: Ignoring because changed text is only embedded objects"
debug.printMessage(debug.LEVEL_INFO, msg, True)
return True
if "insert" in event_type and event.detail2 > 1000:
msg = "EVENT MANAGER: Ignoring because inserted text has more than 1000 chars"
debug.printMessage(debug.LEVEL_INFO, msg, True)
return True
if event_type.endswith("system") and AXUtilities.is_selectable(focus):
# Thunderbird spams us with text changes every time the selected item changes.
msg = "EVENT MANAGER: Ignoring because event is suspected spam"
debug.printMessage(debug.LEVEL_INFO, msg, True)
return True
return False
return False
def _queuePrintln(self, e, isEnqueue=True, isPrune=None):
"""Convenience method to output queue-related debugging info."""
if debug.LEVEL_INFO < debug.debugLevel:
return
tokens = []
if isinstance(e, input_event.KeyboardEvent):
tokens.extend([e.event_string, e.hw_code])
elif isinstance(e, input_event.BrailleEvent):
tokens.append(e.event)
elif not debug.eventDebugFilter or debug.eventDebugFilter.match(e.type):
tokens.append(e)
else:
return
if isPrune:
tokens[0:0] = ["EVENT MANAGER: Pruning"]
elif isPrune is not None:
tokens[0:0] = ["EVENT MANAGER: Not pruning"]
elif isEnqueue:
tokens[0:0] = ["EVENT MANAGER: Queueing"]
else:
tokens[0:0] = ["EVENT MANAGER: Dequeued"]
debug.printTokens(debug.LEVEL_INFO, tokens, True)
def _enqueue_object_event(self, e):
"""Callback for Atspi object events."""
if self._ignore(e):
return
self._queuePrintln(e)
if self._inFlood() and self._prioritizeDuringFlood(e):
msg = 'EVENT MANAGER: Pruning event queue due to flood.'
debug.printMessage(debug.LEVEL_INFO, msg, True)
self._pruneEventsDuringFlood()
app = AXObject.get_application(e.source)
tokens = ["EVENT MANAGER: App for event source is", app]
debug.printTokens(debug.LEVEL_INFO, tokens, True)
script = script_manager.getManager().getScript(app, e.source)
script.eventCache[e.type] = (e, time.time())
self._gidleLock.acquire()
self._eventQueue.put(e)
if not self._gidleId:
self._gidleId = GLib.idle_add(self._dequeue_object_event)
self._gidleLock.release()
def _onNoFocus(self):
if focus_manager.getManager().focus_and_window_are_unknown():
return False
if script_manager.getManager().getActiveScript() is None:
defaultScript = script_manager.getManager().getDefaultScript()
script_manager.getManager().setActiveScript(defaultScript, 'No focus')
defaultScript.idleMessage()
return False
def _dequeue_object_event(self):
"""Handles all object events destined for scripts."""
rerun = True
try:
event = self._eventQueue.get_nowait()
self._queuePrintln(event, isEnqueue=False)
debug.objEvent = event
debugging = not debug.eventDebugFilter \
or debug.eventDebugFilter.match(event.type)
if debugging:
startTime = time.time()
msg = (
f"\nvvvvv PROCESS OBJECT EVENT {event.type} "
f"(queue size: {self._eventQueue.qsize()}) vvvvv"
)
debug.printMessage(debug.eventDebugLevel, msg, False)
self._processObjectEvent(event)
if debugging:
msg = (
f"TOTAL PROCESSING TIME: {time.time() - startTime:.4f}"
f"\n^^^^^ PROCESS OBJECT EVENT {event.type} ^^^^^\n"
)
debug.printMessage(debug.eventDebugLevel, msg, False)
debug.objEvent = None
self._gidleLock.acquire()
if self._eventQueue.empty():
GLib.timeout_add(2500, self._onNoFocus)
self._gidleId = 0
rerun = False # destroy and don't call again
self._gidleLock.release()
except queue.Empty:
msg = 'EVENT MANAGER: Attempted dequeue, but the event queue is empty'
debug.printMessage(debug.LEVEL_SEVERE, msg, True)
self._gidleId = 0
rerun = False # destroy and don't call again
except Exception:
debug.printException(debug.LEVEL_SEVERE)
return rerun
def registerListener(self, eventType):
"""Tells this module to listen for the given event type.
Arguments:
- eventType: the event type.
"""
msg = f'EVENT MANAGER: registering listener for: {eventType}'
debug.printMessage(debug.LEVEL_INFO, msg, True)
if eventType in self._scriptListenerCounts:
self._scriptListenerCounts[eventType] += 1
else:
self._listener.register(eventType)
self._scriptListenerCounts[eventType] = 1
def deregisterListener(self, eventType):
"""Tells this module to stop listening for the given event type.
Arguments:
- eventType: the event type.
"""
msg = f'EVENT MANAGER: deregistering listener for: {eventType}'
debug.printMessage(debug.LEVEL_INFO, msg, True)
if eventType not in self._scriptListenerCounts:
return
self._scriptListenerCounts[eventType] -= 1
if self._scriptListenerCounts[eventType] == 0:
self._listener.deregister(eventType)
del self._scriptListenerCounts[eventType]
def registerScriptListeners(self, script):
"""Tells the event manager to start listening for all the event types
of interest to the script.
Arguments:
- script: the script.
"""
tokens = ["EVENT MANAGER: Registering listeners for:", script]
debug.printTokens(debug.LEVEL_INFO, tokens, True)
for eventType in script.listeners.keys():
self.registerListener(eventType)
def deregisterScriptListeners(self, script):
"""Tells the event manager to stop listening for all the event types
of interest to the script.
Arguments:
- script: the script.
"""
tokens = ["EVENT MANAGER: De-registering listeners for:", script]
debug.printTokens(debug.LEVEL_INFO, tokens, True)
for eventType in script.listeners.keys():
self.deregisterListener(eventType)
@staticmethod
def _getScriptForEvent(event):
"""Returns the script associated with event."""
if event.type.startswith("mouse:"):
mouseEvent = input_event.MouseButtonEvent(event)
script = script_manager.getManager().getScript(mouseEvent.app, mouseEvent.window, False)
tokens = ["EVENT MANAGER: Script for event is", script]
debug.printTokens(debug.LEVEL_INFO, tokens, True)
return script
script = None
app = AXObject.get_application(event.source)
if AXUtilities.is_defunct(app):
tokens = ["EVENT MANAGER:", app, "is defunct. Cannot get script for event."]
debug.printTokens(debug.LEVEL_WARNING, tokens, True)
return None
skipCheck = {
"object:children-changed",
"object:column-reordered",
"object:row-reordered",
"object:property-change",
"object:selection-changed",
"object:state-changed:checked",
"object:state-changed:expanded",
"object:state-changed:indeterminate",
"object:state-changed:pressed",
"object:state-changed:selected",
"object:state-changed:sensitive",
"object:state-changed:showing",
"object:text-changed",
}
check = not any(event.type.startswith(x) for x in skipCheck)
tokens = ["EVENT MANAGER: Getting script for event for", app, "check:", check]
debug.printTokens(debug.LEVEL_INFO, tokens, True)
script = script_manager.getManager().getScript(app, event.source, sanityCheck=check)
tokens = ["EVENT MANAGER: Script for event is", script]
debug.printTokens(debug.LEVEL_INFO, tokens, True)
return script
def _isActivatableEvent(self, event, script=None):
"""Determines if the event is one which should cause us to
change which script is currently active.
Returns a (boolean, string) tuple indicating whether or not
this is an activatable event, and our reason (for the purpose
of debugging).
"""
if not event.source:
return False, "event.source? What event.source??"
if not script:
script = self._getScriptForEvent(event)
if not script:
return False, "There is no script for this event."
if script == script_manager.getManager().getActiveScript():
return False, "The script for this event is already active."
if not script.isActivatableEvent(event):
return False, "The script says not to activate for this event."
if script.forceScriptActivation(event):
return True, "The script insists it should be activated for this event."
eType = event.type
if eType.startswith('window:activate'):
windowActivation = True
else:
windowActivation = eType.startswith('object:state-changed:active') \
and event.detail1 and AXUtilities.is_frame(event.source)
if windowActivation:
if event.source != focus_manager.getManager().get_active_window():
return True, "Window activation"
else:
return False, "Window activation for already-active window"
if eType.startswith('focus') \
or (eType.startswith('object:state-changed:focused')
and event.detail1):
return True, "Event source claimed focus."
if eType.startswith('object:state-changed:selected') and event.detail1 \
and AXUtilities.is_menu(event.source) and AXUtilities.is_focusable(event.source):
return True, "Selection change in focused menu"
# This condition appears with gnome-screensaver-dialog.
# See bug 530368.
if eType.startswith('object:state-changed:showing') \
and AXUtilities.is_panel(event.source) and AXUtilities.is_modal(event.source):
return True, "Modal panel is showing."
return False, "No reason found to activate a different script."
def _eventSourceIsDead(self, event):
if AXObject.is_dead(event.source):
tokens = ["EVENT MANAGER: source of", event.type, "is dead"]
debug.printTokens(debug.LEVEL_INFO, tokens, True)
return True
return False
def _ignoreDuringDeluge(self, event):
"""Returns true if this event should be ignored during a deluge."""
if self._eventSourceIsDead(event):
return True
ignore = ["object:text-changed:delete",
"object:text-changed:insert",
"object:text-changed:delete:system",
"object:text-changed:insert:system",
"object:text-attributes-changed",
"object:text-caret-moved",
"object:children-changed:add",
"object:children-changed:add:system",
"object:children-changed:remove",
"object:children-changed:remove:system",
"object:property-change:accessible-name",
"object:property-change:accessible-description",
"object:selection-changed",
"object:state-changed:showing",
"object:state-changed:sensitive"]
if event.type not in ignore:
return False
return event.source != focus_manager.getManager().get_locus_of_focus()
def _inDeluge(self):
size = self._eventQueue.qsize()
if size > 100:
msg = f"EVENT MANAGER: DELUGE! Queue size is {size}"
debug.printMessage(debug.LEVEL_INFO, msg, True)
return True
return False
def _processDuringFlood(self, event, focus=None):
"""Returns true if this event should be processed during a flood."""
if self._eventSourceIsDead(event):
return False
ignore = ["object:text-changed:delete",
"object:text-changed:insert",
"object:text-changed:delete:system",
"object:text-changed:insert:system",
"object:text-attributes-changed",
"object:text-caret-moved",
"object:children-changed:add",
"object:children-changed:add:system",
"object:children-changed:remove",
"object:children-changed:remove:system",
"object:property-change:accessible-name",
"object:property-change:accessible-description",
"object:selection-changed",
"object:state-changed:showing",
"object:state-changed:sensitive"]
if event.type not in ignore:
return True
focus = focus or focus_manager.getManager().get_locus_of_focus()
return event.source == focus
def _prioritizeDuringFlood(self, event):
"""Returns true if this event should be prioritized during a flood."""
if event.type.startswith("object:state-changed:focused"):
return event.detail1
if event.type.startswith("object:state-changed:selected"):
return event.detail1
if event.type.startswith("object:text-selection-changed"):
return True
if event.type.startswith("window:activate"):
return True
if event.type.startswith("window:deactivate"):
return True
if event.type.startswith("object:state-changed:active"):
return AXUtilities.is_frame(event.source) or AXUtilities.is_window(event.source)
if event.type.startswith("document:load-complete"):
return True
if event.type.startswith("object:state-changed:busy"):
return True
return False
def _pruneEventsDuringFlood(self):
"""Gets rid of events we don't care about during a flood."""
oldSize = self._eventQueue.qsize()
newQueue = queue.Queue(0)
focus = focus_manager.getManager().get_locus_of_focus()
while not self._eventQueue.empty():
try:
event = self._eventQueue.get()
except Exception as error:
msg = f"EVENT MANAGER: Exception pruning events: {error}"
debug.printMessage(debug.LEVEL_INFO, msg, True)
else:
if self._processDuringFlood(event, focus):
newQueue.put(event)
self._queuePrintln(event, isPrune=False)
finally:
if not self._eventQueue.empty():
self._eventQueue.task_done()
self._eventQueue = newQueue
newSize = self._eventQueue.qsize()
msg = f"EVENT MANAGER: {oldSize - newSize} events pruned. New size: {newSize}"
debug.printMessage(debug.LEVEL_INFO, msg, True)
def _inFlood(self):
size = self._eventQueue.qsize()
if size > 50:
msg = f"EVENT MANAGER: FLOOD? Queue size is {size}"
debug.printMessage(debug.LEVEL_INFO, msg, True)
return True
return False
def _shouldProcessEvent(self, event, eventScript, activeScript):
if eventScript == activeScript:
msg = f"EVENT MANAGER: Processing {event.type}: script for event is active"
debug.printMessage(debug.LEVEL_INFO, msg, True)
return True
if eventScript.presentIfInactive:
msg = f"EVENT MANAGER: Processing {event.type}: script handles events when inactive"
debug.printMessage(debug.LEVEL_INFO, msg, True)
return True
if AXUtilities.is_progress_bar(event.source) \
and settings.progressBarVerbosity == settings.PROGRESS_BAR_ALL:
msg = f"EVENT MANAGER: Processing {event.type}: progress bar verbosity is 'all'"
debug.printMessage(debug.LEVEL_INFO, msg, True)
return True
msg = f"EVENT MANAGER: Not processing {event.type} due to lack of reason"
debug.printMessage(debug.LEVEL_INFO, msg, True)
return False
def _processObjectEvent(self, event):
"""Handles all object events destined for scripts.
Arguments:
- e: an at-spi event.
"""
if self._isObsoletedBy(event):
return
eType = event.type
if eType.startswith("object:children-changed:remove") \
and event.source == AXUtilities.get_desktop():
script_manager.getManager().reclaimScripts()
return
if AXObject.is_dead(event.source) or AXUtilities.is_defunct(event.source):
tokens = ["EVENT MANAGER: Ignoring defunct object:", event.source]
debug.printTokens(debug.LEVEL_INFO, tokens, True)
if eType.startswith("window:deactivate") or eType.startswith("window:destroy") \
and focus_manager.getManager().get_active_window() == event.source:
focus_manager.getManager().clear_state("Active window is dead or defunct")
script_manager.getManager().setActiveScript(
None, "Active window is dead or defunct")
return
if eType.startswith("window:") and not eType.endswith("create"):
script_manager.getManager().reclaimScripts()
elif eType.startswith("object:state-changed:active") \
and AXUtilities.is_frame(event.source):
script_manager.getManager().reclaimScripts()
if AXUtilities.is_iconified(event.source):
tokens = ["EVENT MANAGER: Ignoring iconified object:", event.source]
debug.printTokens(debug.LEVEL_INFO, tokens, True)
return
if self._inFlood():
if not self._processDuringFlood(event):
msg = 'EVENT MANAGER: Not processing this event due to flood.'
debug.printMessage(debug.LEVEL_INFO, msg, True)
return
if self._prioritizeDuringFlood(event):
msg = 'EVENT MANAGER: Pruning event queue due to flood.'
debug.printMessage(debug.LEVEL_INFO, msg, True)
self._pruneEventsDuringFlood()
debug.printObjectEvent(debug.LEVEL_INFO, event, timestamp=True)
if not debug.eventDebugFilter or debug.eventDebugFilter.match(eType) \
and not eType.startswith("mouse:"):
indent = " " * 32
debug.printDetails(debug.LEVEL_INFO, indent, event.source)
if isinstance(event.any_data, Atspi.Accessible):
debug.printMessage(debug.LEVEL_INFO, f"{indent}ANY DATA:")
debug.printDetails(debug.LEVEL_INFO, indent, event.any_data, includeApp=False)
script = self._getScriptForEvent(event)
if not script:
msg = "ERROR: Could not get script for event"
debug.printMessage(debug.LEVEL_INFO, msg, True)
return
setNewActiveScript, reason = self._isActivatableEvent(event, script)
msg = f'EVENT MANAGER: Change active script: {setNewActiveScript} ({reason})'
debug.printMessage(debug.LEVEL_INFO, msg, True)
if setNewActiveScript:
try:
script_manager.getManager().setActiveScript(script, reason)
except Exception as error:
tokens = ["EVENT MANAGER: Exception setting active script for",
event.source, ":", error]
debug.printTokens(debug.LEVEL_INFO, tokens, True)
return
activeScript = script_manager.getManager().getActiveScript()
if not self._shouldProcessEvent(event, script, activeScript):
return
try:
script.processObjectEvent(event)
except Exception as error:
msg = f"EVENT MANAGER: Exception processing {event.type}: {error}"
debug.printMessage(debug.LEVEL_INFO, msg, True)
debug.printException(debug.LEVEL_INFO)
if debug.LEVEL_INFO >= debug.debugLevel and script:
attributes = script.getTransferableAttributes()
for key, value in attributes.items():
msg = f"EVENT MANAGER: {key}: {value}"
debug.printMessage(debug.LEVEL_INFO, msg, True)
def _processKeyboardEvent(self, device, pressed, keycode, keysym, state, text):
keyboardEvent = input_event.KeyboardEvent(pressed, keycode, keysym, state, text)
if not keyboardEvent.is_duplicate:
debug.printMessage(debug.LEVEL_INFO, f"\n{keyboardEvent}")
# If pressing insert, then temporarily remove grab to allow toggling
# with a double press
script = script_manager.getManager().getActiveScript()
if pressed and script is not None:
if keyboardEvent.keyval_name in orca_state.grabbedModifiers:
device.remove_key_grab(orca_state.grabbedModifiers[keyboardEvent.keyval_name])
del orca_state.grabbedModifiers[keyboardEvent.keyval_name]
self.bypassedKey = keyboardEvent.keyval_name
elif self.bypassedKey is not None:
# This is a second key press. Re-enable the grab
script.refreshModifierKeyGrab(self.bypassedKey)
self.bypassedKey = None
keyboardEvent.process()
# TODO - JD: Figure out exactly why this is here.
orca_modifier_manager.getManager().update_key_map(keyboardEvent)
def process_braille_event(self, event):
"""Processes this BrailleEvent."""
braille_event = input_event.BrailleEvent(event)
orca_state.lastInputEvent = braille_event
return braille_event.process()
_manager = EventManager()
def getManager():
return _manager