orca r4200 - in branches/phase2: . src/orca
- From: wwalker svn gnome org
- To: svn-commits-list gnome org
- Subject: orca r4200 - in branches/phase2: . src/orca
- Date: Fri, 12 Sep 2008 22:42:00 +0000 (UTC)
Author: wwalker
Date: Fri Sep 12 22:42:00 2008
New Revision: 4200
URL: http://svn.gnome.org/viewvc/orca?rev=4200&view=rev
Log:
Work on input bindings and get the script plumbing kind of working.
Added:
branches/phase2/src/orca/input_bindings.py
Removed:
branches/phase2/src/orca/key_bindings.py
Modified:
branches/phase2/ (props changed)
branches/phase2/src/orca/Makefile.am
branches/phase2/src/orca/input_event.py
branches/phase2/src/orca/script.py
branches/phase2/src/orca/script_manager.py
Modified: branches/phase2/src/orca/Makefile.am
==============================================================================
--- branches/phase2/src/orca/Makefile.am (original)
+++ branches/phase2/src/orca/Makefile.am Fri Sep 12 22:42:00 2008
@@ -10,8 +10,8 @@
braille_monitor.py \
braille.py \
default.py \
+ input_bindings.py \
input_event.py \
- key_bindings.py \
orca_i18n.py \
orca.py \
platform.py \
Added: branches/phase2/src/orca/input_bindings.py
==============================================================================
--- (empty file)
+++ branches/phase2/src/orca/input_bindings.py Fri Sep 12 22:42:00 2008
@@ -0,0 +1,576 @@
+# Orca
+#
+# Copyright 2005-2008 Sun Microsystems Inc.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Library General Public
+# License as published by the Free Software Foundation; either
+# version 2 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
+# Library General Public License for more details.
+#
+# You should have received a copy of the GNU Library 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 defining braille and key bindings and matching
+them to input events."""
+
+__id__ = "$Id: keybindings.py 3971 2008-06-11 00:54:46Z joanied $"
+__version__ = "$Revision: 3971 $"
+__date__ = "$Date: 2008-06-10 20:54:46 -0400 (Tue, 10 Jun 2008) $"
+__copyright__ = "Copyright (c) 2005-2008 Sun Microsystems Inc."
+__license__ = "LGPL"
+
+import logging
+log = logging.getLogger('orca.input_bindings')
+
+try:
+ # This can fail due to gtk not being available. We want to
+ # be able to recover from that if possible. The main driver
+ # for this is to allow "orca --text-setup" to work even if
+ # the desktop is not running.
+ #
+ import gtk
+except ImportError:
+ pass
+
+import pyatspi
+
+########################################################################
+# #
+# METHODS FOR DEALING WITH CONVERTING X KEYSYMS TO KEYCODES #
+# #
+########################################################################
+
+_keysymsCache = {}
+_keycodeCache = {}
+
+def getAllKeysyms(keysym):
+ """Given a keysym, find all other keysyms associated with the key
+ that is mapped to the given keysym. This allows us, for example,
+ to determine that the key bound to KP_Insert is also bound to KP_0.
+ """
+ if keysym not in _keysymsCache:
+ # The keysym itself is always part of the list.
+ #
+ _keysymsCache[keysym] = [keysym]
+
+ # Find the numerical value of the keysym
+ #
+ keyval = gtk.gdk.keyval_from_name(keysym)
+
+ if keyval != 0:
+ # Find the keycodes for the keysym. Since a keysym
+ # can be associated with more than one key, we'll shoot
+ # for the keysym that's in group 0, regardless of shift
+ # level (each entry is of the form [keycode, group,
+ # level]).
+ #
+ keymap = gtk.gdk.keymap_get_default()
+ entries = keymap.get_entries_for_keyval(keyval)
+ keycode = 0
+ if entries:
+ for entry in entries:
+ if entry[1] == 0: # group = 0
+ keycode = entry[0]
+ break
+
+ # Find the keysyms bound to the keycode. These are what
+ # we are looking for.
+ #
+ if keycode != 0:
+ entries = keymap.get_entries_for_keycode(keycode)
+ if entries:
+ for entry in entries:
+ keyval = entry[0]
+ name = gtk.gdk.keyval_name(keyval)
+ if name and (name != keysym):
+ _keysymsCache[keysym].append(name)
+
+ return _keysymsCache[keysym]
+
+def getKeycode(keysym):
+ """Converts an XKeysym string (e.g., 'KP_Enter') to a keycode that
+ should match the event.hw_code for key events.
+
+ This whole situation is caused by the fact that Solaris chooses
+ to give us different keycodes for the same key, and the keypad
+ is the primary place where this happens: if NumLock is not on,
+ there is no telling the difference between keypad keys and the
+ other navigation keys (e.g., arrows, page up/down, etc.). One,
+ for example, would expect to get KP_End for the '1' key on the
+ keypad if NumLock were not on. Instead, we get 'End' and the
+ keycode for it matches the keycode for the other 'End' key. Odd.
+ If NumLock is on, we at least get KP_* keys.
+
+ So...when setting up keybindings, we say we're interested in
+ KeySyms, but those keysyms are carefully chosen so as to result
+ in a keycode that matches the actual key on the keyboard. This
+ is why we use KP_1 instead of KP_End and so on in our keybindings.
+
+ Arguments:
+ - keysym: a string that is a valid representation of an XKeysym.
+
+ Returns an integer representing a key code that should match the
+ event.hw_code for key events.
+ """
+ if not keysym:
+ return 0
+
+ if keysym not in _keycodeCache:
+ keymap = gtk.gdk.keymap_get_default()
+
+ # Find the numerical value of the keysym
+ #
+ keyval = gtk.gdk.keyval_from_name(keysym)
+ if keyval == 0:
+ return 0
+
+ # Now find the keycodes for the keysym. Since a keysym can
+ # be associated with more than one key, we'll shoot for the
+ # keysym that's in group 0, regardless of shift level (each
+ # entry is of the form [keycode, group, level]).
+ #
+ _keycodeCache[keysym] = 0
+ entries = keymap.get_entries_for_keyval(keyval)
+ if entries:
+ for entry in entries:
+ if entry[1] == 0: # group = 0
+ _keycodeCache[keysym] = entry[0]
+ break
+ if _keycodeCache[keysym] == 0:
+ _keycodeCache[keysym] = entries[0][0]
+
+ return _keycodeCache[keysym]
+
+########################################################################
+# #
+# INPUT BINDING CLASSES #
+# #
+########################################################################
+
+class Handler:
+ """Binds a function to a description."""
+ def __init__(self, function, description, learnModeEnabled=True):
+ """Creates a new Handler instance. All bindings
+ (e.g., key bindings and braille bindings) will be handled
+ by an instance of an Handler.
+
+ 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.
+ """
+ self.function = function
+ self.description = description
+ self.learnModeEnabled = learnModeEnabled
+
+ def __str__(self):
+ return "%s (learnModeEnabled=%s)" \
+ % (self.description, self.learnModeEnabled)
+
+ def __eq__(self, other):
+ """Compares one input handler to another."""
+ return (self.function == other.function)
+
+ def processInputEvent(self, script, inputEvent, modifiers):
+ """Processes an input event. If settings.learnModeEnabled is True,
+ this will merely report the description of the input event to braille
+ and speech. If settings.learnModeEnabled is False, this will call the
+ function bound to this Handler instance, passing the
+ inputEvent as the sole argument to the function.
+
+ 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 Handler instance.
+ - modifiers: any modifiers associated with the event
+ """
+ log.debug("process %s for %s" % (inputEvent, script))
+ try:
+ consumed = self.function(script, inputEvent, modifiers)
+ except:
+ log.exception("while attempting to process input event")
+ consumed = False
+ return consumed
+
+class Binding:
+ """A single input event binding, consisting of a command, a keyboard
+ modifier mask, and the Handler.
+ """
+
+ # We really do want this many arguments.
+ #
+ # pylint: disable-msg=R0913
+
+ def __init__(self,
+ command,
+ modifierMask,
+ modifiers,
+ handler,
+ clickCount = 1):
+ """Creates a new input binding.
+
+ Arguments:
+ - command: a command that uniquely identifies this. For
+ braille is can be something like KEY_CMD_ROUTE,
+ KEY_CMD_FWINLT, etc. For keyboard, we want a
+ keysymString - this is typically a string from
+ /usr/include/X11/keysymdef.h with the preceding
+ 'XK_' removed (e.g., XK_KP_Enter becomes the string
+ 'KP_Enter').
+ - modifierMask: bit mask where a set bit tells us what modifiers
+ we care about (see pyatspi.MODIFIER_*)
+ - modifiers: the state the modifiers we care about must be in for
+ this key binding to match an input event (see also
+ pyatspi.MODIFIER_*)
+ - handler: the Handler for this key binding
+ - clickCount: how many times the command should be pressed in a row
+ """
+ self.command = command
+ self.modifierMask = modifierMask
+ self.modifiers = modifiers
+ self.handler = handler
+ self.clickCount = clickCount
+ self._commandCode = None
+
+ def __str__(self):
+ return "[%x %x %s %d %s]" % \
+ (self.modifierMask,
+ self.modifiers,
+ self.command,
+ self.clickCount,
+ self.handler.description)
+
+ def _getCommandCode(self):
+ """Converts the command into a command code. For example,
+ a keysym into a keycode. By default, the command code is
+ the same as the command.
+ """
+ if not self._commandCode:
+ self._commandCode = self.command
+ return self._commandCode
+
+ def matches(self, commandCode, modifiers):
+ """Returns true if this binding matches the given commandCode and
+ modifier state.
+ """
+ if commandCode == self._getCommandCode():
+ result = modifiers & self.modifierMask
+ return result == self.modifiers
+ else:
+ return False
+
+class Bindings(list):
+ """Structure that maintains a set of Binding instances.
+ """
+ def __init__(self):
+ list.__init__(self)
+
+ def __str__(self):
+ result = "[\n"
+ for binding in self:
+ result += " %s\n" % binding
+ result += "]"
+ return result
+
+ def removeByHandler(self, handler):
+ """Removes the binding associated with the handler.
+ """
+ i = len(self)
+ while i > 0:
+ if self[i - 1].handler == handler:
+ del self[i - 1]
+ i = i - 1
+
+ def hasBinding(self, binding, typeOfSearch="strict"):
+ """Return True if keyBinding is already in self.keyBindings.
+
+ The typeOfSearch can be:
+ "strict": matches description, modifiers, key, and
+ click count
+ "description": matches only description.
+ "command": matches the modifiers, command, and modifier mask,
+ and click count
+ "commandNoMask": matches the modifiers, command, and click count
+ """
+ hasIt = False
+
+ for myBinding in self:
+ if typeOfSearch == "strict":
+ if (binding.handler.description \
+ == myBinding.handler.description) \
+ and (binding.command \
+ == myBinding.command) \
+ and (binding.modifierMask \
+ == myBinding.modifierMask) \
+ and (binding.modifiers \
+ == myBinding.modifiers) \
+ and (binding.clickCount \
+ == myBinding.clickCount):
+ hasIt = True
+ elif typeOfSearch == "description":
+ if binding.handler.description \
+ == myBinding.handler.description:
+ hasIt = True
+ elif typeOfSearch == "command":
+ if (binding.command \
+ == myBinding.command) \
+ and (binding.modifierMask \
+ == myBinding.modifierMask) \
+ and (binding.modifiers \
+ == myBinding.modifiers) \
+ and (binding.clickCount \
+ == myBinding.clickCount):
+ hasIt = True
+ elif typeOfSearch == "commandNoMask":
+ if (binding.command \
+ == myBinding.command) \
+ and (binding.modifiers \
+ == myBinding.modifiers) \
+ and (binding.clickCount \
+ == myBinding.clickCount):
+ hasIt = True
+
+ return hasIt
+
+ def getHandler(self, commandCode, modifiers, clickCount):
+ """Returns the input handler of the key binding that matches the
+ given inputEvent and clickCount, or None if no match exists.
+ """
+ candidates = []
+ for binding in self:
+ if binding.matches(commandCode, modifiers):
+ if binding.modifierMask == modifiers \
+ and binding.clickCount == clickCount:
+ return binding.handler
+ candidates.append(binding)
+
+ # If we're still here, we don't have an exact match. Prefer
+ # the one whose click count is closest to, but does not exceed,
+ # the actual click count.
+ #
+ candidates.sort(cmp=lambda x, y: x.clickCount - y.clickCount,
+ reverse=True)
+ for candidate in candidates:
+ if candidate.clickCount <= clickCount:
+ return candidate.handler
+
+ return None
+
+ def consumeInputEvent(self,
+ script,
+ inputEvent,
+ commandCode,
+ modifiers,
+ process=True):
+ """Attempts to consume the given input event.
+ """
+ consumed = False
+ handler = self.getHandler(commandCode,
+ modifiers,
+ script.getClickCount())
+ if handler:
+ consumed = True
+ if process:
+ try:
+ handler.processInputEvent(script,
+ inputEvent,
+ modifiers)
+ except:
+ log.exception("while processing input event")
+
+ return consumed
+
+########################################################################
+# #
+# KEY BINDING CLASSES #
+# #
+########################################################################
+
+class KeyBinding(Binding):
+ """A single binding, consisting of a keycode, a modifier mask,
+ and the Handler.
+ """
+
+ # We really do want this many arguments.
+ #
+ # pylint: disable-msg=R0913
+
+ def __init__(self,
+ command,
+ modifierMask,
+ modifiers,
+ handler,
+ clickCount = 1):
+ """Creates a new key binding.
+
+ Arguments:
+ - command: the keysymString - this is typically a string
+ from /usr/include/X11/keysymdef.h with the preceding 'XK_'
+ removed (e.g., XK_KP_Enter becomes the string 'KP_Enter').
+ - modifierMask: bit mask where a set bit tells us what modifiers
+ we care about (see pyatspi.MODIFIER_*)
+ - modifiers: the state the modifiers we care about must be in for
+ this key binding to match an input event (see also
+ pyatspi.MODIFIER_*)
+ - handler: the Handler for this key binding
+ - clickCount: how many times the key should be pressed in a row
+ """
+ Binding.__init__(self,
+ command,
+ modifierMask,
+ modifiers,
+ handler,
+ clickCount)
+
+ def _getCommandCode(self):
+ """Converts the command into a command code"""
+ if not self._commandCode:
+ self._commandCode = getKeycode(self.command)
+ return self._commandCode
+
+class KeyBindings(Bindings):
+ """Structure that maintains a set of KeyBinding instances.
+ """
+ def __init__(self):
+ Bindings.__init__(self)
+
+ def consumeInputEvent(self, script, keyboardEvent, modifiers):
+ """Attempts to consume the given keyboard event. If these
+ keybindings have a handler for the given keyboardEvent, it is
+ assumed the event will always be consumed (e.g., we want to
+ consume key presses as well, but not really process them).
+ """
+ return Bindings.consumeInputEvent(
+ self,
+ script,
+ keyboardEvent,
+ keyboardEvent.hw_code,
+ modifiers,
+ keyboardEvent.type == pyatspi.KEY_PRESSED_EVENT)
+
+########################################################################
+# #
+# BRAILLE BINDING CLASSES #
+# #
+########################################################################
+
+class BrailleBinding(Binding):
+ # We really do want this many arguments.
+ #
+ # pylint: disable-msg=R0913
+
+ def __init__(self,
+ command,
+ modifierMask,
+ modifiers,
+ handler,
+ clickCount = 1):
+ """Creates a new braille binding.
+
+ Arguments:
+ - command: A BrlAPI KEY_CMD_* command.
+ - modifierMask: bit mask where a set bit tells us what modifiers
+ we care about (see pyatspi.MODIFIER_*)
+ - modifiers: the state the modifiers we care about must be in for
+ this key binding to match an input event (see also
+ pyatspi.MODIFIER_*)
+ - handler: the Handler for this key binding
+ - clickCount: how many times the key should be pressed in a row
+ """
+ Binding.__init__(self,
+ command,
+ modifierMask,
+ modifiers,
+ handler,
+ clickCount)
+
+class BrailleBindings(Bindings):
+ """Structure that maintains a set of BrailleBinding instances.
+ """
+ def __init__(self):
+ Bindings.__init__(self)
+
+ def consumeInputEvent(self, script, brailleEvent, modifiers):
+ """Attempts to consume the given braille event.
+ """
+ return Bindings.consumeInputEvent(
+ self,
+ script,
+ brailleEvent,
+ brailleEvent["command"],
+ modifiers,
+ True)
+
+if __name__ == "__main__":
+ logging.basicConfig(format="%(name)s %(message)s")
+ log.setLevel(logging.DEBUG)
+
+ print getAllKeysyms("Insert")
+ print getKeycode("Insert")
+
+ print "Keyboard tests:\n"
+ bindings = KeyBindings()
+ handler1 = Handler(None, "Does something")
+ print handler1
+ binding1 = KeyBinding("Insert", 0x1ff, 0, handler1, 2)
+ bindings.append(binding1)
+ handler2 = Handler(None, "Does something else")
+ binding2 = KeyBinding("Return", 0x1ff, 0, handler2, 2)
+ bindings.append(binding2)
+ print "All bindings:"
+ print bindings
+
+ print "Bindings has %s: %s" \
+ % (binding2, bindings.hasBinding(binding2))
+
+ bindings.remove(binding2)
+ print "All bindings after remove:"
+ print bindings
+ print "Bindings has %s: %s" \
+ % (binding2, bindings.hasBinding(binding2))
+
+ bindings.removeByHandler(handler1)
+ print "All bindings after removeByHandler:"
+ print bindings
+
+ print "\n\nBraille tests:\n"
+ import brlapi
+ bindings = BrailleBindings()
+ handler1 = Handler(None, "Does something")
+ print handler1
+ binding1 = BrailleBinding(brlapi.KEY_CMD_ROUTE, 0x1ff, 0, handler1, 2)
+ bindings.append(binding1)
+ handler2 = Handler(None, "Does something else")
+ binding2 = BrailleBinding(brlapi.KEY_CMD_FWINRT, 0x1ff, 0, handler2, 2)
+ bindings.append(binding2)
+ print "All bindings:"
+ print bindings
+
+ print "Bindings has %s: %s" \
+ % (binding2, bindings.hasBinding(binding2))
+
+ bindings.remove(binding2)
+ print "All bindings after remove:"
+ print bindings
+ print "Bindings has %s: %s" \
+ % (binding2, bindings.hasBinding(binding2))
+
+ bindings.removeByHandler(handler1)
+ print "All bindings after removeByHandler:"
+ print bindings
Modified: branches/phase2/src/orca/input_event.py
==============================================================================
--- branches/phase2/src/orca/input_event.py (original)
+++ branches/phase2/src/orca/input_event.py Fri Sep 12 22:42:00 2008
@@ -32,10 +32,108 @@
import logging
log = logging.getLogger('orca.input_event')
-import key_bindings
import pyatspi
import time
+from orca_i18n import _ # for gettext support
+
+# A new modifier to use (set by the press of any key in the
+# orcaModifierKeys list) to represent the Orca modifier.
+#
+MODIFIER_ORCA = 8
+
+# The different modifiers/modifier masks associated with key bindings
+#
+NO_MODIFIER_MASK = 0
+ALT_MODIFIER_MASK = 1 << pyatspi.MODIFIER_ALT
+CTRL_MODIFIER_MASK = 1 << pyatspi.MODIFIER_CONTROL
+ORCA_MODIFIER_MASK = 1 << MODIFIER_ORCA
+ORCA_ALT_MODIFIER_MASK = (1 << MODIFIER_ORCA |
+ 1 << pyatspi.MODIFIER_ALT)
+ORCA_CTRL_MODIFIER_MASK = (1 << MODIFIER_ORCA |
+ 1 << pyatspi.MODIFIER_CONTROL)
+ORCA_CTRL_ALT_MODIFIER_MASK = (1 << MODIFIER_ORCA |
+ 1 << pyatspi.MODIFIER_CONTROL |
+ 1 << pyatspi.MODIFIER_ALT)
+ORCA_SHIFT_MODIFIER_MASK = (1 << MODIFIER_ORCA |
+ 1 << pyatspi.MODIFIER_SHIFT)
+SHIFT_MODIFIER_MASK = 1 << pyatspi.MODIFIER_SHIFT
+SHIFT_ALT_MODIFIER_MASK = (1 << pyatspi.MODIFIER_SHIFT |
+ 1 << pyatspi.MODIFIER_ALT)
+COMMAND_MODIFIER_MASK = (1 << pyatspi.MODIFIER_ALT |
+ 1 << pyatspi.MODIFIER_CONTROL |
+ 1 << pyatspi.MODIFIER_META2 |
+ 1 << pyatspi.MODIFIER_META3)
+NON_LOCKING_MODIFIER_MASK = (1 << pyatspi.MODIFIER_SHIFT |
+ 1 << pyatspi.MODIFIER_ALT |
+ 1 << pyatspi.MODIFIER_CONTROL |
+ 1 << pyatspi.MODIFIER_META2 |
+ 1 << pyatspi.MODIFIER_META3 |
+ 1 << MODIFIER_ORCA)
+ALL_BUT_NUMLOCK_MODIFIER_MASK = (1 << MODIFIER_ORCA |
+ 1 << pyatspi.MODIFIER_SHIFT |
+ 1 << pyatspi.MODIFIER_SHIFTLOCK |
+ 1 << pyatspi.MODIFIER_CONTROL |
+ 1 << pyatspi.MODIFIER_ALT |
+ 1 << pyatspi.MODIFIER_META2 |
+ 1 << pyatspi.MODIFIER_META3)
+
+# The 2nd parameter used when creating a key binding refers to the
+# modifiers "we care about." We typically care about all of the
+# modifiers which are not locking keys because we want to avoid
+# conflicts with other commands, such as Orca's command for moving
+# among headings (H) and the Help menu (Alt+H).
+#
+DEFAULT_MODIFIER_MASK = NON_LOCKING_MODIFIER_MASK
+
+def getModifierNames(mods):
+ """Gets the modifier names of a numeric modifier mask as a human
+ consumable string that can be prefixed ahead of a character name.
+ """
+ text = ""
+ if mods & ORCA_MODIFIER_MASK:
+ text += _("Orca") + "+"
+ #if mods & (1 << pyatspi.MODIFIER_NUMLOCK):
+ # text += _("Num_Lock") + "+"
+ if mods & 128:
+ # Translators: this is presented in a GUI to represent the
+ # "right alt" modifier.
+ #
+ text += _("Alt_R") + "+"
+ if mods & (1 << pyatspi.MODIFIER_META3):
+ # Translators: this is presented in a GUI to represent the
+ # "super" modifier.
+ #
+ text += _("Super") + "+"
+ if mods & (1 << pyatspi.MODIFIER_META2):
+ # Translators: this is presented in a GUI to represent the
+ # "meta 2" modifier.
+ #
+ text += _("Meta2") + "+"
+ #if mods & (1 << pyatspi.MODIFIER_META):
+ # text += _("Meta") + "+"
+ if mods & ALT_MODIFIER_MASK:
+ # Translators: this is presented in a GUI to represent the
+ # "left alt" modifier.
+ #
+ text += _("Alt_L") + "+"
+ if mods & CTRL_MODIFIER_MASK:
+ # Translators: this is presented in a GUI to represent the
+ # "control" modifier.
+ #
+ text += _("Ctrl") + "+"
+ if mods & (1 << pyatspi.MODIFIER_SHIFTLOCK):
+ # Translators: this is presented in a GUI to represent the
+ # "caps lock" modifier.
+ #
+ text += _("Caps_Lock") + "+"
+ if mods & SHIFT_MODIFIER_MASK:
+ # Translators: this is presented in a GUI to represent the
+ # "shift " modifier.
+ #
+ text += _("Shift") + "+"
+ return text
+
class InputEvent:
"""Super class for all input events."""
def __init__(self):
@@ -62,7 +160,7 @@
# mapping ASCII control characters to UTF-8.]]]
#
event_string = event.event_string
- if (event.modifiers & key_bindings.CTRL_MODIFIER_MASK) \
+ if (event.modifiers & CTRL_MODIFIER_MASK) \
and (not event.is_text) and (len(event_string) == 1):
value = ord(event.event_string[0])
if value < 32:
@@ -70,8 +168,7 @@
# Filter out the NUMLOCK modifier -- it always causes problems.
#
- event.modifiers = event.modifiers \
- & key_bindings.ALL_BUT_NUMLOCK_MODIFIER_MASK
+ event.modifiers = event.modifiers & ALL_BUT_NUMLOCK_MODIFIER_MASK
self.type = event.type
self.hw_code = event.hw_code
@@ -124,62 +221,8 @@
% (self.button, self.x, self.y)
return s
-class InputEventHandler:
- """Binds a function to a description."""
- def __init__(self, function, description, learnModeEnabled=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.
- """
- self.function = function
- self.description = description
- self.learnModeEnabled = learnModeEnabled
-
- def __str__(self):
- return "%s (learnModeEnabled=%s)" \
- % (self.description, self.learnModeEnabled)
-
- def __eq__(self, other):
- """Compares one input handler to another."""
- return (self.function == other.function)
-
- def processInputEvent(self, script, inputEvent):
- """Processes an input event. If settings.learnModeEnabled is True,
- this will merely report the description of the input event to braille
- and speech. If settings.learnModeEnabled is False, this will call the
- function bound to this InputEventHandler instance, passing the
- inputEvent as the sole argument to the function.
-
- 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.
- """
- log.debug("process %s for %s" % (inputEvent, script))
- try:
- consumed = self.function(script, inputEvent)
- except:
- log.exception("while attempting to process input event")
- consumed = False
- return consumed
-
if __name__ == "__main__":
logging.basicConfig(format="%(name)s %(message)s")
log.setLevel(logging.DEBUG)
- handler = InputEventHandler(None, "Does something")
- print handler
+ print getModifierNames(0x1ff)
Modified: branches/phase2/src/orca/script.py
==============================================================================
--- branches/phase2/src/orca/script.py (original)
+++ branches/phase2/src/orca/script.py Fri Sep 12 22:42:00 2008
@@ -40,7 +40,7 @@
import logging
log = logging.getLogger('orca.script')
-import key_bindings
+import braille
# We want several methods here to be abstract methods so subclasses
# can refer to self.
@@ -58,30 +58,30 @@
Arguments:
- app: the Python Accessible application to create a script for
"""
- self.app = app
+ self._app = app
if app:
- self.name = self.app.name
+ self._name = self._app.name
else:
- self.name = "default"
+ self._name = "default"
- self.name += " (module=" + self.__module__ + ")"
+ self._name += " (module=" + self.__module__ + ")"
- self.listeners = self._getListeners()
+ self._isActive = False
- # By default, handle events for non-active applications.
- #
- self.presentIfInactive = True
- self.locusOfFocus = {}
+ self.presentIfInactive = False
+ self.listeners = self._createListeners()
+ self.keyBindings = self._createKeyBindings()
+ self.brailleBindings = self._createBrailleBindings()
log.debug("NEW SCRIPT: %s" % self)
def __str__(self):
"""Returns a human readable representation of the script.
"""
- return self.name
+ return self._name
- def _getListeners(self):
+ def _createListeners(self):
"""Sets up the AT-SPI event listeners for this script.
Returns a dictionary where the keys are AT-SPI event names
@@ -89,20 +89,19 @@
"""
return {}
- def _getKeyBindings(self):
+ def _createKeyBindings(self):
"""Defines the key bindings for this script.
- Returns an instance of keybindings.KeyBindings.
+ Returns an instance of input_bindings.KeyBindings.
"""
- return key_bindings.KeyBindings()
+ return []
- def _getBrailleBindings(self):
+ def _createBrailleBindings(self):
"""Defines the braille bindings for this script.
- Returns a dictionary where the keys are BrlTTY commands and the
- values are InputEventHandler instances.
+ Returns an instance of input_bindings.BrailleBindings
"""
- return {}
+ return []
def _getPronunciations(self):
"""Defines the application specific pronunciations for this script.
@@ -110,14 +109,50 @@
Returns a dictionary where the keys are the actual text strings and
the values are the replacement strings that are spoken instead.
"""
-
return {}
+ def consumesKeyboardEvent(self, keyboardEvent):
+ """Called when a key is pressed on the keyboard. If we care
+ about it, our processKeyboardEvent method will get called a
+ bit later.
+
+ Arguments:
+ - keyboardEvent: an instance of input_event.KeyboardEvent
+
+ Returns True if the event is of interest.
+ """
+ return False
+
+ def processKeyboardEvent(self, keyboardEvent):
+ """Called whenever a key is pressed on the Braille display
+ and we have an interest in it.
+ """
+ log.debug("processKeyboardEvent %s" % keyboardEvent)
+
+ def consumesBrailleEvent(self, brailleEvent):
+ """Called when a key is pressed on the braille display. If we
+ care about it, our processBrailleEvent method will get called a
+ bit later.
+
+ Arguments:
+ - brailleEvent: an instance of input_event.KeyboardEvent
+
+ Returns True if the event is of interest.
+ """
+ return False
+
+ def processBrailleEvent(self, brailleEvent):
+ """Called whenever a key is pressed on the Braille display
+ and we have an interest in it.
+
+ Arguments:
+ - brailleEvent: an instance of input_event.BrailleEvent
+ """
+ log.debug("processBrailleEvent %s" % brailleEvent)
+
def processObjectEvent(self, event):
"""Processes all AT-SPI object events of interest to this
- script. The interest in events is specified via the
- 'listeners' field that was defined during the construction of
- this script.
+ script.
In general, the primary purpose of handling object events is to
keep track of changes to the locus of focus.
@@ -130,7 +165,8 @@
"""
# Check to see if we really want to process this event.
#
- if not self.presentIfInactive:
+ if not (self._isActive or self.presentIfInactive):
+ log.debug("not processing event because script is not active")
return
# This calls the first listener it finds whose key *begins with* or is
@@ -148,8 +184,34 @@
def activate(self):
"""Called when this script is activated."""
- pass
+ log.debug("%s has been activated" % self)
+
+ self._isActive = True
+
+ # Tell BrlTTY which commands we care about.
+ # [[[TODO: WDW - do this.]]]
+ #
+ braille.setupKeyRanges([])
def deactivate(self):
"""Called when this script is deactivated."""
+ log.debug("%s has been deactivated" % self)
+
+ self._isActive = False
+
+if __name__ == "__main__":
+ logging.basicConfig(format="%(name)s %(message)s")
+ log.setLevel(logging.DEBUG)
+
+ script = Script(None)
+ print script
+
+ script.processObjectEvent(None)
+
+ script.activate()
+ try:
+ script.processObjectEvent(None)
+ except:
+ # Expected since no event was passed in
+ #
pass
Modified: branches/phase2/src/orca/script_manager.py
==============================================================================
--- branches/phase2/src/orca/script_manager.py (original)
+++ branches/phase2/src/orca/script_manager.py Fri Sep 12 22:42:00 2008
@@ -58,9 +58,19 @@
#
ignoredEventsList = ['object:bounds-changed']
+# Assists with dealing with CORBA COMM_FAILURES. A failure doesn't
+# always mean an object disappeared - there just might be a network
+# glitch. So, on COMM_FAILURES, we might retry a few times before
+# giving up on an object. This might need to be overridden by the
+# script.
+#
+commFailureWaitTime = 0.1
+commFailureAttemptLimit = 5
+
import logging
log = logging.getLogger('orca.script_manager')
+import gobject
import Queue
import braille
@@ -81,9 +91,11 @@
self._knownScripts = {}
self._activeScript = None
self._defaultScript = None
- self._eventQueue = Queue.Queue(0)
self._listenerCounts = {}
+ self._gidleId = 0
+ self._eventQueue = Queue.Queue(0)
+
self._registerEventListener("window:activate")
self._registerEventListener("window:deactivate")
self._registerEventListener("object:children-changed:remove")
@@ -343,9 +355,9 @@
del app
del script
except:
- log.exception("while reclaiming scripts")
+ log.exception("exception while reclaiming scripts:")
- def getScript(self, app):
+ def _getScript(self, app):
"""Get a script for an app (and make it if necessary). This is used
instead of a simple call to Script's constructor.
@@ -377,7 +389,14 @@
Arguments:
- newScript: the new script to be made active.
"""
+ if self._activeScript:
+ self._activeScript.deactivate()
+
self._activeScript = newScript
+
+ if self._activeScript:
+ self._activeScript.activate()
+
log.debug("ACTIVE SCRIPT: %s (reason=%s)"
% (self._activeScript, reason))
@@ -394,11 +413,10 @@
Arguments:
- keyboardEvent: an instance of input_event.KeyboardEvent
"""
- if self._activeScript:
- try:
- self._activeScript.processKeyboardEvent(keyboardEvent)
- except:
- log.exception("while processing keyboard event")
+ try:
+ self._activeScript.processKeyboardEvent(keyboardEvent)
+ except:
+ log.exception("exception while processing keyboard event:")
def _dispatchBrailleEvent(self, brailleEvent):
"""Called whenever we get a braille input event.
@@ -406,11 +424,10 @@
Arguments:
- brailleEvent: an instance of input_event.BrailleEvent
"""
- if self._activeScript:
- try:
- self._activeScript.processBrailleEvent(brailleEvent)
- except:
- log.exception("while processing braille event")
+ try:
+ self._activeScript.processBrailleEvent(brailleEvent)
+ except:
+ log.exception("exception while processing braille event:")
def _dispatchObjectEvent(self, event):
"""Handles all events destined for scripts.
@@ -453,6 +470,134 @@
self._enqueueEvent(brailleEvent)
return consume
+ def _processObjectEvent(self, event):
+ """Handles all AT-SPI events.
+
+ Arguments:
+ - event: an AT-SPI event
+ """
+ # Reclaim (delete) any scripts when desktop children go away.
+ # The idea here is that a desktop child is an app. We also
+ # generally do not like object:children-changed:remove events,
+ # either.
+ #
+ if event.type.startswith("object:children-changed:remove") \
+ and (event.source == self.registry.getDesktop(0)):
+ self._reclaimScripts()
+
+ # We don't want to touch a defunct object. It's useless and it
+ # can cause hangs.
+ #
+ try:
+ if event.source.getState().contains(pyatspi.STATE_DEFUNCT):
+ log.debug("IGNORING DEFUNCT OBJECT")
+ return
+ except:
+ log.exception("exception while checking object state for defunct:")
+ return
+
+ try:
+ # If we've received a mouse event, then don't try to get
+ # event.source.getApplication() because the top most parent
+ # is of role unknown, which will cause an ERROR message to be
+ # displayed. See Orca bug #409731 for more details.
+ #
+ if not event.type.startswith("mouse:"):
+ s = self._getScript(event.host_application \
+ or event.source.getApplication())
+ else:
+ s = self._activeScript
+ except:
+ s = None
+ log.exception("exception while trying to find script:")
+ return
+
+ if not s:
+ return
+
+ # We can sometimes get COMM_FAILURES even if the object has not
+ # gone away. This happens a lot with the Java access bridge.
+ # So...we will try a few times before giving up.
+ #
+ retryCount = 0
+ while retryCount <= commFailureAttemptLimit:
+ try:
+ state = event.source.getState()
+ if not state.contains(pyatspi.STATE_ICONIFIED):
+ eType = event.type
+ setNewActiveScript = eType == "window:activate"
+
+ reason = None
+ if not reason and setNewActiveScript:
+ reason = "window:activate event"
+
+ # [[[TODO: WDW - HACK we look for frame that get
+ # focus: as a means to detect active scripts
+ # because yelp does this. Actually, yelp is a bit
+ # odd in that it calls itself 'yelp' then changes
+ # its application name and id to the Gecko toolkit
+ # in use, and then issues a focus: event on the
+ # main window, which is a frame.]]]
+ #
+ setNewActiveScript = setNewActiveScript \
+ or (eType.startswith("focus") \
+ and (event.source.getRole() == pyatspi.ROLE_FRAME))
+
+ if not reason and setNewActiveScript:
+ reason = "frame received focus"
+
+ # Added in a further check. We look for modal panels
+ # that are now showing (such as gnome-screensaver-dialog).
+ # See bug #530368 for more details.
+ #
+ setNewActiveScript = setNewActiveScript \
+ or (eType.startswith("object:state-changed:showing")
+ and (event.source.getRole() == pyatspi.ROLE_PANEL)
+ and state.contains(pyatspi.STATE_MODAL))
+
+ if not reason and setNewActiveScript:
+ reason = "modal panel is showing"
+
+ # Or, we might just be getting a focus event. In this
+ # case, assume the window has focus and we missed an
+ # event for it somehow.
+ #
+ if not setNewActiveScript:
+ if eType.startswith("focus") \
+ or (eType.startswith("object:state-changed:focused")\
+ and event.detail1):
+ setNewActiveScript = \
+ orca_state.activeScript \
+ and event.host_application \
+ and (orca_state.activeScript.app \
+ != event.host_application)
+
+ if not reason and setNewActiveScript:
+ reason = "object received focus"
+
+ if setNewActiveScript:
+ self._setActiveScript(
+ self._getScript(event.host_application \
+ or event.source.getApplication()),
+ reason)
+
+ s.processObjectEvent(event)
+
+ if retryCount:
+ log.warning(" SUCCEEDED AFTER %d TRIES" % retryCount)
+ break
+ except LookupError:
+ log.exception("exception while processing %s" % event.type)
+ retryCount += 1
+ if retryCount <= commFailureAttemptLimit:
+ log.warning(" TRYING AGAIN (%d)" % retryCount)
+ time.sleep(s.commFailureWaitTime)
+ else:
+ log.warning(" GIVING UP AFTER %d TRIES" % (retryCount - 1))
+ except:
+ log.exception("exception while processing %s" % event.type)
+ break
+
def _enqueueEvent(self, e):
"""Handles all events destined for scripts.
@@ -512,6 +657,8 @@
self._eventQueue.put(event)
if processSynchronously:
self._dequeueEvent()
+ else:
+ self._gidleId = gobject.idle_add(self._dequeueEvent)
def _dequeueEvent(self):
"""Handles all events destined for scripts. Called by the GTK
@@ -522,17 +669,16 @@
try:
event = self._eventQueue.get_nowait()
log.debug("DEQUEUED %s <----------" % str(event).replace("\n"," "))
- log.debug("\nvvvvv PROCESS %s vvvvv" % event)
+ log.debug("\nvvvvv PROCESS %s vvvvv" % str(event).replace("\n"," "))
if isinstance(event, input_event.KeyboardEvent):
self._dispatchKeyboardEvent(event)
elif isinstance(event, input_event.BrailleEvent):
self._dispatchBrailleEvent(event)
else:
- self._dispatchObjectEvent(event)
+ self._processObjectEvent(event)
log.debug("\n^^^^^ PROCESS %s ^^^^^" % str(event).replace("\n"," "))
except Queue.Empty:
- log.exception("event queue is empty!")
- rerun = False # destroy and don't call again
+ rerun = False
except:
log.exception("while processing event")
@@ -544,4 +690,4 @@
import utils
manager = ScriptManager()
- print manager.getScript(utils.getKnownApplications()[0])
+ print manager._getScript(utils.getKnownApplications()[0])
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]