1# Orca 2# 3# Copyright 2005-2009 Sun Microsystems Inc. 4# Copyright 2010 Orca Team. 5# Copyright 2014-2015 Igalia, S.L. 6# 7# This library is free software; you can redistribute it and/or 8# modify it under the terms of the GNU Lesser General Public 9# License as published by the Free Software Foundation; either 10# version 2.1 of the License, or (at your option) any later version. 11# 12# This library is distributed in the hope that it will be useful, 13# but WITHOUT ANY WARRANTY; without even the implied warranty of 14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15# Lesser General Public License for more details. 16# 17# You should have received a copy of the GNU Lesser General Public 18# License along with this library; if not, write to the 19# Free Software Foundation, Inc., Franklin Street, Fifth Floor, 20# Boston MA 02110-1301 USA. 21 22__id__ = "$Id$" 23__version__ = "$Revision$" 24__date__ = "$Date$" 25__copyright__ = "Copyright (c) 2005-2009 Sun Microsystems Inc." \ 26 "Copyright (c) 2010 Orca Team." \ 27 "Copyright (c) 2014-2015 Igalia, S.L." 28__license__ = "LGPL" 29 30from gi.repository import Gtk 31import pyatspi 32import time 33 34from orca import caret_navigation 35from orca import cmdnames 36from orca import keybindings 37from orca import debug 38from orca import eventsynthesizer 39from orca import guilabels 40from orca import input_event 41from orca import liveregions 42from orca import messages 43from orca import object_properties 44from orca import orca 45from orca import orca_state 46from orca import settings 47from orca import settings_manager 48from orca import speech 49from orca import speechserver 50from orca import structural_navigation 51from orca.acss import ACSS 52from orca.scripts import default 53 54from .bookmarks import Bookmarks 55from .braille_generator import BrailleGenerator 56from .sound_generator import SoundGenerator 57from .speech_generator import SpeechGenerator 58from .tutorial_generator import TutorialGenerator 59from .script_utilities import Utilities 60 61_settingsManager = settings_manager.getManager() 62 63 64class Script(default.Script): 65 66 def __init__(self, app): 67 super().__init__(app) 68 69 self._sayAllContents = [] 70 self._inSayAll = False 71 self._sayAllIsInterrupted = False 72 self._loadingDocumentContent = False 73 self._madeFindAnnouncement = False 74 self._lastCommandWasCaretNav = False 75 self._lastCommandWasStructNav = False 76 self._lastCommandWasMouseButton = False 77 self._lastMouseButtonContext = None, -1 78 self._lastMouseOverObject = None 79 self._preMouseOverContext = None, -1 80 self._inMouseOverObject = False 81 self._inFocusMode = False 82 self._focusModeIsSticky = False 83 self._browseModeIsSticky = False 84 85 if _settingsManager.getSetting('caretNavigationEnabled') is None: 86 _settingsManager.setSetting('caretNavigationEnabled', True) 87 if _settingsManager.getSetting('sayAllOnLoad') is None: 88 _settingsManager.setSetting('sayAllOnLoad', True) 89 if _settingsManager.getSetting('pageSummaryOnLoad') is None: 90 _settingsManager.setSetting('pageSummaryOnLoad', True) 91 92 self._changedLinesOnlyCheckButton = None 93 self._controlCaretNavigationCheckButton = None 94 self._minimumFindLengthAdjustment = None 95 self._minimumFindLengthLabel = None 96 self._minimumFindLengthSpinButton = None 97 self._pageSummaryOnLoadCheckButton = None 98 self._sayAllOnLoadCheckButton = None 99 self._skipBlankCellsCheckButton = None 100 self._speakCellCoordinatesCheckButton = None 101 self._speakCellHeadersCheckButton = None 102 self._speakCellSpanCheckButton = None 103 self._speakResultsDuringFindCheckButton = None 104 self._structuralNavigationCheckButton = None 105 self._autoFocusModeStructNavCheckButton = None 106 self._autoFocusModeCaretNavCheckButton = None 107 self._autoFocusModeNativeNavCheckButton = None 108 self._layoutModeCheckButton = None 109 110 self.attributeNamesDict["invalid"] = "text-spelling" 111 self.attributeNamesDict["text-align"] = "justification" 112 self.attributeNamesDict["text-indent"] = "indent" 113 114 def deactivate(self): 115 """Called when this script is deactivated.""" 116 117 self._sayAllContents = [] 118 self._inSayAll = False 119 self._sayAllIsInterrupted = False 120 self._loadingDocumentContent = False 121 self._madeFindAnnouncement = False 122 self._lastCommandWasCaretNav = False 123 self._lastCommandWasStructNav = False 124 self._lastCommandWasMouseButton = False 125 self._lastMouseButtonContext = None, -1 126 self._lastMouseOverObject = None 127 self._preMouseOverContext = None, -1 128 self._inMouseOverObject = False 129 self.utilities.clearCachedObjects() 130 self.removeKeyGrabs() 131 132 def getAppKeyBindings(self): 133 """Returns the application-specific keybindings for this script.""" 134 135 keyBindings = keybindings.KeyBindings() 136 137 structNavBindings = self.structuralNavigation.keyBindings 138 for keyBinding in structNavBindings.keyBindings: 139 keyBindings.add(keyBinding) 140 141 caretNavBindings = self.caretNavigation.get_bindings() 142 for keyBinding in caretNavBindings.keyBindings: 143 keyBindings.add(keyBinding) 144 145 liveRegionBindings = self.liveRegionManager.keyBindings 146 for keyBinding in liveRegionBindings.keyBindings: 147 keyBindings.add(keyBinding) 148 149 keyBindings.add( 150 keybindings.KeyBinding( 151 "a", 152 keybindings.defaultModifierMask, 153 keybindings.ORCA_MODIFIER_MASK, 154 self.inputEventHandlers.get("togglePresentationModeHandler"))) 155 156 keyBindings.add( 157 keybindings.KeyBinding( 158 "a", 159 keybindings.defaultModifierMask, 160 keybindings.ORCA_MODIFIER_MASK, 161 self.inputEventHandlers.get("enableStickyFocusModeHandler"), 162 2)) 163 164 keyBindings.add( 165 keybindings.KeyBinding( 166 "a", 167 keybindings.defaultModifierMask, 168 keybindings.ORCA_MODIFIER_MASK, 169 self.inputEventHandlers.get("enableStickyBrowseModeHandler"), 170 3)) 171 172 keyBindings.add( 173 keybindings.KeyBinding( 174 "", 175 keybindings.defaultModifierMask, 176 keybindings.NO_MODIFIER_MASK, 177 self.inputEventHandlers.get("toggleLayoutModeHandler"))) 178 179 180 layout = _settingsManager.getSetting('keyboardLayout') 181 if layout == settings.GENERAL_KEYBOARD_LAYOUT_DESKTOP: 182 key = "KP_Multiply" 183 else: 184 key = "0" 185 186 keyBindings.add( 187 keybindings.KeyBinding( 188 key, 189 keybindings.defaultModifierMask, 190 keybindings.ORCA_MODIFIER_MASK, 191 self.inputEventHandlers.get("moveToMouseOverHandler"))) 192 193 return keyBindings 194 195 def setupInputEventHandlers(self): 196 """Defines InputEventHandlers for this script.""" 197 198 super().setupInputEventHandlers() 199 self.inputEventHandlers.update( 200 self.structuralNavigation.inputEventHandlers) 201 202 self.inputEventHandlers.update( 203 self.caretNavigation.get_handlers()) 204 205 self.inputEventHandlers.update( 206 self.liveRegionManager.inputEventHandlers) 207 208 self.inputEventHandlers["sayAllHandler"] = \ 209 input_event.InputEventHandler( 210 Script.sayAll, 211 cmdnames.SAY_ALL) 212 213 self.inputEventHandlers["panBrailleLeftHandler"] = \ 214 input_event.InputEventHandler( 215 Script.panBrailleLeft, 216 cmdnames.PAN_BRAILLE_LEFT, 217 False) # Do not enable learn mode for this action 218 219 self.inputEventHandlers["panBrailleRightHandler"] = \ 220 input_event.InputEventHandler( 221 Script.panBrailleRight, 222 cmdnames.PAN_BRAILLE_RIGHT, 223 False) # Do not enable learn mode for this action 224 225 self.inputEventHandlers["moveToMouseOverHandler"] = \ 226 input_event.InputEventHandler( 227 Script.moveToMouseOver, 228 cmdnames.MOUSE_OVER_MOVE) 229 230 self.inputEventHandlers["togglePresentationModeHandler"] = \ 231 input_event.InputEventHandler( 232 Script.togglePresentationMode, 233 cmdnames.TOGGLE_PRESENTATION_MODE) 234 235 self.inputEventHandlers["enableStickyFocusModeHandler"] = \ 236 input_event.InputEventHandler( 237 Script.enableStickyFocusMode, 238 cmdnames.SET_FOCUS_MODE_STICKY) 239 240 self.inputEventHandlers["enableStickyBrowseModeHandler"] = \ 241 input_event.InputEventHandler( 242 Script.enableStickyBrowseMode, 243 cmdnames.SET_BROWSE_MODE_STICKY) 244 245 self.inputEventHandlers["toggleLayoutModeHandler"] = \ 246 input_event.InputEventHandler( 247 Script.toggleLayoutMode, 248 cmdnames.TOGGLE_LAYOUT_MODE) 249 250 def getBookmarks(self): 251 """Returns the "bookmarks" class for this script.""" 252 253 try: 254 return self.bookmarks 255 except AttributeError: 256 self.bookmarks = Bookmarks(self) 257 return self.bookmarks 258 259 def getBrailleGenerator(self): 260 """Returns the braille generator for this script.""" 261 262 return BrailleGenerator(self) 263 264 def getCaretNavigation(self): 265 """Returns the caret navigation support for this script.""" 266 267 return caret_navigation.CaretNavigation(self) 268 269 def getEnabledStructuralNavigationTypes(self): 270 """Returns the structural navigation object types for this script.""" 271 272 return [structural_navigation.StructuralNavigation.BLOCKQUOTE, 273 structural_navigation.StructuralNavigation.BUTTON, 274 structural_navigation.StructuralNavigation.CHECK_BOX, 275 structural_navigation.StructuralNavigation.CHUNK, 276 structural_navigation.StructuralNavigation.CLICKABLE, 277 structural_navigation.StructuralNavigation.COMBO_BOX, 278 structural_navigation.StructuralNavigation.CONTAINER, 279 structural_navigation.StructuralNavigation.ENTRY, 280 structural_navigation.StructuralNavigation.FORM_FIELD, 281 structural_navigation.StructuralNavigation.HEADING, 282 structural_navigation.StructuralNavigation.IMAGE, 283 structural_navigation.StructuralNavigation.LANDMARK, 284 structural_navigation.StructuralNavigation.LINK, 285 structural_navigation.StructuralNavigation.LIST, 286 structural_navigation.StructuralNavigation.LIST_ITEM, 287 structural_navigation.StructuralNavigation.LIVE_REGION, 288 structural_navigation.StructuralNavigation.PARAGRAPH, 289 structural_navigation.StructuralNavigation.RADIO_BUTTON, 290 structural_navigation.StructuralNavigation.SEPARATOR, 291 structural_navigation.StructuralNavigation.TABLE, 292 structural_navigation.StructuralNavigation.TABLE_CELL, 293 structural_navigation.StructuralNavigation.UNVISITED_LINK, 294 structural_navigation.StructuralNavigation.VISITED_LINK] 295 296 def getLiveRegionManager(self): 297 """Returns the live region support for this script.""" 298 299 return liveregions.LiveRegionManager(self) 300 301 def getSoundGenerator(self): 302 """Returns the sound generator for this script.""" 303 304 return SoundGenerator(self) 305 306 def getSpeechGenerator(self): 307 """Returns the speech generator for this script.""" 308 309 return SpeechGenerator(self) 310 311 def getTutorialGenerator(self): 312 """Returns the tutorial generator for this script.""" 313 314 return TutorialGenerator(self) 315 316 def getUtilities(self): 317 """Returns the utilites for this script.""" 318 319 return Utilities(self) 320 321 def getAppPreferencesGUI(self): 322 """Return a GtkGrid containing app-unique configuration items.""" 323 324 grid = Gtk.Grid() 325 grid.set_border_width(12) 326 327 generalFrame = Gtk.Frame() 328 grid.attach(generalFrame, 0, 0, 1, 1) 329 330 label = Gtk.Label(label="<b>%s</b>" % guilabels.PAGE_NAVIGATION) 331 label.set_use_markup(True) 332 generalFrame.set_label_widget(label) 333 334 generalAlignment = Gtk.Alignment.new(0.5, 0.5, 1, 1) 335 generalAlignment.set_padding(0, 0, 12, 0) 336 generalFrame.add(generalAlignment) 337 generalGrid = Gtk.Grid() 338 generalAlignment.add(generalGrid) 339 340 label = guilabels.USE_CARET_NAVIGATION 341 value = _settingsManager.getSetting('caretNavigationEnabled') 342 self._controlCaretNavigationCheckButton = \ 343 Gtk.CheckButton.new_with_mnemonic(label) 344 self._controlCaretNavigationCheckButton.set_active(value) 345 generalGrid.attach(self._controlCaretNavigationCheckButton, 0, 0, 1, 1) 346 347 label = guilabels.AUTO_FOCUS_MODE_CARET_NAV 348 value = _settingsManager.getSetting('caretNavTriggersFocusMode') 349 self._autoFocusModeCaretNavCheckButton = Gtk.CheckButton.new_with_mnemonic(label) 350 self._autoFocusModeCaretNavCheckButton.set_active(value) 351 generalGrid.attach(self._autoFocusModeCaretNavCheckButton, 0, 1, 1, 1) 352 353 label = guilabels.USE_STRUCTURAL_NAVIGATION 354 value = self.structuralNavigation.enabled 355 self._structuralNavigationCheckButton = \ 356 Gtk.CheckButton.new_with_mnemonic(label) 357 self._structuralNavigationCheckButton.set_active(value) 358 generalGrid.attach(self._structuralNavigationCheckButton, 0, 2, 1, 1) 359 360 label = guilabels.AUTO_FOCUS_MODE_STRUCT_NAV 361 value = _settingsManager.getSetting('structNavTriggersFocusMode') 362 self._autoFocusModeStructNavCheckButton = Gtk.CheckButton.new_with_mnemonic(label) 363 self._autoFocusModeStructNavCheckButton.set_active(value) 364 generalGrid.attach(self._autoFocusModeStructNavCheckButton, 0, 3, 1, 1) 365 366 label = guilabels.AUTO_FOCUS_MODE_NATIVE_NAV 367 value = _settingsManager.getSetting('nativeNavTriggersFocusMode') 368 self._autoFocusModeNativeNavCheckButton = Gtk.CheckButton.new_with_mnemonic(label) 369 self._autoFocusModeNativeNavCheckButton.set_active(value) 370 generalGrid.attach(self._autoFocusModeNativeNavCheckButton, 0, 4, 1, 1) 371 372 label = guilabels.READ_PAGE_UPON_LOAD 373 value = _settingsManager.getSetting('sayAllOnLoad') 374 self._sayAllOnLoadCheckButton = Gtk.CheckButton.new_with_mnemonic(label) 375 self._sayAllOnLoadCheckButton.set_active(value) 376 generalGrid.attach(self._sayAllOnLoadCheckButton, 0, 5, 1, 1) 377 378 label = guilabels.PAGE_SUMMARY_UPON_LOAD 379 value = _settingsManager.getSetting('pageSummaryOnLoad') 380 self._pageSummaryOnLoadCheckButton = Gtk.CheckButton.new_with_mnemonic(label) 381 self._pageSummaryOnLoadCheckButton.set_active(value) 382 generalGrid.attach(self._pageSummaryOnLoadCheckButton, 0, 6, 1, 1) 383 384 label = guilabels.CONTENT_LAYOUT_MODE 385 value = _settingsManager.getSetting('layoutMode') 386 self._layoutModeCheckButton = Gtk.CheckButton.new_with_mnemonic(label) 387 self._layoutModeCheckButton.set_active(value) 388 generalGrid.attach(self._layoutModeCheckButton, 0, 7, 1, 1) 389 390 tableFrame = Gtk.Frame() 391 grid.attach(tableFrame, 0, 1, 1, 1) 392 393 label = Gtk.Label(label="<b>%s</b>" % guilabels.TABLE_NAVIGATION) 394 label.set_use_markup(True) 395 tableFrame.set_label_widget(label) 396 397 tableAlignment = Gtk.Alignment.new(0.5, 0.5, 1, 1) 398 tableAlignment.set_padding(0, 0, 12, 0) 399 tableFrame.add(tableAlignment) 400 tableGrid = Gtk.Grid() 401 tableAlignment.add(tableGrid) 402 403 label = guilabels.TABLE_SPEAK_CELL_COORDINATES 404 value = _settingsManager.getSetting('speakCellCoordinates') 405 self._speakCellCoordinatesCheckButton = \ 406 Gtk.CheckButton.new_with_mnemonic(label) 407 self._speakCellCoordinatesCheckButton.set_active(value) 408 tableGrid.attach(self._speakCellCoordinatesCheckButton, 0, 0, 1, 1) 409 410 label = guilabels.TABLE_SPEAK_CELL_SPANS 411 value = _settingsManager.getSetting('speakCellSpan') 412 self._speakCellSpanCheckButton = \ 413 Gtk.CheckButton.new_with_mnemonic(label) 414 self._speakCellSpanCheckButton.set_active(value) 415 tableGrid.attach(self._speakCellSpanCheckButton, 0, 1, 1, 1) 416 417 label = guilabels.TABLE_ANNOUNCE_CELL_HEADER 418 value = _settingsManager.getSetting('speakCellHeaders') 419 self._speakCellHeadersCheckButton = \ 420 Gtk.CheckButton.new_with_mnemonic(label) 421 self._speakCellHeadersCheckButton.set_active(value) 422 tableGrid.attach(self._speakCellHeadersCheckButton, 0, 2, 1, 1) 423 424 label = guilabels.TABLE_SKIP_BLANK_CELLS 425 value = _settingsManager.getSetting('skipBlankCells') 426 self._skipBlankCellsCheckButton = \ 427 Gtk.CheckButton.new_with_mnemonic(label) 428 self._skipBlankCellsCheckButton.set_active(value) 429 tableGrid.attach(self._skipBlankCellsCheckButton, 0, 3, 1, 1) 430 431 findFrame = Gtk.Frame() 432 grid.attach(findFrame, 0, 2, 1, 1) 433 434 label = Gtk.Label(label="<b>%s</b>" % guilabels.FIND_OPTIONS) 435 label.set_use_markup(True) 436 findFrame.set_label_widget(label) 437 438 findAlignment = Gtk.Alignment.new(0.5, 0.5, 1, 1) 439 findAlignment.set_padding(0, 0, 12, 0) 440 findFrame.add(findAlignment) 441 findGrid = Gtk.Grid() 442 findAlignment.add(findGrid) 443 444 verbosity = _settingsManager.getSetting('findResultsVerbosity') 445 446 label = guilabels.FIND_SPEAK_RESULTS 447 value = verbosity != settings.FIND_SPEAK_NONE 448 self._speakResultsDuringFindCheckButton = \ 449 Gtk.CheckButton.new_with_mnemonic(label) 450 self._speakResultsDuringFindCheckButton.set_active(value) 451 findGrid.attach(self._speakResultsDuringFindCheckButton, 0, 0, 1, 1) 452 453 label = guilabels.FIND_ONLY_SPEAK_CHANGED_LINES 454 value = verbosity == settings.FIND_SPEAK_IF_LINE_CHANGED 455 self._changedLinesOnlyCheckButton = \ 456 Gtk.CheckButton.new_with_mnemonic(label) 457 self._changedLinesOnlyCheckButton.set_active(value) 458 findGrid.attach(self._changedLinesOnlyCheckButton, 0, 1, 1, 1) 459 460 hgrid = Gtk.Grid() 461 findGrid.attach(hgrid, 0, 2, 1, 1) 462 463 self._minimumFindLengthLabel = \ 464 Gtk.Label(label=guilabels.FIND_MINIMUM_MATCH_LENGTH) 465 self._minimumFindLengthLabel.set_alignment(0, 0.5) 466 hgrid.attach(self._minimumFindLengthLabel, 0, 0, 1, 1) 467 468 self._minimumFindLengthAdjustment = \ 469 Gtk.Adjustment(_settingsManager.getSetting( 470 'findResultsMinimumLength'), 0, 20, 1) 471 self._minimumFindLengthSpinButton = Gtk.SpinButton() 472 self._minimumFindLengthSpinButton.set_adjustment( 473 self._minimumFindLengthAdjustment) 474 hgrid.attach(self._minimumFindLengthSpinButton, 1, 0, 1, 1) 475 self._minimumFindLengthLabel.set_mnemonic_widget( 476 self._minimumFindLengthSpinButton) 477 478 grid.show_all() 479 return grid 480 481 def getPreferencesFromGUI(self): 482 """Returns a dictionary with the app-specific preferences.""" 483 484 if not self._speakResultsDuringFindCheckButton.get_active(): 485 verbosity = settings.FIND_SPEAK_NONE 486 elif self._changedLinesOnlyCheckButton.get_active(): 487 verbosity = settings.FIND_SPEAK_IF_LINE_CHANGED 488 else: 489 verbosity = settings.FIND_SPEAK_ALL 490 491 return { 492 'findResultsVerbosity': verbosity, 493 'findResultsMinimumLength': self._minimumFindLengthSpinButton.get_value(), 494 'sayAllOnLoad': self._sayAllOnLoadCheckButton.get_active(), 495 'pageSummaryOnLoad': self._pageSummaryOnLoadCheckButton.get_active(), 496 'structuralNavigationEnabled': self._structuralNavigationCheckButton.get_active(), 497 'structNavTriggersFocusMode': self._autoFocusModeStructNavCheckButton.get_active(), 498 'caretNavigationEnabled': self._controlCaretNavigationCheckButton.get_active(), 499 'caretNavTriggersFocusMode': self._autoFocusModeCaretNavCheckButton.get_active(), 500 'nativeNavTriggersFocusMode': self._autoFocusModeNativeNavCheckButton.get_active(), 501 'speakCellCoordinates': self._speakCellCoordinatesCheckButton.get_active(), 502 'layoutMode': self._layoutModeCheckButton.get_active(), 503 'speakCellSpan': self._speakCellSpanCheckButton.get_active(), 504 'speakCellHeaders': self._speakCellHeadersCheckButton.get_active(), 505 'skipBlankCells': self._skipBlankCellsCheckButton.get_active() 506 } 507 508 def skipObjectEvent(self, event): 509 """Returns True if this object event should be skipped.""" 510 511 if event.type.startswith('object:state-changed:focused') \ 512 and event.detail1: 513 if event.source.getRole() == pyatspi.ROLE_LINK: 514 return False 515 516 if event.type.startswith('object:children-changed'): 517 try: 518 role = event.any_data.getRole() 519 except: 520 pass 521 else: 522 if role == pyatspi.ROLE_DIALOG: 523 return False 524 525 return super().skipObjectEvent(event) 526 527 def presentationInterrupt(self): 528 super().presentationInterrupt() 529 msg = "WEB: Flushing live region messages" 530 debug.println(debug.LEVEL_INFO, msg, True) 531 self.liveRegionManager.flushMessages() 532 533 def consumesKeyboardEvent(self, keyboardEvent): 534 """Returns True if the script will consume this keyboard event.""" 535 536 # We need to do this here. Orca caret and structural navigation 537 # often result in the user being repositioned without our getting 538 # a corresponding AT-SPI event. Without an AT-SPI event, script.py 539 # won't know to dump the generator cache. See bgo#618827. 540 self.generatorCache = {} 541 542 self._lastMouseButtonContext = None, -1 543 544 handler = self.keyBindings.getInputHandler(keyboardEvent) 545 if handler and self.caretNavigation.handles_navigation(handler): 546 consumes = self.useCaretNavigationModel(keyboardEvent) 547 self._lastCommandWasCaretNav = consumes 548 self._lastCommandWasStructNav = False 549 self._lastCommandWasMouseButton = False 550 return consumes 551 552 if handler and handler.function in self.structuralNavigation.functions: 553 consumes = self.useStructuralNavigationModel() 554 self._lastCommandWasCaretNav = False 555 self._lastCommandWasStructNav = consumes 556 self._lastCommandWasMouseButton = False 557 return consumes 558 559 if handler and handler.function in self.liveRegionManager.functions: 560 # This is temporary. 561 consumes = self.useStructuralNavigationModel() 562 self._lastCommandWasCaretNav = False 563 self._lastCommandWasStructNav = consumes 564 self._lastCommandWasMouseButton = False 565 return consumes 566 567 if not keyboardEvent.isModifierKey(): 568 self._lastCommandWasCaretNav = False 569 self._lastCommandWasStructNav = False 570 self._lastCommandWasMouseButton = False 571 572 return super().consumesKeyboardEvent(keyboardEvent) 573 574 def getEnabledKeyBindings(self): 575 all = super().getEnabledKeyBindings() 576 ret = [] 577 for b in all: 578 if b.handler and self.caretNavigation.handles_navigation(b.handler): 579 if self.useCaretNavigationModel(None): 580 ret.append(b) 581 elif b.handler and b.handler.function in self.structuralNavigation.functions: 582 if self.useStructuralNavigationModel(): 583 ret.append(b) 584 elif b.handler and b.handler.function in self.liveRegionManager.functions: 585 # This is temporary. 586 if self.useStructuralNavigationModel(): 587 ret.append(b) 588 else: 589 ret.append(b) 590 return ret 591 592 def consumesBrailleEvent(self, brailleEvent): 593 """Returns True if the script will consume this braille event.""" 594 595 self._lastCommandWasCaretNav = False 596 self._lastCommandWasStructNav = False 597 self._lastCommandWasMouseButton = False 598 return super().consumesBrailleEvent(brailleEvent) 599 600 # TODO - JD: This needs to be moved out of the scripts. 601 def textLines(self, obj, offset=None): 602 """Creates a generator that can be used to iterate document content.""" 603 604 if not self.utilities.inDocumentContent(): 605 msg = "WEB: textLines called for non-document content %s" % obj 606 debug.println(debug.LEVEL_INFO, msg, True) 607 super().textLines(obj, offset) 608 return 609 610 self._sayAllIsInterrupted = False 611 612 sayAllStyle = _settingsManager.getSetting('sayAllStyle') 613 sayAllBySentence = sayAllStyle == settings.SAYALL_STYLE_SENTENCE 614 if offset is None: 615 obj, characterOffset = self.utilities.getCaretContext() 616 else: 617 characterOffset = offset 618 priorObj, priorOffset = self.utilities.getPriorContext() 619 620 # TODO - JD: This is sad, but it's better than the old, broken 621 # clumpUtterances(). We really need to fix the speechservers' 622 # SayAll support. In the meantime, the generators should be 623 # providing one ACSS per string. 624 def _parseUtterances(utterances): 625 elements, voices = [], [] 626 for u in utterances: 627 if isinstance(u, list): 628 e, v = _parseUtterances(u) 629 elements.extend(e) 630 voices.extend(v) 631 elif isinstance(u, str): 632 elements.append(u) 633 elif isinstance(u, ACSS): 634 voices.append(u) 635 return elements, voices 636 637 self._inSayAll = True 638 done = False 639 while not done: 640 if sayAllBySentence: 641 contents = self.utilities.getSentenceContentsAtOffset(obj, characterOffset) 642 else: 643 contents = self.utilities.getLineContentsAtOffset(obj, characterOffset) 644 self._sayAllContents = contents 645 for i, content in enumerate(contents): 646 obj, startOffset, endOffset, text = content 647 msg = "WEB SAY ALL CONTENT: %i. %s '%s' (%i-%i)" % (i, obj, text, startOffset, endOffset) 648 debug.println(debug.LEVEL_INFO, msg, True) 649 650 if self.utilities.isInferredLabelForContents(content, contents): 651 continue 652 653 if startOffset == endOffset: 654 continue 655 656 if self.utilities.isLabellingInteractiveElement(obj): 657 continue 658 659 if self.utilities.isLinkAncestorOfImageInContents(obj, contents): 660 continue 661 662 utterances = self.speechGenerator.generateContents( 663 [content], eliminatePauses=True, priorObj=priorObj) 664 priorObj = obj 665 666 elements, voices = _parseUtterances(utterances) 667 if len(elements) != len(voices): 668 continue 669 670 for i, element in enumerate(elements): 671 context = speechserver.SayAllContext( 672 obj, element, startOffset, endOffset) 673 msg = "WEB %s" % context 674 debug.println(debug.LEVEL_INFO, msg, True) 675 self._sayAllContexts.append(context) 676 eventsynthesizer.scrollIntoView(obj, startOffset, endOffset) 677 yield [context, voices[i]] 678 679 lastObj, lastOffset = contents[-1][0], contents[-1][2] 680 obj, characterOffset = self.utilities.findNextCaretInOrder(lastObj, lastOffset - 1) 681 if obj == lastObj and characterOffset <= lastOffset: 682 obj, characterOffset = self.utilities.findNextCaretInOrder(lastObj, lastOffset) 683 if obj == lastObj and characterOffset <= lastOffset: 684 msg = "WEB: Cycle within object detected in textLines. Last: %s, %i Next: %s, %i" \ 685 % (lastObj, lastOffset, obj, characterOffset) 686 debug.println(debug.LEVEL_INFO, msg, True) 687 break 688 689 done = obj is None 690 691 self._inSayAll = False 692 self._sayAllContents = [] 693 self._sayAllContexts = [] 694 695 msg = "WEB: textLines complete. Verifying SayAll status" 696 debug.println(debug.LEVEL_INFO, msg, True) 697 self.inSayAll() 698 699 def presentFindResults(self, obj, offset): 700 """Updates the context and presents the find results if appropriate.""" 701 702 text = self.utilities.queryNonEmptyText(obj) 703 if not (text and text.getNSelections() > 0): 704 return 705 706 document = self.utilities.getDocumentForObject(obj) 707 if not document: 708 return 709 710 context = self.utilities.getCaretContext(documentFrame=document) 711 start, end = text.getSelection(0) 712 offset = max(offset, start) 713 self.utilities.setCaretContext(obj, offset, documentFrame=document) 714 if end - start < _settingsManager.getSetting('findResultsMinimumLength'): 715 return 716 717 verbosity = _settingsManager.getSetting('findResultsVerbosity') 718 if verbosity == settings.FIND_SPEAK_NONE: 719 return 720 721 if self._madeFindAnnouncement \ 722 and verbosity == settings.FIND_SPEAK_IF_LINE_CHANGED \ 723 and self.utilities.contextsAreOnSameLine(context, (obj, offset)): 724 return 725 726 contents = self.utilities.getLineContentsAtOffset(obj, offset) 727 self.speakContents(contents) 728 self.updateBraille(obj) 729 730 resultsCount = self.utilities.getFindResultsCount() 731 if resultsCount: 732 self.presentMessage(resultsCount) 733 734 self._madeFindAnnouncement = True 735 736 def sayAll(self, inputEvent, obj=None, offset=None): 737 """Speaks the contents of the document beginning with the present 738 location. Overridden in this script because the sayAll could have 739 been started on an object without text (such as an image). 740 """ 741 742 if not self.utilities.inDocumentContent(): 743 msg = "WEB: SayAll called for non-document content %s" % obj 744 debug.println(debug.LEVEL_INFO, msg, True) 745 return super().sayAll(inputEvent, obj, offset) 746 747 obj = obj or orca_state.locusOfFocus 748 msg = "WEB: SayAll called for document content %s" % obj 749 debug.println(debug.LEVEL_INFO, msg, True) 750 speech.sayAll(self.textLines(obj, offset), self.__sayAllProgressCallback) 751 return True 752 753 def _rewindSayAll(self, context, minCharCount=10): 754 if not self.utilities.inDocumentContent(): 755 return super()._rewindSayAll(context, minCharCount) 756 757 if not _settingsManager.getSetting('rewindAndFastForwardInSayAll'): 758 return False 759 760 try: 761 obj, start, end, string = self._sayAllContents[0] 762 except IndexError: 763 return False 764 765 orca.setLocusOfFocus(None, obj, notifyScript=False) 766 self.utilities.setCaretContext(obj, start) 767 768 prevObj, prevOffset = self.utilities.findPreviousCaretInOrder(obj, start) 769 self.sayAll(None, prevObj, prevOffset) 770 return True 771 772 def _fastForwardSayAll(self, context): 773 if not self.utilities.inDocumentContent(): 774 return super()._fastForwardSayAll(context) 775 776 if not _settingsManager.getSetting('rewindAndFastForwardInSayAll'): 777 return False 778 779 try: 780 obj, start, end, string = self._sayAllContents[-1] 781 except IndexError: 782 return False 783 784 orca.setLocusOfFocus(None, obj, notifyScript=False) 785 self.utilities.setCaretContext(obj, end) 786 787 nextObj, nextOffset = self.utilities.findNextCaretInOrder(obj, end) 788 self.sayAll(None, nextObj, nextOffset) 789 return True 790 791 def __sayAllProgressCallback(self, context, progressType): 792 if not self.utilities.inDocumentContent(): 793 super().__sayAllProgressCallback(context, progressType) 794 return 795 796 if progressType == speechserver.SayAllContext.INTERRUPTED: 797 if isinstance(orca_state.lastInputEvent, input_event.KeyboardEvent): 798 self._sayAllIsInterrupted = True 799 lastKey = orca_state.lastInputEvent.event_string 800 if lastKey == "Down" and self._fastForwardSayAll(context): 801 return 802 elif lastKey == "Up" and self._rewindSayAll(context): 803 return 804 elif not self._lastCommandWasStructNav: 805 orca.emitRegionChanged(context.obj, context.currentOffset) 806 self.utilities.setCaretPosition(context.obj, context.currentOffset) 807 self.updateBraille(context.obj) 808 809 self._inSayAll = False 810 self._sayAllContents = [] 811 self._sayAllContexts = [] 812 return 813 814 orca.setLocusOfFocus(None, context.obj, notifyScript=False) 815 orca.emitRegionChanged( 816 context.obj, context.currentOffset, context.currentEndOffset, orca.SAY_ALL) 817 self.utilities.setCaretContext(context.obj, context.currentOffset) 818 819 def inFocusMode(self): 820 """ Returns True if we're in focus mode.""" 821 822 return self._inFocusMode 823 824 def focusModeIsSticky(self): 825 """Returns True if we're in 'sticky' focus mode.""" 826 827 return self._focusModeIsSticky 828 829 def browseModeIsSticky(self): 830 """Returns True if we're in 'sticky' browse mode.""" 831 832 return self._browseModeIsSticky 833 834 def useFocusMode(self, obj, prevObj=None): 835 """Returns True if we should use focus mode in obj.""" 836 837 if self._focusModeIsSticky: 838 msg = "WEB: Using focus mode because focus mode is sticky" 839 debug.println(debug.LEVEL_INFO, msg, True) 840 return True 841 842 if self._browseModeIsSticky: 843 msg = "WEB: Not using focus mode because browse mode is sticky" 844 debug.println(debug.LEVEL_INFO, msg, True) 845 return False 846 847 if self.inSayAll(): 848 msg = "WEB: Not using focus mode because we're in SayAll." 849 debug.println(debug.LEVEL_INFO, msg, True) 850 return False 851 852 if not _settingsManager.getSetting('structNavTriggersFocusMode') \ 853 and self._lastCommandWasStructNav: 854 msg = "WEB: Not using focus mode due to struct nav settings" 855 debug.println(debug.LEVEL_INFO, msg, True) 856 return False 857 858 if prevObj and self.utilities.isDead(prevObj): 859 prevObj = None 860 861 if not _settingsManager.getSetting('caretNavTriggersFocusMode') \ 862 and self._lastCommandWasCaretNav \ 863 and not self.utilities.isNavigableToolTipDescendant(prevObj): 864 msg = "WEB: Not using focus mode due to caret nav settings" 865 debug.println(debug.LEVEL_INFO, msg, True) 866 return False 867 868 if not _settingsManager.getSetting('nativeNavTriggersFocusMode') \ 869 and not (self._lastCommandWasStructNav or self._lastCommandWasCaretNav): 870 msg = "WEB: Not changing focus/browse mode due to native nav settings" 871 debug.println(debug.LEVEL_INFO, msg, True) 872 return self._inFocusMode 873 874 if self.utilities.isFocusModeWidget(obj): 875 msg = "WEB: Using focus mode because %s is a focus mode widget" % obj 876 debug.println(debug.LEVEL_INFO, msg, True) 877 return True 878 879 doNotToggle = [pyatspi.ROLE_LINK, pyatspi.ROLE_RADIO_BUTTON] 880 if self._inFocusMode and obj and obj.getRole() in doNotToggle \ 881 and self.utilities.lastInputEventWasUnmodifiedArrow(): 882 msg = "WEB: Staying in focus mode due to arrowing in role of %s" % obj 883 debug.println(debug.LEVEL_INFO, msg, True) 884 return True 885 886 if self._inFocusMode and self.utilities.isWebAppDescendant(obj): 887 if self.utilities.forceBrowseModeForWebAppDescendant(obj): 888 msg = "WEB: Forcing browse mode for web app descendant %s" % obj 889 debug.println(debug.LEVEL_INFO, msg, True) 890 return False 891 892 msg = "WEB: Staying in focus mode because we're inside a web application" 893 debug.println(debug.LEVEL_INFO, msg, True) 894 return True 895 896 msg = "WEB: Not using focus mode for %s due to lack of cause" % obj 897 debug.println(debug.LEVEL_INFO, msg, True) 898 return False 899 900 def speakContents(self, contents, **args): 901 """Speaks the specified contents.""" 902 903 utterances = self.speechGenerator.generateContents(contents, **args) 904 speech.speak(utterances) 905 906 def sayCharacter(self, obj): 907 """Speaks the character at the current caret position.""" 908 909 if not self._lastCommandWasCaretNav \ 910 and not self.utilities.isContentEditableWithEmbeddedObjects(obj): 911 super().sayCharacter(obj) 912 return 913 914 document = self.utilities.getTopLevelDocumentForObject(obj) 915 obj, offset = self.utilities.getCaretContext(documentFrame=document) 916 if not obj: 917 return 918 919 contents = None 920 if self.utilities.treatAsEndOfLine(obj, offset) and "Text" in pyatspi.listInterfaces(obj): 921 char = obj.queryText().getText(offset, offset + 1) 922 if char == self.EMBEDDED_OBJECT_CHARACTER: 923 char = "" 924 contents = [[obj, offset, offset + 1, char]] 925 else: 926 contents = self.utilities.getCharacterContentsAtOffset(obj, offset) 927 928 if not contents: 929 return 930 931 obj, start, end, string = contents[0] 932 if start > 0: 933 string = string or "\n" 934 935 if string: 936 self.speakMisspelledIndicator(obj, start) 937 self.speakCharacter(string) 938 else: 939 self.speakContents(contents) 940 941 self.pointOfReference["lastTextUnitSpoken"] = "char" 942 943 def sayWord(self, obj): 944 """Speaks the word at the current caret position.""" 945 946 isEditable = self.utilities.isContentEditableWithEmbeddedObjects(obj) 947 if not self._lastCommandWasCaretNav and not isEditable: 948 super().sayWord(obj) 949 return 950 951 document = self.utilities.getTopLevelDocumentForObject(obj) 952 obj, offset = self.utilities.getCaretContext(documentFrame=document) 953 keyString, mods = self.utilities.lastKeyAndModifiers() 954 if keyString == "Right": 955 offset -= 1 956 957 wordContents = self.utilities.getWordContentsAtOffset(obj, offset, useCache=True) 958 textObj, startOffset, endOffset, word = wordContents[0] 959 self.speakMisspelledIndicator(textObj, startOffset) 960 self.speakContents(wordContents) 961 self.pointOfReference["lastTextUnitSpoken"] = "word" 962 963 def sayLine(self, obj): 964 """Speaks the line at the current caret position.""" 965 966 isEditable = self.utilities.isContentEditableWithEmbeddedObjects(obj) 967 if not (self._lastCommandWasCaretNav or self._lastCommandWasStructNav) and not isEditable: 968 super().sayLine(obj) 969 return 970 971 document = self.utilities.getTopLevelDocumentForObject(obj) 972 priorObj = None 973 if self._lastCommandWasCaretNav or isEditable: 974 priorObj, priorOffset = self.utilities.getPriorContext(documentFrame=document) 975 976 obj, offset = self.utilities.getCaretContext(documentFrame=document) 977 contents = self.utilities.getLineContentsAtOffset(obj, offset, useCache=True) 978 self.speakContents(contents, priorObj=priorObj) 979 self.pointOfReference["lastTextUnitSpoken"] = "line" 980 981 def presentObject(self, obj, **args): 982 if not self.utilities.inDocumentContent(obj): 983 super().presentObject(obj, **args) 984 return 985 986 if obj.getRole() == pyatspi.ROLE_STATUS_BAR: 987 super().presentObject(obj, **args) 988 return 989 990 priorObj = args.get("priorObj") 991 if self._lastCommandWasCaretNav or args.get("includeContext"): 992 priorObj, priorOffset = self.utilities.getPriorContext() 993 args["priorObj"] = priorObj 994 995 if obj.getRole() == pyatspi.ROLE_ENTRY: 996 utterances = self.speechGenerator.generateSpeech(obj, **args) 997 speech.speak(utterances) 998 self.updateBraille(obj) 999 return 1000 1001 # We shouldn't use cache in this method, because if the last thing we presented 1002 # included this object and offset (e.g. a Say All or Mouse Review), we're in 1003 # danger of presented irrelevant context. 1004 useCache = False 1005 offset = args.get("offset", 0) 1006 contents = self.utilities.getObjectContentsAtOffset(obj, offset, useCache) 1007 self.displayContents(contents) 1008 self.speakContents(contents, **args) 1009 1010 def updateBrailleForNewCaretPosition(self, obj): 1011 """Try to reposition the cursor without having to do a full update.""" 1012 1013 text = self.utilities.queryNonEmptyText(obj) 1014 if text and self.EMBEDDED_OBJECT_CHARACTER in text.getText(0, -1): 1015 self.updateBraille(obj) 1016 return 1017 1018 super().updateBrailleForNewCaretPosition(obj) 1019 1020 def updateBraille(self, obj, **args): 1021 """Updates the braille display to show the given object.""" 1022 1023 if not _settingsManager.getSetting('enableBraille') \ 1024 and not _settingsManager.getSetting('enableBrailleMonitor'): 1025 debug.println(debug.LEVEL_INFO, "BRAILLE: disabled", True) 1026 return 1027 1028 document = args.get("documentFrame", self.utilities.getTopLevelDocumentForObject(obj)) 1029 if not document: 1030 msg = "WEB: updating braille for non-document object %s" % obj 1031 debug.println(debug.LEVEL_INFO, msg, True) 1032 super().updateBraille(obj, **args) 1033 return 1034 1035 isContentEditable = self.utilities.isContentEditableWithEmbeddedObjects(obj) 1036 1037 if not self._lastCommandWasCaretNav \ 1038 and not self._lastCommandWasStructNav \ 1039 and not isContentEditable \ 1040 and not self.utilities.isPlainText() \ 1041 and not self.utilities.lastInputEventWasCaretNavWithSelection(): 1042 msg = "WEB: updating braille for unhandled navigation type %s" % obj 1043 debug.println(debug.LEVEL_INFO, msg, True) 1044 super().updateBraille(obj, **args) 1045 return 1046 1047 obj, offset = self.utilities.getCaretContext(documentFrame=document, getZombieReplicant=True) 1048 if offset > 0 and isContentEditable: 1049 text = self.utilities.queryNonEmptyText(obj) 1050 if text: 1051 offset = min(offset, text.characterCount) 1052 1053 contents = self.utilities.getLineContentsAtOffset(obj, offset) 1054 self.displayContents(contents, documentFrame=document) 1055 1056 def displayContents(self, contents, **args): 1057 """Displays contents in braille.""" 1058 1059 if not _settingsManager.getSetting('enableBraille') \ 1060 and not _settingsManager.getSetting('enableBrailleMonitor'): 1061 debug.println(debug.LEVEL_INFO, "BRAILLE: disabled", True) 1062 return 1063 1064 line = self.getNewBrailleLine(clearBraille=True, addLine=True) 1065 document = args.get("documentFrame") 1066 contents = self.brailleGenerator.generateContents(contents, documentFrame=document) 1067 if not contents: 1068 return 1069 1070 regions, focusedRegion = contents 1071 for region in regions: 1072 self.addBrailleRegionsToLine(region, line) 1073 1074 if line.regions: 1075 line.regions[-1].string = line.regions[-1].string.rstrip(" ") 1076 1077 self.setBrailleFocus(focusedRegion, getLinkMask=False) 1078 self.refreshBraille(panToCursor=True, getLinkMask=False) 1079 1080 def panBrailleLeft(self, inputEvent=None, panAmount=0): 1081 """Pans braille to the left.""" 1082 1083 if self.flatReviewContext \ 1084 or not self.utilities.inDocumentContent() \ 1085 or not self.isBrailleBeginningShowing(): 1086 super().panBrailleLeft(inputEvent, panAmount) 1087 return 1088 1089 contents = self.utilities.getPreviousLineContents() 1090 if not contents: 1091 return 1092 1093 obj, start, end, string = contents[0] 1094 self.utilities.setCaretPosition(obj, start) 1095 self.updateBraille(obj) 1096 1097 # Hack: When panning to the left in a document, we want to start at 1098 # the right/bottom of each new object. For now, we'll pan there. 1099 # When time permits, we'll give our braille code some smarts. 1100 while self.panBrailleInDirection(panToLeft=False): 1101 pass 1102 1103 self.refreshBraille(False) 1104 return True 1105 1106 def panBrailleRight(self, inputEvent=None, panAmount=0): 1107 """Pans braille to the right.""" 1108 1109 if self.flatReviewContext \ 1110 or not self.utilities.inDocumentContent() \ 1111 or not self.isBrailleEndShowing(): 1112 super().panBrailleRight(inputEvent, panAmount) 1113 return 1114 1115 contents = self.utilities.getNextLineContents() 1116 if not contents: 1117 return 1118 1119 obj, start, end, string = contents[0] 1120 self.utilities.setCaretPosition(obj, start) 1121 self.updateBraille(obj) 1122 1123 # Hack: When panning to the right in a document, we want to start at 1124 # the left/top of each new object. For now, we'll pan there. When time 1125 # permits, we'll give our braille code some smarts. 1126 while self.panBrailleInDirection(panToLeft=True): 1127 pass 1128 1129 self.refreshBraille(False) 1130 return True 1131 1132 def useCaretNavigationModel(self, keyboardEvent): 1133 """Returns True if caret navigation should be used.""" 1134 1135 if not _settingsManager.getSetting('caretNavigationEnabled') \ 1136 or self._inFocusMode: 1137 return False 1138 1139 if not self.utilities.inDocumentContent(): 1140 return False 1141 1142 if keyboardEvent and keyboardEvent.modifiers & keybindings.SHIFT_MODIFIER_MASK: 1143 return False 1144 1145 return True 1146 1147 def useStructuralNavigationModel(self): 1148 """Returns True if structural navigation should be used.""" 1149 1150 if not self.structuralNavigation.enabled or self._inFocusMode: 1151 return False 1152 1153 if not self.utilities.inDocumentContent(): 1154 return False 1155 1156 return True 1157 1158 def getTextLineAtCaret(self, obj, offset=None, startOffset=None, endOffset=None): 1159 """To-be-removed. Returns the string, caretOffset, startOffset.""" 1160 1161 if self._inFocusMode or not self.utilities.inDocumentContent(obj) \ 1162 or self.utilities.isFocusModeWidget(obj): 1163 return super().getTextLineAtCaret(obj, offset, startOffset, endOffset) 1164 1165 text = self.utilities.queryNonEmptyText(obj) 1166 if offset is None: 1167 try: 1168 offset = max(0, text.caretOffset) 1169 except: 1170 offset = 0 1171 1172 if text and startOffset is not None and endOffset is not None: 1173 return text.getText(startOffset, endOffset), offset, startOffset 1174 1175 contextObj, contextOffset = self.utilities.getCaretContext(documentFrame=None) 1176 if contextObj == obj: 1177 caretOffset = contextOffset 1178 else: 1179 caretOffset = offset 1180 1181 contents = self.utilities.getLineContentsAtOffset(obj, offset) 1182 contents = list(filter(lambda x: x[0] == obj, contents)) 1183 if len(contents) == 1: 1184 index = 0 1185 else: 1186 index = self.utilities.findObjectInContents(obj, offset, contents) 1187 1188 if index > -1: 1189 candidate, startOffset, endOffset, string = contents[index] 1190 if not self.EMBEDDED_OBJECT_CHARACTER in string: 1191 return string, caretOffset, startOffset 1192 1193 return "", 0, 0 1194 1195 def moveToMouseOver(self, inputEvent): 1196 """Moves the context to/from the mouseover which has just appeared.""" 1197 1198 if not self._lastMouseOverObject: 1199 self.presentMessage(messages.MOUSE_OVER_NOT_FOUND) 1200 return 1201 1202 if self._inMouseOverObject: 1203 x, y = self.oldMouseCoordinates 1204 eventsynthesizer.routeToPoint(x, y) 1205 self.restorePreMouseOverContext() 1206 return 1207 1208 obj = self._lastMouseOverObject 1209 obj, offset = self.utilities.findFirstCaretContext(obj, 0) 1210 if not obj: 1211 return 1212 1213 if obj.getState().contains(pyatspi.STATE_FOCUSABLE): 1214 obj.queryComponent().grabFocus() 1215 1216 contents = self.utilities.getObjectContentsAtOffset(obj, offset) 1217 self.utilities.setCaretPosition(obj, offset) 1218 self.speakContents(contents) 1219 self.updateBraille(obj) 1220 self._inMouseOverObject = True 1221 1222 def restorePreMouseOverContext(self): 1223 """Cleans things up after a mouse-over object has been hidden.""" 1224 1225 obj, offset = self._preMouseOverContext 1226 self.utilities.setCaretPosition(obj, offset) 1227 self.speakContents(self.utilities.getObjectContentsAtOffset(obj, offset)) 1228 self.updateBraille(obj) 1229 self._inMouseOverObject = False 1230 self._lastMouseOverObject = None 1231 1232 def enableStickyBrowseMode(self, inputEvent, forceMessage=False): 1233 if not self._browseModeIsSticky or forceMessage: 1234 self.presentMessage(messages.MODE_BROWSE_IS_STICKY) 1235 1236 self._inFocusMode = False 1237 self._focusModeIsSticky = False 1238 self._browseModeIsSticky = True 1239 self.refreshKeyGrabs() 1240 1241 def enableStickyFocusMode(self, inputEvent, forceMessage=False): 1242 if not self._focusModeIsSticky or forceMessage: 1243 self.presentMessage(messages.MODE_FOCUS_IS_STICKY) 1244 1245 self._inFocusMode = True 1246 self._focusModeIsSticky = True 1247 self._browseModeIsSticky = False 1248 self.refreshKeyGrabs() 1249 1250 def toggleLayoutMode(self, inputEvent): 1251 layoutMode = not _settingsManager.getSetting('layoutMode') 1252 if layoutMode: 1253 self.presentMessage(messages.MODE_LAYOUT) 1254 else: 1255 self.presentMessage(messages.MODE_OBJECT) 1256 _settingsManager.setSetting('layoutMode', layoutMode) 1257 1258 def togglePresentationMode(self, inputEvent, documentFrame=None): 1259 [obj, characterOffset] = self.utilities.getCaretContext(documentFrame) 1260 if self._inFocusMode: 1261 try: 1262 parentRole = obj.parent.getRole() 1263 except: 1264 parentRole = None 1265 if parentRole == pyatspi.ROLE_LIST_BOX: 1266 self.utilities.setCaretContext(obj.parent, -1) 1267 elif parentRole == pyatspi.ROLE_MENU: 1268 self.utilities.setCaretContext(obj.parent.parent, -1) 1269 if not self._loadingDocumentContent: 1270 self.presentMessage(messages.MODE_BROWSE) 1271 else: 1272 if not self.utilities.grabFocusWhenSettingCaret(obj) \ 1273 and (self._lastCommandWasCaretNav \ 1274 or self._lastCommandWasStructNav \ 1275 or inputEvent): 1276 self.utilities.grabFocus(obj) 1277 1278 self.presentMessage(messages.MODE_FOCUS) 1279 self._inFocusMode = not self._inFocusMode 1280 self._focusModeIsSticky = False 1281 self._browseModeIsSticky = False 1282 self.refreshKeyGrabs() 1283 1284 def locusOfFocusChanged(self, event, oldFocus, newFocus): 1285 """Handles changes of focus of interest to the script.""" 1286 1287 if newFocus and self.utilities.isZombie(newFocus): 1288 msg = "WEB: New focus is Zombie: %s" % newFocus 1289 debug.println(debug.LEVEL_INFO, msg, True) 1290 return True 1291 1292 document = self.utilities.getTopLevelDocumentForObject(newFocus) 1293 if not document and self.utilities.isDocument(newFocus): 1294 document = newFocus 1295 1296 if not document: 1297 msg = "WEB: Locus of focus changed to non-document obj" 1298 self._madeFindAnnouncement = False 1299 self._inFocusMode = False 1300 debug.println(debug.LEVEL_INFO, msg, True) 1301 self.refreshKeyGrabs() 1302 return False 1303 1304 if self.flatReviewContext: 1305 self.toggleFlatReviewMode() 1306 1307 caretOffset = 0 1308 if self.utilities.inFindContainer(oldFocus) \ 1309 or (self.utilities.isDocument(newFocus) and oldFocus == orca_state.activeWindow): 1310 contextObj, contextOffset = self.utilities.getCaretContext(documentFrame=document) 1311 if contextObj and not self.utilities.isZombie(contextObj): 1312 newFocus, caretOffset = contextObj, contextOffset 1313 1314 if newFocus.getRole() in [pyatspi.ROLE_UNKNOWN, pyatspi.ROLE_REDUNDANT_OBJECT]: 1315 msg = "WEB: Event source has bogus role. Likely browser bug." 1316 debug.println(debug.LEVEL_INFO, msg, True) 1317 newFocus, offset = self.utilities.findFirstCaretContext(newFocus, 0) 1318 1319 text = self.utilities.queryNonEmptyText(newFocus) 1320 if text and (0 <= text.caretOffset <= text.characterCount): 1321 caretOffset = text.caretOffset 1322 1323 self.utilities.setCaretContext(newFocus, caretOffset, document) 1324 self.updateBraille(newFocus, documentFrame=document) 1325 orca.emitRegionChanged(newFocus, caretOffset) 1326 1327 if self._lastCommandWasMouseButton and event \ 1328 and event.type.startswith("object:text-caret-moved"): 1329 msg = "WEB: Last input event was mouse button. Generating line contents." 1330 debug.println(debug.LEVEL_INFO, msg, True) 1331 contents = self.utilities.getLineContentsAtOffset(newFocus, caretOffset) 1332 utterances = self.speechGenerator.generateContents(contents, priorObj=oldFocus) 1333 elif self.utilities.isContentEditableWithEmbeddedObjects(newFocus) \ 1334 and (self._lastCommandWasCaretNav or self._lastCommandWasStructNav) \ 1335 and not (newFocus.getRole() == pyatspi.ROLE_TABLE_CELL and newFocus.name): 1336 msg = "WEB: New focus %s content editable. Generating line contents." % newFocus 1337 debug.println(debug.LEVEL_INFO, msg, True) 1338 contents = self.utilities.getLineContentsAtOffset(newFocus, caretOffset) 1339 utterances = self.speechGenerator.generateContents(contents) 1340 elif self.utilities.isAnchor(newFocus): 1341 msg = "WEB: New focus %s is anchor. Generating line contents." % newFocus 1342 debug.println(debug.LEVEL_INFO, msg, True) 1343 contents = self.utilities.getLineContentsAtOffset(newFocus, 0) 1344 utterances = self.speechGenerator.generateContents(contents) 1345 elif self.utilities.lastInputEventWasPageNav() and not self.utilities.getTable(newFocus): 1346 msg = "WEB: New focus %s was scrolled to. Generating line contents." % newFocus 1347 debug.println(debug.LEVEL_INFO, msg, True) 1348 contents = self.utilities.getLineContentsAtOffset(newFocus, caretOffset) 1349 utterances = self.speechGenerator.generateContents(contents) 1350 elif self.utilities.isFocusedWithMathChild(newFocus): 1351 msg = "WEB: New focus %s has math child. Generating line contents." % newFocus 1352 debug.println(debug.LEVEL_INFO, msg, True) 1353 contents = self.utilities.getLineContentsAtOffset(newFocus, caretOffset) 1354 utterances = self.speechGenerator.generateContents(contents) 1355 elif newFocus.getRole() == pyatspi.ROLE_HEADING: 1356 msg = "WEB: New focus %s is heading. Generating object contents." % newFocus 1357 debug.println(debug.LEVEL_INFO, msg, True) 1358 contents = self.utilities.getObjectContentsAtOffset(newFocus, 0) 1359 utterances = self.speechGenerator.generateContents(contents) 1360 elif self.utilities.caretMovedToSamePageFragment(event, oldFocus): 1361 msg = "WEB: Event source %s is same page fragment. Generating line contents." % event.source 1362 debug.println(debug.LEVEL_INFO, msg, True) 1363 contents = self.utilities.getLineContentsAtOffset(newFocus, 0) 1364 utterances = self.speechGenerator.generateContents(contents) 1365 elif self.utilities.lastInputEventWasLineNav() and self.utilities.isZombie(oldFocus): 1366 msg = "WEB: Last input event was line nav; oldFocus is zombie. Generating line contents." 1367 debug.println(debug.LEVEL_INFO, msg, True) 1368 contents = self.utilities.getLineContentsAtOffset(newFocus, caretOffset) 1369 utterances = self.speechGenerator.generateContents(contents) 1370 elif self.utilities.lastInputEventWasLineNav() and event \ 1371 and event.type.startswith("object:children-changed"): 1372 msg = "WEB: Last input event was line nav and children changed. Generating line contents." 1373 debug.println(debug.LEVEL_INFO, msg, True) 1374 contents = self.utilities.getLineContentsAtOffset(newFocus, caretOffset) 1375 utterances = self.speechGenerator.generateContents(contents) 1376 else: 1377 msg = "WEB: New focus %s is not a special case. Generating speech." % newFocus 1378 debug.println(debug.LEVEL_INFO, msg, True) 1379 utterances = self.speechGenerator.generateSpeech(newFocus, priorObj=oldFocus) 1380 1381 speech.speak(utterances) 1382 self._saveFocusedObjectInfo(newFocus) 1383 1384 if self.utilities.inTopLevelWebApp(newFocus) and not self._browseModeIsSticky: 1385 announce = not self.utilities.inDocumentContent(oldFocus) 1386 self.enableStickyFocusMode(None, announce) 1387 return True 1388 1389 if not self._focusModeIsSticky \ 1390 and not self._browseModeIsSticky \ 1391 and self.useFocusMode(newFocus, oldFocus) != self._inFocusMode: 1392 self.togglePresentationMode(None, document) 1393 1394 if not self.utilities.inDocumentContent(oldFocus): 1395 self.refreshKeyGrabs() 1396 1397 return True 1398 1399 def onActiveChanged(self, event): 1400 """Callback for object:state-changed:active accessibility events.""" 1401 1402 if not self.utilities.inDocumentContent(event.source): 1403 msg = "WEB: Event source is not in document content" 1404 debug.println(debug.LEVEL_INFO, msg, True) 1405 return False 1406 1407 if not event.detail1: 1408 msg = "WEB: Ignoring because event source is now inactive" 1409 debug.println(debug.LEVEL_INFO, msg, True) 1410 return True 1411 1412 role = event.source.getRole() 1413 if role in [pyatspi.ROLE_DIALOG, pyatspi.ROLE_ALERT]: 1414 msg = "WEB: Event handled: Setting locusOfFocus to event source" 1415 debug.println(debug.LEVEL_INFO, msg, True) 1416 orca.setLocusOfFocus(event, event.source) 1417 return True 1418 1419 return False 1420 1421 def onActiveDescendantChanged(self, event): 1422 """Callback for object:active-descendant-changed accessibility events.""" 1423 1424 if not self.utilities.inDocumentContent(event.source): 1425 msg = "WEB: Event source is not in document content" 1426 debug.println(debug.LEVEL_INFO, msg, True) 1427 return False 1428 1429 return True 1430 1431 def onBusyChanged(self, event): 1432 """Callback for object:state-changed:busy accessibility events.""" 1433 1434 if event.detail1 and self._loadingDocumentContent: 1435 msg = "WEB: Ignoring: Already loading document content" 1436 debug.println(debug.LEVEL_INFO, msg, True) 1437 return True 1438 1439 if not self.utilities.inDocumentContent(event.source): 1440 msg = "WEB: Event source is not in document content" 1441 debug.println(debug.LEVEL_INFO, msg, True) 1442 return False 1443 1444 if event.source.getRole() != pyatspi.ROLE_DOCUMENT_WEB \ 1445 and not self.utilities.isOrDescendsFrom(orca_state.locusOfFocus, event.source): 1446 msg = "WEB: Ignoring: Not document and not something we're in" 1447 debug.println(debug.LEVEL_INFO, msg, True) 1448 return True 1449 1450 self.structuralNavigation.clearCache() 1451 1452 if self.utilities.getDocumentForObject(event.source.parent): 1453 msg = "WEB: Ignoring: Event source is nested document" 1454 debug.println(debug.LEVEL_INFO, msg, True) 1455 return True 1456 1457 obj, offset = self.utilities.getCaretContext() 1458 if not obj or self.utilities.isZombie(obj): 1459 self.utilities.clearCaretContext() 1460 1461 shouldPresent = True 1462 if not self.utilities.isShowingOrVisible(event.source): 1463 shouldPresent = False 1464 msg = "WEB: Not presenting because source is not showing or visible" 1465 debug.println(debug.LEVEL_INFO, msg, True) 1466 elif not self.utilities.documentFrameURI(event.source): 1467 shouldPresent = False 1468 msg = "WEB: Not presenting because source lacks URI" 1469 debug.println(debug.LEVEL_INFO, msg, True) 1470 elif not event.detail1 and self._inFocusMode and not self.utilities.isZombie(obj): 1471 shouldPresent = False 1472 msg = "WEB: Not presenting due to focus mode for %s" % obj 1473 debug.println(debug.LEVEL_INFO, msg, True) 1474 1475 if not _settingsManager.getSetting('onlySpeakDisplayedText') and shouldPresent: 1476 if event.detail1: 1477 self.presentMessage(messages.PAGE_LOADING_START) 1478 elif event.source.name: 1479 msg = messages.PAGE_LOADING_END_NAMED % event.source.name 1480 self.presentMessage(msg, resetStyles=False) 1481 else: 1482 self.presentMessage(messages.PAGE_LOADING_END) 1483 1484 activeDocument = self.utilities.activeDocument() 1485 if activeDocument and activeDocument != event.source: 1486 msg = "WEB: Ignoring: Event source is not active document" 1487 debug.println(debug.LEVEL_INFO, msg, True) 1488 return True 1489 1490 self._loadingDocumentContent = event.detail1 1491 if event.detail1: 1492 return True 1493 1494 self.utilities.clearCachedObjects() 1495 if self.utilities.isDead(obj): 1496 obj = None 1497 1498 if not self.utilities.isDead(orca_state.locusOfFocus) \ 1499 and not self.utilities.inDocumentContent(orca_state.locusOfFocus) \ 1500 and orca_state.locusOfFocus.getState().contains(pyatspi.STATE_FOCUSED): 1501 msg = "WEB: Not presenting content, focus is outside of document" 1502 debug.println(debug.LEVEL_INFO, msg, True) 1503 return True 1504 1505 if _settingsManager.getSetting('pageSummaryOnLoad') and shouldPresent: 1506 obj = obj or event.source 1507 msg = "WEB: Getting page summary for obj %s" % obj 1508 debug.println(debug.LEVEL_INFO, msg, True) 1509 summary = self.utilities.getPageSummary(obj) 1510 if summary: 1511 self.presentMessage(summary) 1512 1513 obj, offset = self.utilities.getCaretContext() 1514 1515 try: 1516 sourceIsBusy = event.souce.getState().contains(pyatspi.STATE_BUSY) 1517 except: 1518 sourceIsBusy = False 1519 1520 if not sourceIsBusy and self.utilities.isTopLevelWebApp(event.source): 1521 msg = "WEB: Setting locusOfFocus to %s with sticky focus mode" % obj 1522 debug.println(debug.LEVEL_INFO, msg, True) 1523 orca.setLocusOfFocus(event, obj) 1524 self.enableStickyFocusMode(None, True) 1525 return True 1526 1527 if self.useFocusMode(obj) != self._inFocusMode: 1528 self.togglePresentationMode(None) 1529 1530 if not obj: 1531 msg = "WEB: Could not get caret context" 1532 debug.println(debug.LEVEL_INFO, msg, True) 1533 return True 1534 1535 if self.utilities.isFocusModeWidget(obj): 1536 msg = "WEB: Setting locus of focus to focusModeWidget %s" % obj 1537 debug.println(debug.LEVEL_INFO, msg, True) 1538 orca.setLocusOfFocus(event, obj) 1539 return True 1540 1541 state = obj.getState() 1542 if self.utilities.isLink(obj) and state.contains(pyatspi.STATE_FOCUSED): 1543 msg = "WEB: Setting locus of focus to focused link %s. No SayAll." % obj 1544 debug.println(debug.LEVEL_INFO, msg, True) 1545 orca.setLocusOfFocus(event, obj) 1546 return True 1547 1548 if offset > 0: 1549 msg = "WEB: Setting locus of focus to context obj %s. No SayAll" % obj 1550 debug.println(debug.LEVEL_INFO, msg, True) 1551 orca.setLocusOfFocus(event, obj) 1552 return True 1553 1554 try: 1555 focusState = orca_state.locusOfFocus.getState() 1556 except: 1557 inFocusedObject = False 1558 else: 1559 inFocusedObject = focusState.contains(pyatspi.STATE_FOCUSED) 1560 1561 if not inFocusedObject: 1562 msg = "WEB: Setting locus of focus to context obj %s (no notification)" % obj 1563 debug.println(debug.LEVEL_INFO, msg, True) 1564 orca.setLocusOfFocus(event, obj, False) 1565 1566 self.updateBraille(obj) 1567 if self.utilities.documentFragment(event.source): 1568 msg = "WEB: Not doing SayAll due to page fragment" 1569 debug.println(debug.LEVEL_INFO, msg, True) 1570 elif not _settingsManager.getSetting('sayAllOnLoad'): 1571 msg = "WEB: Not doing SayAll due to sayAllOnLoad being False" 1572 debug.println(debug.LEVEL_INFO, msg, True) 1573 self.speakContents(self.utilities.getLineContentsAtOffset(obj, offset)) 1574 elif _settingsManager.getSetting('enableSpeech'): 1575 msg = "WEB: Doing SayAll" 1576 debug.println(debug.LEVEL_INFO, msg, True) 1577 self.sayAll(None) 1578 else: 1579 msg = "WEB: Not doing SayAll due to enableSpeech being False" 1580 debug.println(debug.LEVEL_INFO, msg, True) 1581 1582 return True 1583 1584 def onCaretMoved(self, event): 1585 """Callback for object:text-caret-moved accessibility events.""" 1586 1587 self.utilities.sanityCheckActiveWindow() 1588 1589 if self.utilities.isZombie(event.source): 1590 msg = "WEB: Event source is Zombie" 1591 debug.println(debug.LEVEL_INFO, msg, True) 1592 return True 1593 1594 document = self.utilities.getTopLevelDocumentForObject(event.source) 1595 if not document: 1596 if self.utilities.eventIsBrowserUINoise(event): 1597 msg = "WEB: Ignoring event believed to be browser UI noise" 1598 debug.println(debug.LEVEL_INFO, msg, True) 1599 return True 1600 1601 if self.utilities.eventIsBrowserUIAutocompleteNoise(event): 1602 msg = "WEB: Ignoring event believed to be browser UI autocomplete noise" 1603 debug.println(debug.LEVEL_INFO, msg, True) 1604 return True 1605 1606 msg = "WEB: Event source is not in document content" 1607 debug.println(debug.LEVEL_INFO, msg, True) 1608 return False 1609 1610 obj, offset = self.utilities.getCaretContext(document, False, False) 1611 msg = "WEB: Context: %s, %i (focus: %s)" % (obj, offset, orca_state.locusOfFocus) 1612 debug.println(debug.LEVEL_INFO, msg, True) 1613 1614 if self._lastCommandWasCaretNav: 1615 msg = "WEB: Event ignored: Last command was caret nav" 1616 debug.println(debug.LEVEL_INFO, msg, True) 1617 return True 1618 1619 if self._lastCommandWasStructNav: 1620 msg = "WEB: Event ignored: Last command was struct nav" 1621 debug.println(debug.LEVEL_INFO, msg, True) 1622 return True 1623 1624 if self._lastCommandWasMouseButton: 1625 msg = "WEB: Last command was mouse button" 1626 debug.println(debug.LEVEL_INFO, msg, True) 1627 1628 if (event.source, event.detail1) == (obj, offset): 1629 msg = "WEB: Event is for current caret context." 1630 debug.println(debug.LEVEL_INFO, msg, True) 1631 return True 1632 1633 if (event.source, event.detail1) == self._lastMouseButtonContext: 1634 msg = "WEB: Event is for last mouse button context." 1635 debug.println(debug.LEVEL_INFO, msg, True) 1636 return True 1637 1638 self._lastMouseButtonContext = event.source, event.detail1 1639 1640 msg = "WEB: Event handled: Last command was mouse button" 1641 debug.println(debug.LEVEL_INFO, msg, True) 1642 self.utilities.setCaretContext(event.source, event.detail1) 1643 notify = not self.utilities.isEntryDescendant(event.source) 1644 orca.setLocusOfFocus(event, event.source, notify, True) 1645 if orca_state.locusOfFocus == event.source: 1646 self.updateBraille(event.source) 1647 return True 1648 1649 if self.utilities.lastInputEventWasTab(): 1650 msg = "WEB: Last input event was Tab." 1651 debug.println(debug.LEVEL_INFO, msg, True) 1652 1653 if self.utilities.isDocument(event.source): 1654 msg = "WEB: Event ignored: Caret moved in document due to Tab." 1655 debug.println(debug.LEVEL_INFO, msg, True) 1656 return True 1657 1658 if event.source.getRole() in [pyatspi.ROLE_ENTRY, pyatspi.ROLE_SPIN_BUTTON] \ 1659 and event.source.getState().contains(pyatspi.STATE_FOCUSED) \ 1660 and event.source != orca_state.locusOfFocus: 1661 msg = "WEB: Event ignored: Entry is not (yet) the locus of focus. Waiting for focus event." 1662 debug.println(debug.LEVEL_INFO, msg, True) 1663 return True 1664 1665 if self.utilities.inFindContainer(): 1666 msg = "WEB: Event handled: Presenting find results" 1667 debug.println(debug.LEVEL_INFO, msg, True) 1668 self.presentFindResults(event.source, event.detail1) 1669 self._saveFocusedObjectInfo(orca_state.locusOfFocus) 1670 return True 1671 1672 if not self.utilities.eventIsFromLocusOfFocusDocument(event): 1673 msg = "WEB: Event ignored: Not from locus of focus document" 1674 debug.println(debug.LEVEL_INFO, msg, True) 1675 return True 1676 1677 if self.utilities.textEventIsDueToInsertion(event): 1678 msg = "WEB: Event handled: Updating position due to insertion" 1679 debug.println(debug.LEVEL_INFO, msg, True) 1680 self._saveLastCursorPosition(event.source, event.detail1) 1681 return True 1682 1683 if self.utilities.textEventIsDueToDeletion(event): 1684 msg = "WEB: Event handled: Updating position due to deletion" 1685 debug.println(debug.LEVEL_INFO, msg, True) 1686 self._saveLastCursorPosition(event.source, event.detail1) 1687 return True 1688 1689 if self.utilities.eventIsAutocompleteNoise(event, document): 1690 msg = "WEB: Event ignored: Autocomplete noise" 1691 debug.println(debug.LEVEL_INFO, msg, True) 1692 return True 1693 1694 if self._inFocusMode and self.utilities.caretMovedOutsideActiveGrid(event): 1695 msg = "WEB: Event ignored: Caret moved outside active grid during focus mode" 1696 debug.println(debug.LEVEL_INFO, msg, True) 1697 return True 1698 1699 if self.utilities.treatEventAsSpinnerValueChange(event): 1700 msg = "WEB: Event handled as the value-change event we wish we'd get" 1701 debug.println(debug.LEVEL_INFO, msg, True) 1702 self.updateBraille(event.source) 1703 self._presentTextAtNewCaretPosition(event) 1704 return True 1705 1706 if not self.utilities.queryNonEmptyText(event.source) \ 1707 and not event.source.getState().contains(pyatspi.STATE_EDITABLE): 1708 msg = "WEB: Event ignored: Was for non-editable object we're treating as textless" 1709 debug.println(debug.LEVEL_INFO, msg, True) 1710 return True 1711 1712 obj, offset = self.utilities.findFirstCaretContext(event.source, event.detail1) 1713 notify = force = handled = False 1714 1715 if self.utilities.lastInputEventWasPageNav(): 1716 msg = "WEB: Caret moved due to scrolling." 1717 debug.println(debug.LEVEL_INFO, msg, True) 1718 notify = force = handled = True 1719 1720 elif self.utilities.caretMovedToSamePageFragment(event): 1721 msg = "WEB: Caret moved to fragment." 1722 debug.println(debug.LEVEL_INFO, msg, True) 1723 notify = force = handled = True 1724 1725 elif self.utilities.lastInputEventWasCaretNav(): 1726 msg = "WEB: Caret moved due to native caret navigation." 1727 debug.println(debug.LEVEL_INFO, msg, True) 1728 1729 msg = "WEB: Setting context and focus to: %s, %i" % (obj, offset) 1730 debug.println(debug.LEVEL_INFO, msg, True) 1731 self.utilities.setCaretContext(obj, offset, document) 1732 orca.setLocusOfFocus(event, obj, notify, force) 1733 return handled 1734 1735 def onCheckedChanged(self, event): 1736 """Callback for object:state-changed:checked accessibility events.""" 1737 1738 if not self.utilities.inDocumentContent(event.source): 1739 msg = "WEB: Event source is not in document content" 1740 debug.println(debug.LEVEL_INFO, msg, True) 1741 return False 1742 1743 obj, offset = self.utilities.getCaretContext() 1744 if obj != event.source: 1745 msg = "WEB: Event source is not context object" 1746 debug.println(debug.LEVEL_INFO, msg, True) 1747 return True 1748 1749 oldObj, oldState = self.pointOfReference.get('checkedChange', (None, 0)) 1750 if hash(oldObj) == hash(obj) and oldState == event.detail1: 1751 msg = "WEB: Ignoring event, state hasn't changed" 1752 debug.println(debug.LEVEL_INFO, msg, True) 1753 return True 1754 1755 role = obj.getRole() 1756 if not (self._lastCommandWasCaretNav and role == pyatspi.ROLE_RADIO_BUTTON): 1757 msg = "WEB: Event is something default can handle" 1758 debug.println(debug.LEVEL_INFO, msg, True) 1759 return False 1760 1761 self.updateBraille(obj) 1762 speech.speak(self.speechGenerator.generateSpeech(obj, alreadyFocused=True)) 1763 self.pointOfReference['checkedChange'] = hash(obj), event.detail1 1764 return True 1765 1766 def onChildrenAdded(self, event): 1767 """Callback for object:children-changed:add accessibility events.""" 1768 1769 if self.utilities.eventIsBrowserUINoise(event): 1770 msg = "WEB: Ignoring event believed to be browser UI noise" 1771 debug.println(debug.LEVEL_INFO, msg, True) 1772 return True 1773 1774 isLiveRegion = self.utilities.isLiveRegion(event.source) 1775 document = self.utilities.getTopLevelDocumentForObject(event.source) 1776 if document and not isLiveRegion: 1777 if event.source == orca_state.locusOfFocus: 1778 msg = "WEB: Dumping cache and context: source is focus %s" % orca_state.locusOfFocus 1779 debug.println(debug.LEVEL_INFO, msg, True) 1780 self.utilities.dumpCache(document, preserveContext=False) 1781 elif self.utilities.isDead(orca_state.locusOfFocus): 1782 msg = "WEB: Dumping cache: dead focus %s" % orca_state.locusOfFocus 1783 debug.println(debug.LEVEL_INFO, msg, True) 1784 self.utilities.dumpCache(document, preserveContext=True) 1785 elif pyatspi.findAncestor(orca_state.locusOfFocus, lambda x: x == event.source): 1786 msg = "WEB: Dumping cache: source is ancestor of focus %s" % orca_state.locusOfFocus 1787 debug.println(debug.LEVEL_INFO, msg, True) 1788 self.utilities.dumpCache(document, preserveContext=True) 1789 else: 1790 msg = "WEB: Not dumping full cache. Focus is %s" % orca_state.locusOfFocus 1791 debug.println(debug.LEVEL_INFO, msg, True) 1792 self.utilities.clearCachedObjects() 1793 1794 elif isLiveRegion: 1795 if self.utilities.handleAsLiveRegion(event): 1796 msg = "WEB: Event to be handled as live region" 1797 debug.println(debug.LEVEL_INFO, msg, True) 1798 self.liveRegionManager.handleEvent(event) 1799 else: 1800 msg = "WEB: Ignoring because live region event not to be handled." 1801 debug.println(debug.LEVEL_INFO, msg, True) 1802 return True 1803 else: 1804 msg = "WEB: Could not get document for event source" 1805 debug.println(debug.LEVEL_INFO, msg, True) 1806 return False 1807 1808 if self._loadingDocumentContent: 1809 msg = "WEB: Ignoring because document content is being loaded." 1810 debug.println(debug.LEVEL_INFO, msg, True) 1811 return True 1812 1813 if self.utilities.isZombie(document): 1814 msg = "WEB: Ignoring because %s is zombified." % document 1815 debug.println(debug.LEVEL_INFO, msg, True) 1816 return True 1817 1818 try: 1819 docIsBusy = document.getState().contains(pyatspi.STATE_BUSY) 1820 except: 1821 docIsBusy = False 1822 msg = "WEB: Exception getting state of %s" % document 1823 debug.println(debug.LEVEL_INFO, msg, True) 1824 if docIsBusy: 1825 msg = "WEB: Ignoring because %s is busy." % document 1826 debug.println(debug.LEVEL_INFO, msg, True) 1827 return True 1828 1829 if not event.any_data or self.utilities.isZombie(event.any_data): 1830 msg = "WEB: Ignoring because any data is null or zombified." 1831 debug.println(debug.LEVEL_INFO, msg, True) 1832 return True 1833 1834 if self.utilities.handleEventFromContextReplicant(event, event.any_data): 1835 msg = "WEB: Event handled by updating locusOfFocus and context to child." 1836 debug.println(debug.LEVEL_INFO, msg, True) 1837 return True 1838 1839 childRole = event.any_data.getRole() 1840 if childRole == pyatspi.ROLE_ALERT: 1841 if event.any_data == self.utilities.lastQueuedLiveRegion(): 1842 msg = "WEB: Ignoring %s (is last queued live region)" % event.any_data 1843 debug.println(debug.LEVEL_INFO, msg, True) 1844 return True 1845 1846 msg = "WEB: Presenting event.any_data" 1847 debug.println(debug.LEVEL_INFO, msg, True) 1848 self.presentObject(event.any_data) 1849 1850 focused = self.utilities.focusedObject(event.any_data) 1851 if focused: 1852 notify = self.utilities.queryNonEmptyText(focused) is None 1853 msg = "WEB: Setting locusOfFocus and caret context to %s" % focused 1854 debug.println(debug.LEVEL_INFO, msg) 1855 orca.setLocusOfFocus(event, focused, notify) 1856 self.utilities.setCaretContext(focused, 0) 1857 return True 1858 1859 if self.lastMouseRoutingTime and 0 < time.time() - self.lastMouseRoutingTime < 1: 1860 utterances = [] 1861 utterances.append(messages.NEW_ITEM_ADDED) 1862 utterances.extend(self.speechGenerator.generateSpeech(child, force=True)) 1863 speech.speak(utterances) 1864 self._lastMouseOverObject = event.any_data 1865 self.preMouseOverContext = self.utilities.getCaretContext() 1866 return True 1867 1868 return False 1869 1870 def onChildrenRemoved(self, event): 1871 """Callback for object:children-changed:removed accessibility events.""" 1872 1873 if not self.utilities.inDocumentContent(event.source): 1874 msg = "WEB: Event source is not in document content." 1875 debug.println(debug.LEVEL_INFO, msg, True) 1876 return False 1877 1878 if self._loadingDocumentContent: 1879 msg = "WEB: Ignoring because document content is being loaded." 1880 debug.println(debug.LEVEL_INFO, msg, True) 1881 return True 1882 1883 if self.utilities.isLiveRegion(event.source): 1884 if self.utilities.handleEventForRemovedChild(event): 1885 msg = "WEB: Event handled for removed live-region child." 1886 debug.println(debug.LEVEL_INFO, msg, True) 1887 else: 1888 msg = "WEB: Ignoring removal from live region." 1889 debug.println(debug.LEVEL_INFO, msg, True) 1890 return True 1891 1892 document = self.utilities.getTopLevelDocumentForObject(event.source) 1893 if document: 1894 if event.source == orca_state.locusOfFocus: 1895 msg = "WEB: Dumping cache and context: source is focus %s" % orca_state.locusOfFocus 1896 debug.println(debug.LEVEL_INFO, msg, True) 1897 self.utilities.dumpCache(document, preserveContext=False) 1898 elif self.utilities.isDead(orca_state.locusOfFocus): 1899 msg = "WEB: Dumping cache: dead focus %s" % orca_state.locusOfFocus 1900 debug.println(debug.LEVEL_INFO, msg, True) 1901 self.utilities.dumpCache(document, preserveContext=True) 1902 elif pyatspi.findAncestor(orca_state.locusOfFocus, lambda x: x == event.source): 1903 msg = "WEB: Dumping cache: source is ancestor of focus %s" % orca_state.locusOfFocus 1904 debug.println(debug.LEVEL_INFO, msg, True) 1905 self.utilities.dumpCache(document, preserveContext=True) 1906 else: 1907 msg = "WEB: Not dumping full cache. Focus is %s" % orca_state.locusOfFocus 1908 debug.println(debug.LEVEL_INFO, msg, True) 1909 self.utilities.clearCachedObjects() 1910 1911 if self.utilities.handleEventForRemovedChild(event): 1912 msg = "WEB: Event handled for removed child." 1913 debug.println(debug.LEVEL_INFO, msg, True) 1914 return True 1915 1916 return False 1917 1918 def onColumnReordered(self, event): 1919 """Callback for object:column-reordered accessibility events.""" 1920 1921 if not self.utilities.inDocumentContent(event.source): 1922 msg = "WEB: Event source is not in document content" 1923 debug.println(debug.LEVEL_INFO, msg, True) 1924 return False 1925 1926 if event.source != self.utilities.getTable(orca_state.locusOfFocus): 1927 msg = "WEB: locusOfFocus (%s) is not in this table" % orca_state.locusOfFocus 1928 debug.println(debug.LEVEL_INFO, msg, True) 1929 return False 1930 1931 self.pointOfReference['last-table-sort-time'] = time.time() 1932 self.presentMessage(messages.TABLE_REORDERED_COLUMNS) 1933 header = self.utilities.containingTableHeader(orca_state.locusOfFocus) 1934 if header: 1935 self.presentMessage(self.utilities.getSortOrderDescription(header, True)) 1936 1937 return True 1938 1939 def onDocumentLoadComplete(self, event): 1940 """Callback for document:load-complete accessibility events.""" 1941 1942 if self.utilities.getDocumentForObject(event.source.parent): 1943 msg = "WEB: Ignoring: Event source is nested document" 1944 debug.println(debug.LEVEL_INFO, msg, True) 1945 return True 1946 1947 msg = "WEB: Updating loading state and resetting live regions" 1948 debug.println(debug.LEVEL_INFO, msg, True) 1949 self._loadingDocumentContent = False 1950 self.liveRegionManager.reset() 1951 return True 1952 1953 def onDocumentLoadStopped(self, event): 1954 """Callback for document:load-stopped accessibility events.""" 1955 1956 if self.utilities.getDocumentForObject(event.source.parent): 1957 msg = "WEB: Ignoring: Event source is nested document" 1958 debug.println(debug.LEVEL_INFO, msg, True) 1959 return True 1960 1961 msg = "WEB: Updating loading state" 1962 debug.println(debug.LEVEL_INFO, msg, True) 1963 self._loadingDocumentContent = False 1964 return True 1965 1966 def onDocumentReload(self, event): 1967 """Callback for document:reload accessibility events.""" 1968 1969 if self.utilities.getDocumentForObject(event.source.parent): 1970 msg = "WEB: Ignoring: Event source is nested document" 1971 debug.println(debug.LEVEL_INFO, msg, True) 1972 return True 1973 1974 msg = "WEB: Updating loading state" 1975 debug.println(debug.LEVEL_INFO, msg, True) 1976 self._loadingDocumentContent = True 1977 return True 1978 1979 def onExpandedChanged(self, event): 1980 """Callback for object:state-changed:expanded accessibility events.""" 1981 1982 if self.utilities.isZombie(event.source): 1983 msg = "WEB: Event source is Zombie" 1984 debug.println(debug.LEVEL_INFO, msg, True) 1985 return True 1986 1987 if not self.utilities.inDocumentContent(event.source): 1988 msg = "WEB: Event source is not in document content" 1989 debug.println(debug.LEVEL_INFO, msg, True) 1990 return False 1991 1992 obj, offset = self.utilities.getCaretContext(searchIfNeeded=False) 1993 msg = "WEB: Caret context is %s, %i (focus: %s)" % (obj, offset, orca_state.locusOfFocus) 1994 debug.println(debug.LEVEL_INFO, msg, True) 1995 1996 if not obj or self.utilities.isZombie(obj) and event.source == orca_state.locusOfFocus: 1997 msg = "WEB: Setting caret context to event source" 1998 debug.println(debug.LEVEL_INFO, msg, True) 1999 self.utilities.setCaretContext(event.source, 0) 2000 2001 return False 2002 2003 def onFocus(self, event): 2004 """Callback for focus: accessibility events.""" 2005 2006 # We should get proper state-changed events for these. 2007 if self.utilities.inDocumentContent(event.source): 2008 msg = "WEB: Ignoring because object:state-changed-focused expected." 2009 debug.println(debug.LEVEL_INFO, msg, True) 2010 return True 2011 2012 return False 2013 2014 def onFocusedChanged(self, event): 2015 """Callback for object:state-changed:focused accessibility events.""" 2016 2017 if not event.detail1: 2018 msg = "WEB: Ignoring because event source lost focus" 2019 debug.println(debug.LEVEL_INFO, msg, True) 2020 return True 2021 2022 if self.utilities.isZombie(event.source): 2023 msg = "WEB: Event source is Zombie" 2024 debug.println(debug.LEVEL_INFO, msg, True) 2025 return True 2026 2027 document = self.utilities.getDocumentForObject(event.source) 2028 if not document: 2029 msg = "WEB: Could not get document for event source" 2030 debug.println(debug.LEVEL_INFO, msg, True) 2031 return False 2032 2033 role = event.source.getRole() 2034 if self.utilities.isWebAppDescendant(event.source): 2035 if self._browseModeIsSticky: 2036 msg = "WEB: Web app descendant claimed focus, but browse mode is sticky" 2037 debug.println(debug.LEVEL_INFO, msg, True) 2038 elif role == pyatspi.ROLE_TOOL_TIP \ 2039 and pyatspi.findAncestor(orca_state.locusOfFocus, lambda x: x and x == event.source): 2040 msg = "WEB: Event believed to be side effect of tooltip navigation." 2041 debug.println(debug.LEVEL_INFO, msg, True) 2042 return True 2043 else: 2044 msg = "WEB: Event handled: Setting locusOfFocus to web app descendant" 2045 debug.println(debug.LEVEL_INFO, msg, True) 2046 orca.setLocusOfFocus(event, event.source) 2047 return True 2048 2049 state = event.source.getState() 2050 if state.contains(pyatspi.STATE_EDITABLE): 2051 msg = "WEB: Event source is editable" 2052 debug.println(debug.LEVEL_INFO, msg, True) 2053 return False 2054 2055 if role in [pyatspi.ROLE_DIALOG, pyatspi.ROLE_ALERT]: 2056 msg = "WEB: Event handled: Setting locusOfFocus to event source" 2057 debug.println(debug.LEVEL_INFO, msg, True) 2058 orca.setLocusOfFocus(event, event.source) 2059 return True 2060 2061 if self.utilities.handleEventFromContextReplicant(event, event.source): 2062 msg = "WEB: Event handled by updating locusOfFocus and context to source." 2063 debug.println(debug.LEVEL_INFO, msg, True) 2064 return True 2065 2066 obj, offset = self.utilities.getCaretContext() 2067 msg = "WEB: Caret context is %s, %i (focus: %s)" \ 2068 % (obj, offset, orca_state.locusOfFocus) 2069 debug.println(debug.LEVEL_INFO, msg, True) 2070 2071 if not obj or self.utilities.isZombie(obj): 2072 msg = "WEB: Clearing context - obj is null or zombie" 2073 debug.println(debug.LEVEL_INFO, msg, True) 2074 self.utilities.clearCaretContext() 2075 2076 obj, offset = self.utilities.searchForCaretContext(event.source) 2077 if obj: 2078 notify = self.utilities.inFindContainer(orca_state.locusOfFocus) 2079 msg = "WEB: Updating focus and context to %s, %i" % (obj, offset) 2080 debug.println(debug.LEVEL_INFO, msg, True) 2081 orca.setLocusOfFocus(event, obj, notify) 2082 self.utilities.setCaretContext(obj, offset) 2083 else: 2084 msg = "WEB: Search for caret context failed" 2085 debug.println(debug.LEVEL_INFO, msg, True) 2086 2087 if self._lastCommandWasCaretNav: 2088 msg = "WEB: Event ignored: Last command was caret nav" 2089 debug.println(debug.LEVEL_INFO, msg, True) 2090 return True 2091 2092 if self._lastCommandWasStructNav: 2093 msg = "WEB: Event ignored: Last command was struct nav" 2094 debug.println(debug.LEVEL_INFO, msg, True) 2095 return True 2096 2097 if not state.contains(pyatspi.STATE_FOCUSABLE) \ 2098 and not state.contains(pyatspi.STATE_FOCUSED): 2099 msg = "WEB: Event ignored: Source is not focusable or focused" 2100 debug.println(debug.LEVEL_INFO, msg, True) 2101 return True 2102 2103 if not role in [pyatspi.ROLE_DOCUMENT_FRAME, pyatspi.ROLE_DOCUMENT_WEB]: 2104 msg = "WEB: Deferring to other scripts for handling non-document source" 2105 debug.println(debug.LEVEL_INFO, msg, True) 2106 return False 2107 2108 if not obj: 2109 msg = "WEB: Unable to get non-null, non-zombie context object" 2110 debug.println(debug.LEVEL_INFO, msg, True) 2111 return False 2112 2113 if self.utilities.lastInputEventWasPageNav(): 2114 msg = "WEB: Event handled: Focus changed due to scrolling" 2115 debug.println(debug.LEVEL_INFO, msg, True) 2116 orca.setLocusOfFocus(event, obj) 2117 self.utilities.setCaretContext(obj, offset) 2118 return True 2119 2120 wasFocused = obj.getState().contains(pyatspi.STATE_FOCUSED) 2121 obj.clearCache() 2122 isFocused = obj.getState().contains(pyatspi.STATE_FOCUSED) 2123 if wasFocused != isFocused: 2124 msg = "WEB: Focused state of %s changed to %s" % (obj, isFocused) 2125 debug.println(debug.LEVEL_INFO, msg, True) 2126 return False 2127 2128 if self.utilities.isAnchor(obj): 2129 cause = "Context is anchor" 2130 elif not (self.utilities.isLink(obj) and not isFocused): 2131 cause = "Context is not a non-focused link" 2132 elif self.utilities.isChildOfCurrentFragment(obj): 2133 cause = "Context is child of current fragment" 2134 elif document == event.source and self.utilities.documentFragment(event.source): 2135 cause = "Document URI is fragment" 2136 else: 2137 return False 2138 2139 msg = "WEB: Event handled: Setting locusOfFocus to %s (%s)" % (obj, cause) 2140 debug.println(debug.LEVEL_INFO, msg, True) 2141 orca.setLocusOfFocus(event, obj) 2142 return True 2143 2144 def onMouseButton(self, event): 2145 """Callback for mouse:button accessibility events.""" 2146 2147 self._lastCommandWasCaretNav = False 2148 self._lastCommandWasStructNav = False 2149 self._lastCommandWasMouseButton = True 2150 return False 2151 2152 def onNameChanged(self, event): 2153 """Callback for object:property-change:accessible-name events.""" 2154 2155 if self.utilities.eventIsBrowserUINoise(event): 2156 msg = "WEB: Ignoring event believed to be browser UI noise" 2157 debug.println(debug.LEVEL_INFO, msg, True) 2158 return True 2159 2160 return True 2161 2162 def onRowReordered(self, event): 2163 """Callback for object:row-reordered accessibility events.""" 2164 2165 if not self.utilities.inDocumentContent(event.source): 2166 msg = "WEB: Event source is not in document content" 2167 debug.println(debug.LEVEL_INFO, msg, True) 2168 return False 2169 2170 if event.source != self.utilities.getTable(orca_state.locusOfFocus): 2171 msg = "WEB: locusOfFocus (%s) is not in this table" % orca_state.locusOfFocus 2172 debug.println(debug.LEVEL_INFO, msg, True) 2173 return False 2174 2175 self.pointOfReference['last-table-sort-time'] = time.time() 2176 self.presentMessage(messages.TABLE_REORDERED_ROWS) 2177 header = self.utilities.containingTableHeader(orca_state.locusOfFocus) 2178 if header: 2179 self.presentMessage(self.utilities.getSortOrderDescription(header, True)) 2180 2181 return True 2182 2183 def onSelectedChanged(self, event): 2184 """Callback for object:state-changed:selected accessibility events.""" 2185 2186 if self.utilities.eventIsBrowserUIAutocompleteNoise(event): 2187 msg = "WEB: Ignoring event believed to be browser UI autocomplete noise" 2188 debug.println(debug.LEVEL_INFO, msg, True) 2189 return True 2190 2191 if self.utilities.eventIsBrowserUIPageSwitch(event): 2192 msg = "WEB: Event believed to be browser UI page switch" 2193 debug.println(debug.LEVEL_INFO, msg, True) 2194 if event.detail1: 2195 self.presentObject(event.source, priorObj=orca_state.locusOfFocus) 2196 return True 2197 2198 if not self.utilities.inDocumentContent(event.source): 2199 msg = "WEB: Event source is not in document content" 2200 debug.println(debug.LEVEL_INFO, msg, True) 2201 return False 2202 2203 if orca_state.locusOfFocus != event.source: 2204 msg = "WEB: Ignoring because event source is not locusOfFocus" 2205 debug.println(debug.LEVEL_INFO, msg, True) 2206 return True 2207 2208 return False 2209 2210 def onSelectionChanged(self, event): 2211 """Callback for object:selection-changed accessibility events.""" 2212 2213 if self.utilities.eventIsBrowserUIAutocompleteNoise(event): 2214 msg = "WEB: Ignoring event believed to be browser UI autocomplete noise" 2215 debug.println(debug.LEVEL_INFO, msg, True) 2216 return True 2217 2218 if self.utilities.eventIsBrowserUIPageSwitch(event): 2219 msg = "WEB: Ignoring event believed to be browser UI page switch" 2220 debug.println(debug.LEVEL_INFO, msg, True) 2221 return True 2222 2223 if not self.utilities.inDocumentContent(event.source): 2224 msg = "WEB: Event source is not in document content" 2225 debug.println(debug.LEVEL_INFO, msg, True) 2226 return False 2227 2228 if not self.utilities.inDocumentContent(orca_state.locusOfFocus): 2229 msg = "WEB: Event ignored: locusOfFocus (%s) is not in document content" \ 2230 % orca_state.locusOfFocus 2231 debug.println(debug.LEVEL_INFO, msg, True) 2232 return True 2233 2234 if not self.utilities.eventIsFromLocusOfFocusDocument(event): 2235 msg = "WEB: Event ignored: Not from locus of focus document" 2236 debug.println(debug.LEVEL_INFO, msg, True) 2237 return True 2238 2239 if self.utilities.isWebAppDescendant(event.source): 2240 if self._inFocusMode: 2241 msg = "WEB: Event source is web app descendant and we're in focus mode" 2242 debug.println(debug.LEVEL_INFO, msg, True) 2243 return False 2244 2245 msg = "WEB: Event source is web app descendant and we're in browse mode" 2246 debug.println(debug.LEVEL_INFO, msg, True) 2247 return True 2248 2249 obj, offset = self.utilities.getCaretContext() 2250 ancestor = self.utilities.commonAncestor(obj, event.source) 2251 if ancestor and self.utilities.isTextBlockElement(ancestor): 2252 msg = "WEB: Ignoring: Common ancestor of context and event source is text block" 2253 debug.println(debug.LEVEL_INFO, msg, True) 2254 return True 2255 2256 return False 2257 2258 def onShowingChanged(self, event): 2259 """Callback for object:state-changed:showing accessibility events.""" 2260 2261 if event.detail1 and self.utilities.isTopLevelBrowserUIAlert(event.source): 2262 msg = "WEB: Event handled: Presenting event source" 2263 debug.println(debug.LEVEL_INFO, msg, True) 2264 self.presentObject(event.source) 2265 return True 2266 2267 if not self.utilities.inDocumentContent(event.source): 2268 msg = "WEB: Event source is not in document content" 2269 debug.println(debug.LEVEL_INFO, msg, True) 2270 return False 2271 2272 return True 2273 2274 def onTextAttributesChanged(self, event): 2275 """Callback for object:text-attributes-changed accessibility events.""" 2276 2277 msg = "WEB: Clearing cached text attributes" 2278 debug.println(debug.LEVEL_INFO, msg, True) 2279 self._currentTextAttrs = {} 2280 return False 2281 2282 def onTextDeleted(self, event): 2283 """Callback for object:text-changed:delete accessibility events.""" 2284 2285 if self.utilities.isZombie(event.source): 2286 msg = "WEB: Event source is Zombie" 2287 debug.println(debug.LEVEL_INFO, msg, True) 2288 return True 2289 2290 if self.utilities.lastInputEventWasPageSwitch(): 2291 msg = "WEB: Deletion is believed to be due to page switch" 2292 debug.println(debug.LEVEL_INFO, msg, True) 2293 return True 2294 2295 if self.utilities.isLiveRegion(event.source): 2296 msg = "WEB: Ignoring deletion from live region" 2297 debug.println(debug.LEVEL_INFO, msg, True) 2298 return True 2299 2300 if self.utilities.eventIsBrowserUINoise(event): 2301 msg = "WEB: Ignoring event believed to be browser UI noise" 2302 debug.println(debug.LEVEL_INFO, msg, True) 2303 return True 2304 2305 if not self.utilities.inDocumentContent(event.source): 2306 msg = "WEB: Event source is not in document content" 2307 debug.println(debug.LEVEL_INFO, msg, True) 2308 return False 2309 2310 if self.utilities.eventIsSpinnerNoise(event): 2311 msg = "WEB: Ignoring: Event believed to be spinner noise" 2312 debug.println(debug.LEVEL_INFO, msg, True) 2313 return True 2314 2315 if self.utilities.eventIsAutocompleteNoise(event): 2316 msg = "WEB: Ignoring event believed to be autocomplete noise" 2317 debug.println(debug.LEVEL_INFO, msg, True) 2318 return True 2319 2320 msg = "WEB: Clearing content cache due to text deletion" 2321 debug.println(debug.LEVEL_INFO, msg, True) 2322 self.utilities.clearContentCache() 2323 2324 if self.utilities.textEventIsDueToDeletion(event): 2325 msg = "WEB: Event believed to be due to editable text deletion" 2326 debug.println(debug.LEVEL_INFO, msg, True) 2327 return False 2328 2329 if self.utilities.textEventIsDueToInsertion(event): 2330 msg = "WEB: Ignoring event believed to be due to text insertion" 2331 debug.println(debug.LEVEL_INFO, msg, True) 2332 return True 2333 2334 obj, offset = self.utilities.getCaretContext(getZombieReplicant=False) 2335 if obj and obj != event.source \ 2336 and not pyatspi.findAncestor(obj, lambda x: x == event.source): 2337 msg = "WEB: Ignoring event because it isn't %s or its ancestor" % obj 2338 debug.println(debug.LEVEL_INFO, msg, True) 2339 return True 2340 2341 if self.utilities.isZombie(obj): 2342 if self.utilities.isLink(obj): 2343 msg = "WEB: Focused link deleted. Taking no further action." 2344 debug.println(debug.LEVEL_INFO, msg, True) 2345 return True 2346 2347 obj, offset = self.utilities.getCaretContext(getZombieReplicant=True) 2348 if obj: 2349 orca.setLocusOfFocus(event, obj, notifyScript=False) 2350 2351 if self.utilities.isZombie(obj): 2352 msg = "WEB: Unable to get non-null, non-zombie context object" 2353 debug.println(debug.LEVEL_INFO, msg, True) 2354 2355 document = self.utilities.getDocumentForObject(event.source) 2356 if document: 2357 msg = "WEB: Clearing structural navigation cache for %s" % document 2358 debug.println(debug.LEVEL_INFO, msg, True) 2359 self.structuralNavigation.clearCache(document) 2360 2361 if not event.source.getState().contains(pyatspi.STATE_EDITABLE) \ 2362 and not self.utilities.isContentEditableWithEmbeddedObjects(event.source): 2363 if self._inMouseOverObject \ 2364 and self.utilities.isZombie(self._lastMouseOverObject): 2365 msg = "WEB: Restoring pre-mouseover context" 2366 debug.println(debug.LEVEL_INFO, msg, True) 2367 self.restorePreMouseOverContext() 2368 2369 msg = "WEB: Done processing non-editable source" 2370 debug.println(debug.LEVEL_INFO, msg, True) 2371 return True 2372 2373 return False 2374 2375 def onTextInserted(self, event): 2376 """Callback for object:text-changed:insert accessibility events.""" 2377 2378 if self.utilities.isZombie(event.source): 2379 msg = "WEB: Event source is Zombie" 2380 debug.println(debug.LEVEL_INFO, msg, True) 2381 return True 2382 2383 if self.utilities.lastInputEventWasPageSwitch(): 2384 msg = "WEB: Insertion is believed to be due to page switch" 2385 debug.println(debug.LEVEL_INFO, msg, True) 2386 return True 2387 2388 if self.utilities.handleAsLiveRegion(event): 2389 msg = "WEB: Event to be handled as live region" 2390 debug.println(debug.LEVEL_INFO, msg, True) 2391 self.liveRegionManager.handleEvent(event) 2392 return True 2393 2394 if self.utilities.isLiveRegion(event.source): 2395 msg = "WEB: Ignoring because live region event not to be handled." 2396 debug.println(debug.LEVEL_INFO, msg, True) 2397 return True 2398 2399 if self.utilities.eventIsEOCAdded(event): 2400 msg = "WEB: Ignoring: Event was for embedded object char" 2401 debug.println(debug.LEVEL_INFO, msg, True) 2402 return True 2403 2404 if self.utilities.eventIsBrowserUINoise(event): 2405 msg = "WEB: Ignoring event believed to be browser UI noise" 2406 debug.println(debug.LEVEL_INFO, msg, True) 2407 return True 2408 2409 if not self.utilities.inDocumentContent(event.source): 2410 msg = "WEB: Event source is not in document content" 2411 debug.println(debug.LEVEL_INFO, msg, True) 2412 return False 2413 2414 if self.utilities.eventIsSpinnerNoise(event): 2415 msg = "WEB: Ignoring: Event believed to be spinner noise" 2416 debug.println(debug.LEVEL_INFO, msg, True) 2417 return True 2418 2419 if self.utilities.eventIsAutocompleteNoise(event): 2420 msg = "WEB: Ignoring: Event believed to be autocomplete noise" 2421 debug.println(debug.LEVEL_INFO, msg, True) 2422 return True 2423 2424 msg = "WEB: Clearing content cache due to text insertion" 2425 debug.println(debug.LEVEL_INFO, msg, True) 2426 self.utilities.clearContentCache() 2427 2428 document = self.utilities.getTopLevelDocumentForObject(event.source) 2429 if self.utilities.isDead(orca_state.locusOfFocus): 2430 msg = "WEB: Dumping cache: dead focus %s" % orca_state.locusOfFocus 2431 debug.println(debug.LEVEL_INFO, msg, True) 2432 self.utilities.dumpCache(document, preserveContext=True) 2433 else: 2434 msg = "WEB: Clearing structural navigation cache for %s" % document 2435 debug.println(debug.LEVEL_INFO, msg, True) 2436 self.structuralNavigation.clearCache(document) 2437 2438 text = self.utilities.queryNonEmptyText(event.source) 2439 if not text: 2440 msg = "WEB: Ignoring: Event source is not a text object" 2441 debug.println(debug.LEVEL_INFO, msg, True) 2442 return True 2443 2444 state = event.source.getState() 2445 if not state.contains(pyatspi.STATE_EDITABLE): 2446 if event.source != orca_state.locusOfFocus: 2447 msg = "WEB: Done processing non-editable, non-locusOfFocus source" 2448 debug.println(debug.LEVEL_INFO, msg, True) 2449 return True 2450 2451 if self.utilities.isClickableElement(event.source): 2452 msg = "WEB: Event handled: Re-setting locusOfFocus to changed clickable" 2453 debug.println(debug.LEVEL_INFO, msg, True) 2454 orca.setLocusOfFocus(None, event.source, force=True) 2455 return True 2456 2457 return False 2458 2459 def onTextSelectionChanged(self, event): 2460 """Callback for object:text-selection-changed accessibility events.""" 2461 2462 if self.utilities.isZombie(event.source): 2463 msg = "WEB: Event source is Zombie" 2464 debug.println(debug.LEVEL_INFO, msg, True) 2465 return True 2466 2467 if self.utilities.eventIsBrowserUINoise(event): 2468 msg = "WEB: Ignoring event believed to be browser UI noise" 2469 debug.println(debug.LEVEL_INFO, msg, True) 2470 return True 2471 2472 if not self.utilities.inDocumentContent(event.source): 2473 msg = "WEB: Event source is not in document content" 2474 debug.println(debug.LEVEL_INFO, msg, True) 2475 return False 2476 2477 if not self.utilities.inDocumentContent(orca_state.locusOfFocus): 2478 msg = "WEB: Event ignored: locusOfFocus (%s) is not in document content" \ 2479 % orca_state.locusOfFocus 2480 debug.println(debug.LEVEL_INFO, msg, True) 2481 return True 2482 2483 if self.utilities.eventIsAutocompleteNoise(event): 2484 msg = "WEB: Ignoring: Event believed to be autocomplete noise" 2485 debug.println(debug.LEVEL_INFO, msg, True) 2486 return True 2487 2488 if self.utilities.eventIsSpinnerNoise(event): 2489 msg = "WEB: Ignoring: Event believed to be spinner noise" 2490 debug.println(debug.LEVEL_INFO, msg, True) 2491 return True 2492 2493 if self.utilities.textEventIsForNonNavigableTextObject(event): 2494 msg = "WEB: Ignoring event for non-navigable text object" 2495 debug.println(debug.LEVEL_INFO, msg, True) 2496 return True 2497 2498 text = self.utilities.queryNonEmptyText(event.source) 2499 if not text: 2500 msg = "WEB: Ignoring: Event source is not a text object" 2501 debug.println(debug.LEVEL_INFO, msg, True) 2502 return True 2503 2504 if self.utilities.isContentEditableWithEmbeddedObjects(event.source): 2505 msg = "WEB: In content editable with embedded objects" 2506 debug.println(debug.LEVEL_INFO, msg, True) 2507 return False 2508 2509 offset = text.caretOffset 2510 char = text.getText(offset, offset+1) 2511 if char == self.EMBEDDED_OBJECT_CHARACTER \ 2512 and not self.utilities.lastInputEventWasCaretNavWithSelection() \ 2513 and not self.utilities.lastInputEventWasCommand(): 2514 msg = "WEB: Ignoring: Not selecting and event offset is at embedded object" 2515 debug.println(debug.LEVEL_INFO, msg, True) 2516 return True 2517 2518 return False 2519 2520 def onWindowActivated(self, event): 2521 """Callback for window:activate accessibility events.""" 2522 2523 msg = "WEB: Deferring to app/toolkit script" 2524 debug.println(debug.LEVEL_INFO, msg, True) 2525 return False 2526 2527 def onWindowDeactivated(self, event): 2528 """Callback for window:deactivate accessibility events.""" 2529 2530 msg = "WEB: Clearing command state" 2531 debug.println(debug.LEVEL_INFO, msg, True) 2532 self._lastCommandWasCaretNav = False 2533 self._lastCommandWasStructNav = False 2534 self._lastCommandWasMouseButton = False 2535 self._lastMouseButtonContext = None, -1 2536 self.removeKeyGrabs() 2537 return False 2538 2539 def getTransferableAttributes(self): 2540 return {"_lastCommandWasCaretNav": self._lastCommandWasCaretNav, 2541 "_lastCommandWasStructNav": self._lastCommandWasStructNav, 2542 "_lastCommandWasMouseButton": self._lastCommandWasMouseButton, 2543 "_inFocusMode": self._inFocusMode, 2544 "_focusModeIsSticky": self._focusModeIsSticky, 2545 "_browseModeIsSticky": self._browseModeIsSticky, 2546 } 2547