1# Orca
2#
3# Copyright 2005-2008 Sun Microsystems Inc.
4# Copyright 2011-2016 Igalia, S.L.
5#
6# This library is free software; you can redistribute it and/or
7# modify it under the terms of the GNU Lesser General Public
8# License as published by the Free Software Foundation; either
9# version 2.1 of the License, or (at your option) any later version.
10#
11# This library is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14# Lesser General Public License for more details.
15#
16# You should have received a copy of the GNU Lesser General Public
17# License along with this library; if not, write to the
18# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
19# Boston MA  02110-1301 USA.
20
21"""Provides support for handling input events."""
22
23__id__        = "$Id$"
24__version__   = "$Revision$"
25__date__      = "$Date$"
26__copyright__ = "Copyright (c) 2005-2008 Sun Microsystems Inc." \
27                "Copyright (c) 2011-2016 Igalia, S.L."
28__license__   = "LGPL"
29
30import math
31import pyatspi
32import time
33from gi.repository import Gdk
34from gi.repository import GLib
35
36from . import debug
37from . import keybindings
38from . import keynames
39from . import messages
40from . import orca_state
41from . import script_manager
42from . import settings
43
44KEYBOARD_EVENT     = "keyboard"
45BRAILLE_EVENT      = "braille"
46MOUSE_BUTTON_EVENT = "mouse:button"
47
48class InputEvent:
49
50    def __init__(self, eventType):
51        """Creates a new KEYBOARD_EVENT, BRAILLE_EVENT, or MOUSE_BUTTON_EVENT."""
52
53        self.type = eventType
54        self.time = time.time()
55        self._clickCount = 0
56
57    def getClickCount(self):
58        """Return the count of the number of clicks a user has made."""
59
60        return self._clickCount
61
62    def setClickCount(self):
63        """Updates the count of the number of clicks a user has made."""
64
65        pass
66
67def _getXkbStickyKeysState():
68    from subprocess import check_output, CalledProcessError
69
70    try:
71        output = check_output(['xkbset', 'q'])
72        for line in output.decode('ASCII', errors='ignore').split('\n'):
73            if line.startswith('Sticky-Keys = '):
74                return line.endswith('On')
75    except:
76        pass
77    return False
78
79class KeyboardEvent(InputEvent):
80
81    stickyKeys = _getXkbStickyKeysState()
82
83    duplicateCount = 0
84    orcaModifierPressed = False
85
86    # Whether last press of the Orca modifier was alone
87    lastOrcaModifierAlone = False
88    lastOrcaModifierAloneTime = None
89    # Whether the current press of the Orca modifier is alone
90    currentOrcaModifierAlone = False
91    currentOrcaModifierAloneTime = None
92    # When the second orca press happened
93    secondOrcaModifierTime = None
94    # Sticky modifiers state, to be applied to the next keyboard event
95    orcaStickyModifiers = 0
96
97    TYPE_UNKNOWN          = "unknown"
98    TYPE_PRINTABLE        = "printable"
99    TYPE_MODIFIER         = "modifier"
100    TYPE_LOCKING          = "locking"
101    TYPE_FUNCTION         = "function"
102    TYPE_ACTION           = "action"
103    TYPE_NAVIGATION       = "navigation"
104    TYPE_DIACRITICAL      = "diacritical"
105    TYPE_ALPHABETIC       = "alphabetic"
106    TYPE_NUMERIC          = "numeric"
107    TYPE_PUNCTUATION      = "punctuation"
108    TYPE_SPACE            = "space"
109
110    GDK_PUNCTUATION_KEYS = [Gdk.KEY_acute,
111                            Gdk.KEY_ampersand,
112                            Gdk.KEY_apostrophe,
113                            Gdk.KEY_asciicircum,
114                            Gdk.KEY_asciitilde,
115                            Gdk.KEY_asterisk,
116                            Gdk.KEY_at,
117                            Gdk.KEY_backslash,
118                            Gdk.KEY_bar,
119                            Gdk.KEY_braceleft,
120                            Gdk.KEY_braceright,
121                            Gdk.KEY_bracketleft,
122                            Gdk.KEY_bracketright,
123                            Gdk.KEY_brokenbar,
124                            Gdk.KEY_cedilla,
125                            Gdk.KEY_cent,
126                            Gdk.KEY_colon,
127                            Gdk.KEY_comma,
128                            Gdk.KEY_copyright,
129                            Gdk.KEY_currency,
130                            Gdk.KEY_degree,
131                            Gdk.KEY_diaeresis,
132                            Gdk.KEY_dollar,
133                            Gdk.KEY_EuroSign,
134                            Gdk.KEY_equal,
135                            Gdk.KEY_exclam,
136                            Gdk.KEY_exclamdown,
137                            Gdk.KEY_grave,
138                            Gdk.KEY_greater,
139                            Gdk.KEY_guillemotleft,
140                            Gdk.KEY_guillemotright,
141                            Gdk.KEY_hyphen,
142                            Gdk.KEY_less,
143                            Gdk.KEY_macron,
144                            Gdk.KEY_minus,
145                            Gdk.KEY_notsign,
146                            Gdk.KEY_numbersign,
147                            Gdk.KEY_paragraph,
148                            Gdk.KEY_parenleft,
149                            Gdk.KEY_parenright,
150                            Gdk.KEY_percent,
151                            Gdk.KEY_period,
152                            Gdk.KEY_periodcentered,
153                            Gdk.KEY_plus,
154                            Gdk.KEY_plusminus,
155                            Gdk.KEY_question,
156                            Gdk.KEY_questiondown,
157                            Gdk.KEY_quotedbl,
158                            Gdk.KEY_quoteleft,
159                            Gdk.KEY_quoteright,
160                            Gdk.KEY_registered,
161                            Gdk.KEY_section,
162                            Gdk.KEY_semicolon,
163                            Gdk.KEY_slash,
164                            Gdk.KEY_sterling,
165                            Gdk.KEY_underscore,
166                            Gdk.KEY_yen]
167
168    GDK_ACCENTED_LETTER_KEYS = [Gdk.KEY_Aacute,
169                                Gdk.KEY_aacute,
170                                Gdk.KEY_Acircumflex,
171                                Gdk.KEY_acircumflex,
172                                Gdk.KEY_Adiaeresis,
173                                Gdk.KEY_adiaeresis,
174                                Gdk.KEY_Agrave,
175                                Gdk.KEY_agrave,
176                                Gdk.KEY_Aring,
177                                Gdk.KEY_aring,
178                                Gdk.KEY_Atilde,
179                                Gdk.KEY_atilde,
180                                Gdk.KEY_Ccedilla,
181                                Gdk.KEY_ccedilla,
182                                Gdk.KEY_Eacute,
183                                Gdk.KEY_eacute,
184                                Gdk.KEY_Ecircumflex,
185                                Gdk.KEY_ecircumflex,
186                                Gdk.KEY_Ediaeresis,
187                                Gdk.KEY_ediaeresis,
188                                Gdk.KEY_Egrave,
189                                Gdk.KEY_egrave,
190                                Gdk.KEY_Iacute,
191                                Gdk.KEY_iacute,
192                                Gdk.KEY_Icircumflex,
193                                Gdk.KEY_icircumflex,
194                                Gdk.KEY_Idiaeresis,
195                                Gdk.KEY_idiaeresis,
196                                Gdk.KEY_Igrave,
197                                Gdk.KEY_igrave,
198                                Gdk.KEY_Ntilde,
199                                Gdk.KEY_ntilde,
200                                Gdk.KEY_Oacute,
201                                Gdk.KEY_oacute,
202                                Gdk.KEY_Ocircumflex,
203                                Gdk.KEY_ocircumflex,
204                                Gdk.KEY_Odiaeresis,
205                                Gdk.KEY_odiaeresis,
206                                Gdk.KEY_Ograve,
207                                Gdk.KEY_ograve,
208                                Gdk.KEY_Ooblique,
209                                Gdk.KEY_ooblique,
210                                Gdk.KEY_Otilde,
211                                Gdk.KEY_otilde,
212                                Gdk.KEY_Uacute,
213                                Gdk.KEY_uacute,
214                                Gdk.KEY_Ucircumflex,
215                                Gdk.KEY_ucircumflex,
216                                Gdk.KEY_Udiaeresis,
217                                Gdk.KEY_udiaeresis,
218                                Gdk.KEY_Ugrave,
219                                Gdk.KEY_ugrave,
220                                Gdk.KEY_Yacute,
221                                Gdk.KEY_yacute]
222
223    def __init__(self, event):
224        """Creates a new InputEvent of type KEYBOARD_EVENT.
225
226        Arguments:
227        - event: the AT-SPI keyboard event
228        """
229
230        super().__init__(KEYBOARD_EVENT)
231        self.id = event.id
232        self.type = event.type
233        self.hw_code = event.hw_code
234        self.modifiers = event.modifiers & Gdk.ModifierType.MODIFIER_MASK
235        if event.modifiers & (1 << pyatspi.MODIFIER_NUMLOCK):
236            self.modifiers |= (1 << pyatspi.MODIFIER_NUMLOCK)
237        self.event_string = event.event_string
238        self.keyval_name = Gdk.keyval_name(event.id)
239        if self.event_string  == "":
240            self.event_string = self.keyval_name
241        self.timestamp = event.timestamp
242        self.is_duplicate = self in [orca_state.lastInputEvent,
243                                     orca_state.lastNonModifierKeyEvent]
244        self._script = orca_state.activeScript
245        self._app = None
246        self._window = orca_state.activeWindow
247        self._obj = orca_state.locusOfFocus
248        self._handler = None
249        self._consumer = None
250        self._should_consume = None
251        self._consume_reason = None
252        self._did_consume = None
253        self._result_reason = None
254        self._bypassOrca = None
255        self._is_kp_with_numlock = False
256
257        # Some implementors don't populate this field at all. More often than not,
258        # the event_string and the keyval_name coincide for input events.
259        if not self.event_string:
260            self.event_string = self.keyval_name
261
262        # Some implementors do populate the field, but with the keyname rather than
263        # the printable character. This messes us up with punctuation and other symbols.
264        if len(self.event_string) > 1 \
265           and (self.id in KeyboardEvent.GDK_PUNCTUATION_KEYS or \
266                self.id in KeyboardEvent.GDK_ACCENTED_LETTER_KEYS):
267            self.event_string = chr(self.id)
268
269        # Some implementors don't include numlock in the modifiers. Unfortunately,
270        # trying to heuristically hack around this just by looking at the event
271        # is not reliable. Ditto regarding asking Gdk for the numlock state.
272        if self.keyval_name.startswith("KP"):
273            if event.modifiers & (1 << pyatspi.MODIFIER_NUMLOCK):
274                self._is_kp_with_numlock = True
275
276        if self._script:
277            self._app = self._script.app
278            if not self._window:
279                self._window = orca_state.activeWindow = self._script.utilities.activeWindow()
280                msg = 'INPUT EVENT: Updated window and active window to %s' % self._window
281                debug.println(debug.LEVEL_INFO, msg, True)
282
283        if self._window and self._app != self._window.getApplication():
284            self._script = script_manager.getManager().getScript(self._window.getApplication())
285            self._app = self._script.app
286            msg = 'INPUT EVENT: Updated script to %s' % self._script
287            debug.println(debug.LEVEL_INFO, msg, True)
288
289        if self.is_duplicate:
290            KeyboardEvent.duplicateCount += 1
291        else:
292            KeyboardEvent.duplicateCount = 0
293
294        self.keyType = None
295
296        _isPressed = event.type == pyatspi.KEY_PRESSED_EVENT
297
298        try:
299            role = self._obj.getRole()
300        except:
301            role = None
302        _mayEcho = _isPressed or role == pyatspi.ROLE_TERMINAL
303
304        if KeyboardEvent.stickyKeys and not self.isOrcaModifier() \
305           and not KeyboardEvent.lastOrcaModifierAlone:
306            doubleEvent = self._getDoubleClickCandidate()
307            if doubleEvent and \
308               doubleEvent.modifiers & keybindings.ORCA_MODIFIER_MASK:
309                # this is the second event of a double-click, and sticky Orca
310                # affected the first, so copy over the modifiers to the second
311                KeyboardEvent.orcaStickyModifiers = doubleEvent.modifiers
312
313        if not self.isOrcaModifier():
314            if KeyboardEvent.orcaModifierPressed:
315                KeyboardEvent.currentOrcaModifierAlone = False
316                KeyboardEvent.currentOrcaModifierAloneTime = None
317            else:
318                KeyboardEvent.lastOrcaModifierAlone = False
319                KeyboardEvent.lastOrcaModifierAloneTime = None
320
321        if self.isNavigationKey():
322            self.keyType = KeyboardEvent.TYPE_NAVIGATION
323            self.shouldEcho = _mayEcho and settings.enableNavigationKeys
324        elif self.isActionKey():
325            self.keyType = KeyboardEvent.TYPE_ACTION
326            self.shouldEcho = _mayEcho and settings.enableActionKeys
327        elif self.isModifierKey():
328            self.keyType = KeyboardEvent.TYPE_MODIFIER
329            self.shouldEcho = _mayEcho and settings.enableModifierKeys
330            if self.isOrcaModifier():
331                now = time.time()
332                if KeyboardEvent.lastOrcaModifierAlone:
333                    if _isPressed:
334                        KeyboardEvent.secondOrcaModifierTime = now
335                    if KeyboardEvent.secondOrcaModifierTime < KeyboardEvent.lastOrcaModifierAloneTime + 0.5:
336                        # double-orca, let the real action happen
337                        self._bypassOrca = True
338                    if not _isPressed:
339                        KeyboardEvent.lastOrcaModifierAlone = False
340                        KeyboardEvent.lastOrcaModifierAloneTime = False
341                else:
342                    KeyboardEvent.orcaModifierPressed = _isPressed
343                    if _isPressed:
344                        KeyboardEvent.currentOrcaModifierAlone = True
345                        KeyboardEvent.currentOrcaModifierAloneTime = now
346                    else:
347                        KeyboardEvent.lastOrcaModifierAlone = KeyboardEvent.currentOrcaModifierAlone
348                        KeyboardEvent.lastOrcaModifierAloneTime = KeyboardEvent.currentOrcaModifierAloneTime
349        elif self.isFunctionKey():
350            self.keyType = KeyboardEvent.TYPE_FUNCTION
351            self.shouldEcho = _mayEcho and settings.enableFunctionKeys
352        elif self.isDiacriticalKey():
353            self.keyType = KeyboardEvent.TYPE_DIACRITICAL
354            self.shouldEcho = _mayEcho and settings.enableDiacriticalKeys
355        elif self.isLockingKey():
356            self.keyType = KeyboardEvent.TYPE_LOCKING
357            self.shouldEcho = settings.presentLockingKeys
358            if self.shouldEcho is None:
359                self.shouldEcho = not settings.onlySpeakDisplayedText
360            self.shouldEcho = self.shouldEcho and _isPressed
361        elif self.isAlphabeticKey():
362            self.keyType = KeyboardEvent.TYPE_ALPHABETIC
363            self.shouldEcho = _mayEcho \
364                and (settings.enableAlphabeticKeys or settings.enableEchoByCharacter)
365        elif self.isNumericKey():
366            self.keyType = KeyboardEvent.TYPE_NUMERIC
367            self.shouldEcho = _mayEcho \
368                and (settings.enableNumericKeys or settings.enableEchoByCharacter)
369        elif self.isPunctuationKey():
370            self.keyType = KeyboardEvent.TYPE_PUNCTUATION
371            self.shouldEcho = _mayEcho \
372                and (settings.enablePunctuationKeys or settings.enableEchoByCharacter)
373        elif self.isSpace():
374            self.keyType = KeyboardEvent.TYPE_SPACE
375            self.shouldEcho = _mayEcho \
376                and (settings.enableSpace or settings.enableEchoByCharacter)
377        else:
378            self.keyType = KeyboardEvent.TYPE_UNKNOWN
379            self.shouldEcho = False
380
381        if not self.isLockingKey():
382            self.shouldEcho = self.shouldEcho and settings.enableKeyEcho
383
384        if not self.isModifierKey():
385            self.setClickCount()
386
387        if orca_state.bypassNextCommand and _isPressed:
388            KeyboardEvent.orcaModifierPressed = False
389
390        if KeyboardEvent.orcaModifierPressed:
391            self.modifiers |= keybindings.ORCA_MODIFIER_MASK
392
393        if KeyboardEvent.stickyKeys:
394            # apply all recorded sticky modifiers
395            self.modifiers |= KeyboardEvent.orcaStickyModifiers
396            if self.isModifierKey():
397                # add this modifier to the sticky ones
398                KeyboardEvent.orcaStickyModifiers |= self.modifiers
399            else:
400                # Non-modifier key, so clear the sticky modifiers. If the user
401                # actually double-presses that key, the modifiers of this event
402                # will be copied over to the second event, see earlier in this
403                # function.
404                KeyboardEvent.orcaStickyModifiers = 0
405
406        self._should_consume, self._consume_reason = self.shouldConsume()
407
408    def _getDoubleClickCandidate(self):
409        lastEvent = orca_state.lastNonModifierKeyEvent
410        if isinstance(lastEvent, KeyboardEvent) \
411           and lastEvent.event_string == self.event_string \
412           and self.time - lastEvent.time <= settings.doubleClickTimeout:
413            return lastEvent
414        return None
415
416    def setClickCount(self):
417        """Updates the count of the number of clicks a user has made."""
418
419        doubleEvent = self._getDoubleClickCandidate()
420        if not doubleEvent:
421            self._clickCount = 1
422            return
423
424        self._clickCount = doubleEvent.getClickCount()
425        if self.is_duplicate:
426            return
427
428        if self.type == pyatspi.KEY_RELEASED_EVENT:
429            return
430
431        if self._clickCount < 3:
432            self._clickCount += 1
433            return
434
435        self._clickCount = 1
436
437    def __eq__(self, other):
438        if not other:
439            return False
440
441        if self.type == other.type and self.hw_code == other.hw_code:
442            return self.timestamp == other.timestamp
443
444        return False
445
446    def __str__(self):
447        if self._shouldObscure():
448            keyid = hw_code = modifiers = event_string = keyval_name = key_type = "*"
449        else:
450            keyid = self.id
451            hw_code = self.hw_code
452            modifiers = self.modifiers
453            event_string = self.event_string
454            keyval_name = self.keyval_name
455            key_type = self.keyType
456
457        return ("KEYBOARD_EVENT:  type=%s\n" % self.type.value_name.upper()) \
458             + ("                 id=%s\n" % keyid) \
459             + ("                 hw_code=%s\n" % hw_code) \
460             + ("                 modifiers=%s\n" % modifiers) \
461             + ("                 event_string=(%s)\n" % event_string) \
462             + ("                 keyval_name=(%s)\n" % keyval_name) \
463             + ("                 timestamp=%d\n" % self.timestamp) \
464             + ("                 time=%f\n" % time.time()) \
465             + ("                 keyType=%s\n" % key_type) \
466             + ("                 clickCount=%s\n" % self._clickCount) \
467             + ("                 shouldEcho=%s\n" % self.shouldEcho)
468
469    def _shouldObscure(self):
470        try:
471            role = self._obj.getRole()
472        except:
473            return False
474
475        if role != pyatspi.ROLE_PASSWORD_TEXT:
476            return False
477
478        if not self.isPrintableKey():
479            return False
480
481        if self.modifiers & keybindings.CTRL_MODIFIER_MASK \
482           or self.modifiers & keybindings.ALT_MODIFIER_MASK \
483           or self.modifiers & keybindings.ORCA_MODIFIER_MASK:
484            return False
485
486        return True
487
488    def _isReleaseForLastNonModifierKeyEvent(self):
489        last = orca_state.lastNonModifierKeyEvent
490        if not last:
491            return False
492
493        if not last.isPressedKey() or self.isPressedKey():
494            return False
495
496        if self.id == last.id and self.hw_code == last.hw_code:
497            return self.modifiers == last.modifiers
498
499        return False
500
501    def isReleaseFor(self, other):
502        """Return True if this is the release event for other."""
503
504        if not other:
505            return False
506
507        if not other.isPressedKey() or self.isPressedKey():
508            return False
509
510        return self.id == other.id \
511            and self.hw_code == other.hw_code \
512            and self.modifiers == other.modifiers \
513            and self.event_string == other.event_string \
514            and self.keyval_name == other.keyval_name \
515            and self.keyType == other.keyType \
516            and self._clickCount == other._clickCount
517
518    def isNavigationKey(self):
519        """Return True if this is a navigation key."""
520
521        if self.keyType:
522            return self.keyType == KeyboardEvent.TYPE_NAVIGATION
523
524        return self.event_string in \
525            ["Left", "Right", "Up", "Down", "Home", "End"]
526
527    def isActionKey(self):
528        """Return True if this is an action key."""
529
530        if self.keyType:
531            return self.keyType == KeyboardEvent.TYPE_ACTION
532
533        return self.event_string in \
534            ["Return", "Escape", "Tab", "BackSpace", "Delete",
535             "Page_Up", "Page_Down"]
536
537    def isAlphabeticKey(self):
538        """Return True if this is an alphabetic key."""
539
540        if self.keyType:
541            return self.keyType == KeyboardEvent.TYPE_ALPHABETIC
542
543        if not len(self.event_string) == 1:
544            return False
545
546        return self.event_string.isalpha()
547
548    def isDiacriticalKey(self):
549        """Return True if this is a non-spacing diacritical key."""
550
551        if self.keyType:
552            return self.keyType == KeyboardEvent.TYPE_DIACRITICAL
553
554        return self.event_string.startswith("dead_")
555
556    def isFunctionKey(self):
557        """Return True if this is a function key."""
558
559        if self.keyType:
560            return self.keyType == KeyboardEvent.TYPE_FUNCTION
561
562        return self.event_string in \
563            ["F1", "F2", "F3", "F4", "F5", "F6",
564             "F7", "F8", "F9", "F10", "F11", "F12"]
565
566    def isLockingKey(self):
567        """Return True if this is a locking key."""
568
569        if self.keyType:
570            return self.keyType in KeyboardEvent.TYPE_LOCKING
571
572        lockingKeys = ["Caps_Lock", "Shift_Lock", "Num_Lock", "Scroll_Lock"]
573        if not self.event_string in lockingKeys:
574            return False
575
576        if not orca_state.bypassNextCommand and not self._bypassOrca:
577            return not self.event_string in settings.orcaModifierKeys
578
579        return True
580
581    def isModifierKey(self):
582        """Return True if this is a modifier key."""
583
584        if self.keyType:
585            return self.keyType == KeyboardEvent.TYPE_MODIFIER
586
587        if self.isOrcaModifier():
588            return True
589
590        return self.event_string in \
591            ['Alt_L', 'Alt_R', 'Control_L', 'Control_R',
592             'Shift_L', 'Shift_R', 'Meta_L', 'Meta_R',
593             'ISO_Level3_Shift']
594
595    def isNumericKey(self):
596        """Return True if this is a numeric key."""
597
598        if self.keyType:
599            return self.keyType == KeyboardEvent.TYPE_NUMERIC
600
601        if not len(self.event_string) == 1:
602            return False
603
604        return self.event_string.isnumeric()
605
606    def isOrcaModifier(self, checkBypassMode=True):
607        """Return True if this is the Orca modifier key."""
608
609        if checkBypassMode and orca_state.bypassNextCommand:
610            return False
611
612        if self.event_string in settings.orcaModifierKeys:
613            return True
614
615        if self.keyval_name == "KP_0" \
616           and "KP_Insert" in settings.orcaModifierKeys \
617           and self.modifiers & keybindings.SHIFT_MODIFIER_MASK:
618            return True
619
620        return False
621
622    def isOrcaModified(self):
623        """Return True if this key is Orca modified."""
624
625        if orca_state.bypassNextCommand:
626            return False
627
628        return self.modifiers & keybindings.ORCA_MODIFIER_MASK
629
630    def isKeyPadKeyWithNumlockOn(self):
631        """Return True if this is a key pad key with numlock on."""
632
633        return self._is_kp_with_numlock
634
635    def isPrintableKey(self):
636        """Return True if this is a printable key."""
637
638        if self.event_string in ["space", " "]:
639            return True
640
641        if not len(self.event_string) == 1:
642            return False
643
644        return self.event_string.isprintable()
645
646    def isPressedKey(self):
647        """Returns True if the key is pressed"""
648
649        return self.type == pyatspi.KEY_PRESSED_EVENT
650
651    def isPunctuationKey(self):
652        """Return True if this is a punctuation key."""
653
654        if self.keyType:
655            return self.keyType == KeyboardEvent.TYPE_PUNCTUATION
656
657        if not len(self.event_string) == 1:
658            return False
659
660        if self.isAlphabeticKey() or self.isNumericKey():
661            return False
662
663        return self.event_string.isprintable() and not self.event_string.isspace()
664
665    def isSpace(self):
666        """Return True if this is the space key."""
667
668        if self.keyType:
669            return self.keyType == KeyboardEvent.TYPE_SPACE
670
671        return self.event_string in ["space", " "]
672
673    def isFromApplication(self, app):
674        """Return True if this is associated with the specified app."""
675
676        return self._app == app
677
678    def isCharacterEchoable(self):
679        """Returns True if the script will echo this event as part of
680        character echo. We do this to not double-echo a given printable
681        character."""
682
683        if not self.isPrintableKey():
684            return False
685
686        if orca_state.learnModeEnabled:
687            return False
688
689        script = orca_state.activeScript
690        return script and script.utilities.willEchoCharacter(self)
691
692    def getLockingState(self):
693        """Returns True if the event locked a locking key, False if the
694        event unlocked a locking key, and None if we do not know or this
695        is not a locking key."""
696
697        if not self.isLockingKey():
698            return None
699
700        if self.event_string == "Caps_Lock":
701            mod = pyatspi.MODIFIER_SHIFTLOCK
702        elif self.event_string == "Shift_Lock":
703            mod = pyatspi.MODIFIER_SHIFT
704        elif self.event_string == "Num_Lock":
705            mod = pyatspi.MODIFIER_NUMLOCK
706        else:
707            return None
708
709        return not self.modifiers & (1 << mod)
710
711    def getLockingStateString(self):
712        """Returns the string which reflects the locking state we wish to
713        include when presenting a locking key."""
714
715        locked = self.getLockingState()
716        if locked is None:
717            return ''
718
719        if not locked:
720            return messages.LOCKING_KEY_STATE_OFF
721
722        return messages.LOCKING_KEY_STATE_ON
723
724    def getKeyName(self):
725        """Returns the string to be used for presenting the key to the user."""
726
727        return keynames.getKeyName(self.event_string)
728
729    def getObject(self):
730        """Returns the object believed to be associated with this key event."""
731
732        return self._obj
733
734    def _getUserHandler(self):
735        # TODO - JD: This should go away once plugin support is in place.
736        try:
737            bindings = settings.keyBindingsMap.get(self._script.__module__)
738        except:
739            bindings = None
740        if not bindings:
741            try:
742                bindings = settings.keyBindingsMap.get("default")
743            except:
744                bindings = None
745
746        try:
747            handler = bindings.getInputHandler(self)
748        except:
749            handler = None
750
751        return handler
752
753    def shouldConsume(self):
754        """Returns True if this event should be consumed."""
755
756        if not self.timestamp:
757            return False, 'No timestamp'
758
759        if not self._script:
760            return False, 'No active script when received'
761
762        if self.is_duplicate:
763            return False, 'Is duplicate'
764
765        if orca_state.capturingKeys:
766            return False, 'Capturing keys'
767
768        if orca_state.bypassNextCommand:
769            return False, 'Bypass next command'
770
771        self._handler = self._getUserHandler() \
772            or self._script.keyBindings.getInputHandler(self)
773
774        # TODO - JD: Right now we need to always call consumesKeyboardEvent()
775        # because that method is updating state, even in instances where there
776        # is no handler.
777        scriptConsumes = self._script.consumesKeyboardEvent(self)
778
779        if self._isReleaseForLastNonModifierKeyEvent():
780            return scriptConsumes, 'Is release for last non-modifier keyevent'
781
782        if orca_state.learnModeEnabled:
783            if self.event_string == 'Escape':
784                self._consumer = self._script.exitLearnMode
785                return True, 'Exiting Learn Mode'
786
787            if self.event_string == 'F1' and not self.modifiers:
788                self._consumer = self._script.showHelp
789                return True, 'Showing Help'
790
791            if self.event_string in ['F2', 'F3'] and not self.modifiers:
792                self._consumer = self._script.listOrcaShortcuts
793                return True, 'Listing shortcuts'
794
795            self._consumer = self._presentHandler
796            return True, 'In Learn Mode'
797
798        if self.isModifierKey():
799            if not self.isOrcaModifier():
800                return False, 'Non-Orca modifier not in Learn Mode'
801            return True, 'Orca modifier'
802
803        if orca_state.listNotificationsModeEnabled:
804            self._consumer = self._script.listNotifications
805            return True, 'Listing notifications'
806
807        if not self._handler:
808            return False, 'No handler'
809
810        return scriptConsumes, 'Script indication'
811
812    def didConsume(self):
813        """Returns True if this event was consumed."""
814
815        if self._did_consume is not None:
816            return self._did_consume
817
818        return False
819
820    def isHandledBy(self, method):
821        if not self._handler:
822            return False
823
824        return method.__func__ == self._handler.function
825
826    def _present(self, inputEvent=None):
827        if self.isPressedKey():
828            self._script.presentationInterrupt()
829
830        return self._script.presentKeyboardEvent(self)
831
832    def _presentHandler(self, input_event=None):
833        if not self._handler:
834            return False
835
836        if self._handler.learnModeEnabled and self._handler.description:
837            self._script.presentMessage(self._handler.description)
838
839        return True
840
841    def process(self):
842        """Processes this input event."""
843
844        startTime = time.time()
845        if not self._shouldObscure():
846            data = "'%s' (%d)" % (self.event_string, self.hw_code)
847        else:
848            data = "(obscured)"
849
850        if self.is_duplicate:
851            data = '%s DUPLICATE EVENT #%i' % (data, KeyboardEvent.duplicateCount)
852
853        msg = '\nvvvvv PROCESS %s: %s vvvvv' % (self.type.value_name.upper(), data)
854        debug.println(debug.LEVEL_INFO, msg, False)
855
856        msg = 'HOST_APP: %s' % self._app
857        debug.println(debug.LEVEL_INFO, msg, True)
858
859        msg = 'WINDOW:   %s' % self._window
860        debug.println(debug.LEVEL_INFO, msg, True)
861
862        msg = 'LOCATION: %s' % self._obj
863        debug.println(debug.LEVEL_INFO, msg, True)
864
865        msg = 'CONSUME:  %s (%s)' % (self._should_consume, self._consume_reason)
866        debug.println(debug.LEVEL_INFO, msg, True)
867
868        self._did_consume, self._result_reason = self._process()
869
870        if self._should_consume != self._did_consume:
871            msg = 'CONSUMED: %s (%s)' % (self._did_consume, self._result_reason)
872            debug.println(debug.LEVEL_INFO, msg, True)
873
874        if debug.LEVEL_INFO >= debug.debugLevel and orca_state.activeScript:
875            attributes = orca_state.activeScript.getTransferableAttributes()
876            for key, value in attributes.items():
877                msg = 'INPUT EVENT: %s: %s' % (key, value)
878                debug.println(debug.LEVEL_INFO, msg, True)
879
880        msg = 'TOTAL PROCESSING TIME: %.4f' % (time.time() - startTime)
881        debug.println(debug.LEVEL_INFO, msg, True)
882
883        msg = '^^^^^ PROCESS %s: %s ^^^^^\n' % (self.type.value_name.upper(), data)
884        debug.println(debug.LEVEL_INFO, msg, False)
885
886        return self._did_consume
887
888    def _process(self):
889        """Processes this input event."""
890
891        if self._bypassOrca:
892            if (self.event_string == "Caps_Lock" \
893                or self.event_string == "Shift_Lock") \
894               and self.type == pyatspi.KEY_PRESSED_EVENT:
895                    self._lock_mod()
896                    self.keyType = KeyboardEvent.TYPE_LOCKING
897                    self._present()
898            return False, 'Bypassed orca modifier'
899
900        orca_state.lastInputEvent = self
901        if not self.isModifierKey():
902            orca_state.lastNonModifierKeyEvent = self
903
904        if not self._script:
905            return False, 'No active script'
906
907        if self.is_duplicate:
908            return False, 'Is duplicate'
909
910        self._present()
911
912        if not self.isPressedKey():
913            return self._should_consume, 'Consumed based on handler'
914
915        if orca_state.capturingKeys:
916            return False, 'Capturing keys'
917
918        if self.isOrcaModifier():
919            return True, 'Orca modifier'
920
921        if orca_state.bypassNextCommand:
922            if not self.isModifierKey():
923                orca_state.bypassNextCommand = False
924            self._script.addKeyGrabs()
925            return False, 'Bypass next command'
926
927        if not self._should_consume:
928            return False, 'Should not consume'
929
930        if not (self._consumer or self._handler):
931            return False, 'No consumer or handler'
932
933        if self._consumer or self._handler.function:
934            GLib.timeout_add(1, self._consume)
935            return True, 'Will be consumed'
936
937        return False, 'Unaddressed case'
938
939    def _lock_mod(self):
940        def lock_mod(modifiers, modifier):
941            def lockit():
942                try:
943                    if modifiers & modifier:
944                        lock = pyatspi.KEY_UNLOCKMODIFIERS
945                        debug.println(debug.LEVEL_INFO, "Unlocking capslock", True)
946                    else:
947                        lock = pyatspi.KEY_LOCKMODIFIERS
948                        debug.println(debug.LEVEL_INFO, "Locking capslock", True)
949                    pyatspi.Registry.generateKeyboardEvent(modifier, None, lock)
950                    debug.println(debug.LEVEL_INFO, "Done with capslock", True)
951                except:
952                    debug.println(debug.LEVEL_INFO, "Could not trigger capslock, " \
953                        "at-spi2-core >= 2.32 is needed for triggering capslock", True)
954                    pass
955            return lockit
956        if self.event_string == "Caps_Lock":
957            modifier = 1 << pyatspi.MODIFIER_SHIFTLOCK
958        elif self.event_string == "Shift_Lock":
959            modifier = 1 << pyatspi.MODIFIER_SHIFT
960        else:
961            msg = "Unknown locking key %s" % self.event_string
962            debug.println(debug.LEVEL_WARNING, msg, True)
963            return
964        debug.println(debug.LEVEL_INFO, "Scheduling capslock", True)
965        GLib.timeout_add(1, lock_mod(self.modifiers, modifier))
966
967    def _consume(self):
968        startTime = time.time()
969        data = "'%s' (%d)" % (self.event_string, self.hw_code)
970        msg = 'vvvvv CONSUME %s: %s vvvvv' % (self.type.value_name.upper(), data)
971        debug.println(debug.LEVEL_INFO, msg, False)
972
973        if self._consumer:
974            msg = 'INFO: Consumer is %s' % self._consumer.__name__
975            debug.println(debug.LEVEL_INFO, msg, True)
976            self._consumer(self)
977        elif self._handler.function:
978            msg = 'INFO: Handler is %s' % self._handler.description
979            debug.println(debug.LEVEL_INFO, msg, True)
980            self._handler.function(self._script, self)
981        else:
982            msg = 'INFO: No handler or consumer'
983            debug.println(debug.LEVEL_INFO, msg, True)
984
985        msg = 'TOTAL PROCESSING TIME: %.4f' % (time.time() - startTime)
986        debug.println(debug.LEVEL_INFO, msg, True)
987
988        msg = '^^^^^ CONSUME %s: %s ^^^^^' % (self.type.value_name.upper(), data)
989        debug.println(debug.LEVEL_INFO, msg, False)
990
991        return False
992
993class BrailleEvent(InputEvent):
994
995    def __init__(self, event):
996        """Creates a new InputEvent of type BRAILLE_EVENT.
997
998        Arguments:
999        - event: the integer BrlTTY command for this event.
1000        """
1001        super().__init__(BRAILLE_EVENT)
1002        self.event = event
1003
1004class MouseButtonEvent(InputEvent):
1005
1006    try:
1007        display = Gdk.Display.get_default()
1008        seat = Gdk.Display.get_default_seat(display)
1009        _pointer = seat.get_pointer()
1010    except:
1011        _pointer = None
1012
1013    def __init__(self, event):
1014        """Creates a new InputEvent of type MOUSE_BUTTON_EVENT."""
1015
1016        super().__init__(MOUSE_BUTTON_EVENT)
1017        self.x = event.detail1
1018        self.y = event.detail2
1019        self.pressed = event.type.endswith('p')
1020        self.button = event.type[len("mouse:button:"):-1]
1021        self._script = orca_state.activeScript
1022        self.window = orca_state.activeWindow
1023        self.obj = None
1024
1025        if self.pressed:
1026            self._validateCoordinates()
1027
1028        if not self._script:
1029            return
1030
1031        if not self._script.utilities.canBeActiveWindow(self.window):
1032            self.window = self._script.utilities.activeWindow()
1033
1034        if not self.window:
1035            return
1036
1037        self.obj = self._script.utilities.descendantAtPoint(
1038            self.window, self.x, self.y, event.any_data)
1039
1040    def _validateCoordinates(self):
1041        if not self._pointer:
1042            return
1043
1044        screen, x, y = self._pointer.get_position()
1045        if math.sqrt((self.x - x)**2 + (self.y - y)**2) < 25:
1046            return
1047
1048        msg = "WARNING: Event coordinates (%i, %i) may be bogus. " \
1049              "Updating to (%i, %i)." % (self.x, self.y, x, y)
1050        debug.println(debug.LEVEL_INFO, msg, True)
1051        self.x, self.y = x, y
1052
1053    def setClickCount(self):
1054        """Updates the count of the number of clicks a user has made."""
1055
1056        if not self.pressed:
1057            return
1058
1059        lastInputEvent = orca_state.lastInputEvent
1060        if not isinstance(lastInputEvent, MouseButtonEvent):
1061            self._clickCount = 1
1062            return
1063
1064        if self.time - lastInputEvent.time < settings.doubleClickTimeout \
1065            and lastInputEvent.button == self.button:
1066            if self._clickCount < 2:
1067                self._clickCount += 1
1068                return
1069
1070        self._clickCount = 1
1071
1072
1073class InputEventHandler:
1074
1075    def __init__(self, function, description, learnModeEnabled=True):
1076        """Creates a new InputEventHandler instance.  All bindings
1077        (e.g., key bindings and braille bindings) will be handled
1078        by an instance of an InputEventHandler.
1079
1080        Arguments:
1081        - function: the function to call with an InputEvent instance as its
1082                    sole argument.  The function is expected to return True
1083                    if it consumes the event; otherwise it should return
1084                    False
1085        - description: a localized string describing what this InputEvent
1086                       does
1087        - learnModeEnabled: if True, the description will be spoken and
1088                            brailled if learn mode is enabled.  If False,
1089                            the function will be called no matter what.
1090        """
1091
1092        self.function = function
1093        self.description = description
1094        self.learnModeEnabled = learnModeEnabled
1095
1096    def __eq__(self, other):
1097        """Compares one input handler to another."""
1098
1099        if not other:
1100            return False
1101
1102        return (self.function == other.function)
1103
1104    def processInputEvent(self, script, inputEvent):
1105        """Processes an input event.  If learnModeEnabled is True,
1106        this will merely present the description of the input event via
1107        If learnModeEnabled is False, this will call the function bound
1108        to this InputEventHandler instance, passing the inputEvent as
1109        the sole argument to the function.
1110
1111        This function is expected to return True if it consumes the
1112        event; otherwise it is expected to return False.
1113
1114        Arguments:
1115        - script:     the script (if any) associated with this event
1116        - inputEvent: the input event to pass to the function bound
1117                      to this InputEventHandler instance.
1118        """
1119
1120        consumed = False
1121
1122        if orca_state.learnModeEnabled and self._learnModeEnabled:
1123            if self.description:
1124                script.presentMessage(self.description)
1125                consumed = True
1126        else:
1127            try:
1128                consumed = self.function(script, inputEvent)
1129            except:
1130                debug.printException(debug.LEVEL_SEVERE)
1131
1132        return consumed
1133