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