Coverage Report - orca.focus_tracking_presenter

ModuleCoverage %
orca.focus_tracking_presenter
68%
1
# Orca
2
#
3
# Copyright 2004-2007 Sun Microsystems Inc.
4
#
5
# This library is free software; you can redistribute it and/or
6
# modify it under the terms of the GNU Library General Public
7
# License as published by the Free Software Foundation; either
8
# version 2 of the License, or (at your option) any later version.
9
#
10
# This library is distributed in the hope that it will be useful,
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13
# Library General Public License for more details.
14
#
15
# You should have received a copy of the GNU Library General Public
16
# License along with this library; if not, write to the
17
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18
# Boston, MA 02111-1307, USA.
19
20 1
"""Provides the FocusTrackingPresenter for Orca."""
21
22 1
__id__        = "$Id: focus_tracking_presenter.py 2578 2007-08-01 08:50:04Z wwalker $"
23 1
__version__   = "$Revision: 2578 $"
24 1
__date__      = "$Date: 2007-08-01 04:50:04 -0400 (Wed, 01 Aug 2007) $"
25 1
__copyright__ = "Copyright (c) 2005-2007 Sun Microsystems Inc."
26 1
__license__   = "LGPL"
27
28 1
import gobject
29 1
import Queue
30 1
import threading
31 1
import time
32
33 1
import atspi
34 1
import braille
35 1
import default
36 1
import debug
37 1
import input_event
38 1
import orca_state
39 1
import presentation_manager
40 1
import rolenames
41 1
import settings
42 1
import speech
43
44 1
from orca_i18n import _ # for gettext support
45
46 1
import CORBA
47
48 2
class FocusTrackingPresenter(presentation_manager.PresentationManager):
49
    """Maintain a set of scripts for all running applications, and
50
    also keeps the notion of an activeScript.  All object events are
51
    passed to the associated script for that application, regardless if
52
    the application has keyboard focus or not.  All keyboard events are
53
    passed to the active script only if it has indicated interest in the
54 1
    event."""
55
56 1
    def __init__(self):
57
58
        # Dictionary that keeps count of event listeners by type.
59
        # This is a bit convoluted for now, but what happens is that
60
        # scripts tell the FocusTrackingPresenter to listen for
61
        # object events based on what they want, and the
62
        # FocusTrackingPresenter then eventually passes them to the
63
        # script.  Because both the FocusTrackingPresenter and scripts
64
        # are interested in object events, and the FocusTrackingPresenter
65
        # is what delves them out, we keep at most one listener to avoid
66
        # receiving the same event twice in a row.
67
        #
68 1
        self.registry        = atspi.Registry()
69 1
        self._knownScripts   = {}
70 1
        self._knownAppSettings = {}
71 1
        self._oldAppSettings = None
72 1
        self._eventQueue     = Queue.Queue(0)
73 1
        self._gidleId        = 0
74 1
        self._gidleLock      = threading.Lock()
75 1
        self.noFocusTimestamp = 0.0
76
77 1
        self.setActiveScript(None)
78
79 1
        if settings.debugEventQueue:
80 0
            self._enqueueEventCount = 0
81 0
            self._dequeueEventCount = 0
82
83
    ########################################################################
84
    #                                                                      #
85
    # METHODS FOR KEEPING TRACK OF LISTENERS REGISTERED WITH ATSPI         #
86
    #                                                                      #
87
    ########################################################################
88
89 1
    def _registerEventListener(self, eventType):
90
        """Tells this module to listen for the given event type.
91
92
        Arguments:
93
        - eventType: the event type.
94
        """
95
96 2057
        if self._listenerCounts.has_key(eventType):
97 2030
            self._listenerCounts[eventType] += 1
98
        else:
99 27
            self.registry.registerEventListener(self._enqueueEvent,
100 27
                                                eventType)
101 27
            self._listenerCounts[eventType] = 1
102
103 1
    def _deregisterEventListener(self, eventType):
104
        """Tells this module to stop listening for the given event type.
105
106
        Arguments:
107
        - eventType: the event type.
108
        """
109
110 1924
        self._listenerCounts[eventType] -= 1
111 1924
        if self._listenerCounts[eventType] == 0:
112 0
            self.registry.deregisterEventListener(self._enqueueEvent,
113 0
                                                  eventType)
114 0
            del self._listenerCounts[eventType]
115
116 1
    def _registerEventListeners(self, script):
117
        """Tells the FocusTrackingPresenter to listen for all
118
        the event types of interest to the script.
119
120
        Arguments:
121
        - script: the script.
122
        """
123
124 2133
        for eventType in script.listeners.keys():
125 2054
            self._registerEventListener(eventType)
126
127 1
    def _deregisterEventListeners(self, script):
128
        """Tells the FocusTrackingPresenter to stop listening for all the
129
        event types of interest to the script.
130
131
        Arguments:
132
        - script: the script.
133
        """
134
135 1998
        for eventType in script.listeners.keys():
136 1924
            self._deregisterEventListener(eventType)
137
138
    ########################################################################
139
    #                                                                      #
140
    # METHODS FOR KEEPING TRACK OF KNOWN SCRIPTS.                          #
141
    #                                                                      #
142
    ########################################################################
143
144
    # The cache of the currently known scripts.  The key is the Python
145
    # Accessible application, and the value is the script for that app.
146
    #
147
148 1
    def _createScript(self, app):
149
        """For the given application name, create a new script instance.
150
        We'll first see if a mapping from appName to module name exists.
151
        If it does, we use that.  If it doesn't, we try the app name.
152
        If all else fails, we fall back to the default script.
153
        """
154
155 78
        script = None
156
157 78
        if settings.enableCustomScripts:
158
            # Look for custom scripts first.
159
            #
160
            # We'll use the LEVEL_FINEST level for debug output here as
161
            # it really isn't an error if the script cannot be found.
162
            # But, sometimes a script cannot be "found" because it has
163
            # a syntax error in it, so we want to give script writers
164
            # a vehicle for debugging these types of things.
165
            #
166 78
            scriptPackages = settings.scriptPackages
167
168 78
            moduleName = settings.getScriptModuleName(app)
169 78
            module = None
170
171 78
            if moduleName and len(moduleName):
172 232
                for package in scriptPackages:
173 156
                    if len(package):
174 156
                        name = package + "." + moduleName
175
                    else:
176 0
                        name = moduleName
177 156
                    try:
178 156
                        debug.println(debug.LEVEL_FINEST,
179 156
                                      "Looking for script at %s.py..." % name)
180 156
                        module = __import__(name,
181 156
                                            globals(),
182 156
                                            locals(),
183 156
                                            [''])
184 2
                        debug.println(debug.LEVEL_FINEST,
185 2
                                      "...found %s.py" % name)
186 2
                        break
187 154
                    except ImportError:
188 154
                        debug.println(debug.LEVEL_FINEST,
189 154
                                      "...could not find %s.py" % name)
190 0
                    except:
191 0
                        debug.printException(debug.LEVEL_SEVERE)
192 0
                        debug.println(debug.LEVEL_SEVERE,
193 0
                                      "While attempting to import %s" % name)
194 78
            if module:
195 2
                try:
196 2
                    script = module.Script(app)
197 0
                except:
198
                    # We do not want the getScript method to fail.  If it does,
199
                    # we want to let the script developer know what went wrong,
200
                    # but we also want to move along without crashing Orca.
201
                    #
202 0
                    debug.printException(debug.LEVEL_SEVERE)
203
204
        # If there is no custom script for an application, try seeing if
205
        # there is a script for the toolkit of the application.  If there
206
        # is, then try to use it.  If there isn't, then fall back to the
207
        # default script. Note that this search is restricted to the "orca"
208
        # package for now.
209
        #
210 78
        if (not script) \
211 76
            and app \
212 76
            and app.__dict__.has_key("toolkitName") \
213 38
            and app.toolkitName:
214
215 38
            try:
216 38
                debug.println(
217 38
                    debug.LEVEL_FINE,
218 38
                    "Looking for toolkit script %s.py..." % app.toolkitName)
219 38
                module = __import__(app.toolkitName,
220 38
                                    globals(),
221 38
                                    locals(),
222 38
                                    [''])
223 0
                script = module.Script(app)
224 0
                debug.println(debug.LEVEL_FINE,
225 0
                              "...found %s.py" % name)
226 38
            except ImportError:
227 38
                debug.println(
228 38
                    debug.LEVEL_FINE,
229 38
                    "...could not find %s.py" % app.toolkitName)
230 0
            except:
231 0
                debug.printException(debug.LEVEL_SEVERE)
232 0
                debug.println(
233 0
                    debug.LEVEL_SEVERE,
234 0
                    "While attempting to import %s" % app.toolkitName)
235
236 78
        if not script:
237 76
            script = default.Script(app)
238
239 78
        return script
240
241 1
    def _getScript(self, app):
242
        """Get a script for an app (and make it if necessary).  This is used
243
        instead of a simple calls to Script's constructor.
244
245
        Arguments:
246
        - app: the Python app
247
248
        Returns an instance of a Script.
249
        """
250
251
        # We might not know what the app is.  In this case, just defer
252
        # to the default script for support.
253
        #
254 13243
        if not app:
255 1
            if not self._defaultScript:
256 1
                self._defaultScript = default.Script(None)
257 1
                self._registerEventListeners(self._defaultScript)
258 1
            script = self._defaultScript
259 13242
        elif self._knownScripts.has_key(app):
260 13164
            script = self._knownScripts[app]
261
        else:
262 78
            script = self._createScript(app)
263 78
            self._knownScripts[app] = script
264 78
            self._registerEventListeners(script)
265
266 13243
        return script
267
268 1
    def setActiveScript(self, newScript):
269
        """Set the new active script.
270
271
        Arguments:
272
        - newScript: the new script to be made active.
273
        """
274
275 360
        try:
276
            # If old ("factory") settings don't exist yet, save
277
            # a set, else restore the old application settings.
278
            #
279 360
            if not self._oldAppSettings:
280
                self._oldAppSettings = \
281 4
                    orca_state.activeScript.saveOldAppSettings()
282
            else:
283 356
                orca_state.activeScript.restoreOldAppSettings( \
284 356
                    self._oldAppSettings)
285
286 358
            orca_state.activeScript.deactivate()
287 2
        except:
288 2
            pass
289
290 360
        orca_state.activeScript = newScript
291
292 360
        try:
293 360
            orca_state.activeScript.activate()
294 2
        except:
295 2
            pass
296
297 1
    def _cleanupCache(self):
298
        """Looks for defunct accessible objects in the cache and removes them.
299
        """
300
301 148
        objectsRemoved = 0
302 5652
        for obj in atspi.Accessible._cache.values():
303 5504
            try:
304 5504
                if obj.state.count(atspi.Accessibility.STATE_DEFUNCT):
305 0
                    atspi.Accessible.deleteAccessible(obj)
306 0
                    objectsRemoved += 1
307
                else:
308
                    # Try to force a COMM_FAILURE
309
                    #
310 5504
                    obj.toString()
311 628
            except CORBA.COMM_FAILURE:
312 628
                atspi.Accessible.deleteAccessible(obj)
313 628
                objectsRemoved += 1
314
315 148
        debug.println(debug.LEVEL_FINEST,
316 148
                      "_cleanupCache: %d objects removed." % objectsRemoved)
317
318 1
    def _cleanupGarbage(self):
319
        """Cleans up garbage on the heap."""
320 0
        import gc
321 0
        gc.collect()
322 0
        for obj in gc.garbage:
323 0
            try:
324 0
                if isinstance(obj, atspi.Accessible):
325 0
                    gc.garbage.remove(obj)
326 0
                    obj.__del__()
327 0
            except:
328 0
                pass
329
330 1
    def _reclaimScripts(self):
331
        """Compares the list of known scripts to the list of known apps,
332
        deleting any scripts as necessary.
333
        """
334
335
        # Sometimes the desktop can become unavailable.  This happens
336
        # often when synaptic is used to load new modules (see the bug
337
        # report http://bugzilla.gnome.org/show_bug.cgi?id=342022).
338
        # So...if this happens, we'll just move along.  The next
339
        # successful call to _reclaimScripts will reclaim anything we
340
        # didn't reclaim this time.
341
        #
342 148
        try:
343 148
            apps = []
344 148
            desktop = self.registry.desktop
345 2865
            for i in range(0, desktop.childCount):
346 2717
                try:
347 2717
                    acc = desktop.getChildAtIndex(i)
348 2569
                    app = atspi.Accessible.makeAccessible(acc)
349 2569
                    if app:
350 2537
                        apps.insert(0, app)
351 148
                except:
352 148
                    pass
353
354 620
            for app in self._knownScripts.keys():
355 472
                if apps.count(app) == 0:
356 74
                    script = self._knownScripts[app]
357 74
                    self._deregisterEventListeners(script)
358
359
                    # Provide a bunch of hints to the garbage collector
360
                    # that we just don't care about this stuff any longer.
361
                    # Note the "app.app = None" - that helps remove a
362
                    # cycle of the application referring to itself.
363
                    #
364 74
                    del self._knownScripts[app]
365 74
                    app.app = None
366 74
                    del app
367 74
                    del script
368 0
        except:
369 0
            debug.printException(debug.LEVEL_FINEST)
370
371
    ########################################################################
372
    #                                                                      #
373
    # METHODS FOR KEEPING TRACK OF APPLICATION SETTINGS.                   #
374
    #                                                                      #
375
    ########################################################################
376
377 1
    def loadAppSettings(self, script):
378
        """Load the users application specific settings for an app.
379
380
        Arguments:
381
        - script: the current active script.
382
        """
383
384 357
        app = script.app
385 357
        settingsPackages = settings.settingsPackages
386 357
        moduleName = settings.getScriptModuleName(app)
387 357
        module = None
388
389 357
        if moduleName and len(moduleName):
390 714
            for package in settingsPackages:
391 357
                if len(package):
392 357
                    name = package + "." + moduleName
393
                else:
394 0
                    name = moduleName
395 357
                try:
396 357
                    debug.println(debug.LEVEL_FINEST,
397 357
                                  "Looking for settings at %s.py..." % name)
398 357
                    if not self._knownAppSettings.has_key(name):
399
                        self._knownAppSettings[name] = \
400 357
                            __import__(name, globals(), locals(), [''])
401 0
                    reload(self._knownAppSettings[name])
402 0
                    debug.println(debug.LEVEL_FINEST,
403 0
                                  "...found %s.py" % name)
404
405
                    # Setup the user's application specific key bindings.
406
                    # (if any).
407
                    #
408 0
                    if hasattr(self._knownAppSettings[name],
409 0
                               "overrideAppKeyBindings"):
410
                        script.overrideAppKeyBindings = \
411 0
                            self._knownAppSettings[name].overrideAppKeyBindings
412
                        script.keyBindings = \
413 0
                         self._knownAppSettings[name].overrideAppKeyBindings( \
414 0
                            script, script.keyBindings)
415
416
                    # Setup the user's application specific pronunciations
417
                    # (if any).
418
                    #
419 0
                    if hasattr(self._knownAppSettings[name],
420 0
                               "overridePronunciations"):
421
                        script.overridePronunciations = \
422 0
                            self._knownAppSettings[name].overridePronunciations
423
                        script.app_pronunciation_dict = \
424 0
                         self._knownAppSettings[name].overridePronunciations( \
425 0
                            script, script.app_pronunciation_dict)
426
427 0
                    break
428 357
                except ImportError:
429 357
                    debug.println(debug.LEVEL_FINEST,
430 357
                                  "...could not find %s.py" % name)
431
432
    ########################################################################
433
    #                                                                      #
434
    # METHODS FOR PRE-PROCESSING AND MASSAGING AT-SPI OBJECT EVENTS        #
435
    # for processing by the rest of Orca.                                  #
436
    #                                                                      #
437
    ########################################################################
438
439 1
    def _processKeyboardEvent(self, keyboardEvent):
440
        """Processes the given keyboard event based on the keybinding from the
441
        currently active script.
442
443
        Arguments:
444
        - keyboardEvent: an instance of input_event.KeyboardEvent
445
        """
446 193
        if orca_state.activeScript:
447 193
            try:
448 193
                orca_state.activeScript.processKeyboardEvent(keyboardEvent)
449 0
            except:
450 0
                debug.printException(debug.LEVEL_WARNING)
451 0
                debug.printStack(debug.LEVEL_WARNING)
452
453 1
    def _processBrailleEvent(self, brailleEvent):
454
        """Called whenever a cursor key is pressed on the Braille display.
455
456
        Arguments:
457
        - brailleEvent: an instance of input_event.BrailleEvent
458
        """
459 0
        if orca_state.activeScript:
460 0
            try:
461 0
                orca_state.activeScript.processBrailleEvent(brailleEvent)
462 0
            except:
463 0
                debug.printException(debug.LEVEL_WARNING)
464 0
                debug.printStack(debug.LEVEL_WARNING)
465
466 1
    def _processObjectEvent(self, event):
467
        """Handles all events destined for scripts.
468
469
        Arguments:
470
        - e: an at-spi event.
471
        """
472
473 13852
        debug.printObjectEvent(debug.LEVEL_FINEST, event)
474
475
        # [[[TODO: WDW - HACK to prevent gnome-panel from killing
476
        # itself.  It seems to do so after it issues some tool tip
477
        # events and Orca attempts to process them.  We're not really
478
        # doing anything with tool tips right now, so we just ignore
479
        # them.  Note that this is just a bandaid to the problem.  We
480
        # should do something better.  Please refer to bug 368626
481
        # http://bugzilla.gnome.org/show_bug.cgi?id=368626 to follow
482
        # this problem.]]]
483
        #
484 13852
        try:
485 13852
            if event.source.role == rolenames.ROLE_TOOL_TIP:
486
                # Check that it's okay to present tool tips. Always present
487
                # tooltips initiated by the user pressing Control-F1 on the
488
                # keyboard.
489
                #
490 0
                if isinstance(orca_state.lastInputEvent, \
491 0
                              input_event.KeyboardEvent):
492 0
                    if not orca_state.lastNonModifierKeyEvent.event_string == "F1":
493 0
                        return
494
495
                    # Mouse move events don't update orca_state.lastInputEvent
496
                    # so it's possible the user accidentally nudged the
497
                    # mouse and generated another tooltip event. If the
498
                    # current time minus the last keyboard event time is
499
                    # greater than 0.2 seconds, than just ignore this tooltip
500
                    # event.
501
                    #
502 0
                    currentTime =  time.time()
503 0
                    if (currentTime - orca_state.lastInputEvent.time) > 0.2:
504 0
                        return
505 0
                elif not settings.presentToolTips:
506 0
                    return
507 0
        except:
508 0
            pass
509
510
        # Reclaim (delete) any scripts when desktop children go away.
511
        # The idea here is that a desktop child is an app. We also
512
        # generally do not like object:children-changed:remove events,
513
        # either.
514
        #
515 13852
        if event.type == "object:children-changed:remove":
516 148
            if event.source == atspi.Accessible.makeAccessible(
517 148
                                   self.registry.desktop):
518 148
                self._reclaimScripts()
519 148
                self._cleanupCache()
520 148
                if settings.debugMemoryUsage:
521 0
                    self._cleanupGarbage()
522 148
            return
523
524 13704
        try:
525
            # We don't want to touch a defunct object.  It's useless and it
526
            # can cause hangs.
527
            #
528 13704
            if event.source \
529 13704
               and event.source.state.count(atspi.Accessibility.STATE_DEFUNCT):
530 819
                debug.println(debug.LEVEL_FINEST,
531 819
                              "IGNORING DEFUNCT OBJECT")
532 819
                atspi.Accessible.deleteAccessible(event.source)
533 819
                return
534
535 12885
            if (not debug.eventDebugFilter) \
536 0
                or (debug.eventDebugFilter \
537 0
                    and debug.eventDebugFilter.match(event.type)):
538 12885
                if not event.type.startswith("mouse:"):
539 12885
                    debug.printDetails(debug.LEVEL_FINEST, "    ", event.source)
540
541 0
        except CORBA.COMM_FAILURE:
542 0
            debug.printException(debug.LEVEL_WARNING)
543 0
            debug.println(debug.LEVEL_FINEST,
544 0
                          "COMM_FAILURE above while processing event: " \
545 0
                          + event.type)
546 0
        except CORBA.OBJECT_NOT_EXIST:
547 0
            debug.printException(debug.LEVEL_WARNING)
548 0
            debug.println(debug.LEVEL_WARNING,
549 0
                          "OBJECT_NOT_EXIST above while processing event: " \
550 0
                          + event.type)
551 0
            atspi.Accessible.deleteAccessible(event.source)
552 0
            return
553 0
        except:
554 0
            debug.printException(debug.LEVEL_WARNING)
555 0
            return
556
557 12885
        if not event.source:
558 0
            debug.println(debug.LEVEL_WARNING,
559 0
                          "ERROR: received an event with no source.")
560 0
            return
561
562
        # We can sometimes get COMM_FAILURES even if the object has not
563
        # gone away.  This happens a lot with the Java access bridge.
564
        # So...we will try a few times before giving up.
565
        #
566
        # [[[TODO: WDW - might want to consider re-introducing the reload
567
        # feature of scripts somewhere around here.  I pulled it out as
568
        # part of the big refactor to make scripts object-oriented. Logged
569
        # as bugzilla bug 319777.]]]
570
        #
571 12885
        retryCount = 0
572 12885
        oldLocusOfFocus = orca_state.locusOfFocus
573 12885
        try:
574
            # If we've received a mouse event, then don't try to get
575
            # event.source.app because the top most parent is of role
576
            # unknown, which will cause an ERROR message to be displayed.
577
            # See Orca bug #409731 for more details.
578
            #
579 12885
            if not event.type.startswith("mouse:"):
580 12885
                s = self._getScript(event.source.app)
581
            else:
582 0
                s = orca_state.activeScript
583 12885
            if not s:
584 0
                return
585 0
        except:
586 0
            s = None
587 0
            debug.printException(debug.LEVEL_WARNING)
588 0
            debug.println(debug.LEVEL_WARNING,
589 0
                          "ERROR: received an event, but Script is None")
590
591 12885
        while s and retryCount <= s.commFailureAttemptLimit:
592 13070
            try:
593 13070
                if not event.source.state.count( \
594 13070
                                      atspi.Accessibility.STATE_ICONIFIED):
595
596
                    # [[[TODO: WDW - HACK we look for frame that get
597
                    # focus: as a means to detect active scripts
598
                    # because yelp does this.  Actually, yelp is a bit
599
                    # odd in that it calls itself 'yelp' then changes
600
                    # its application name and id to the Gecko toolkit
601
                    # in use, and then issues a focus: event on the
602
                    # main window, which is a frame.]]]
603
                    #
604 13070
                    if (event.type == "window:activate") \
605 12713
                       or ((event.type == "focus:") \
606 346
                           and (event.source.role == rolenames.ROLE_FRAME)):
607
608
                        # We'll let someone else decide if it's important
609
                        # to stop speech or not.
610
                        #speech.stop()
611 357
                        debug.println(debug.LEVEL_FINE, "ACTIVE SCRIPT: " \
612 357
                                      + orca_state.activeScript.name)
613
614 357
                        self.setActiveScript(self._getScript(event.source.app))
615
616
                        # Load in the application specific settings for the
617
                        # app for this event (if found).
618
                        #
619 357
                        appSettings = self.loadAppSettings( \
620 357
                                                      orca_state.activeScript)
621
622
                        # Tell BrlTTY which commands we care about.
623
                        #
624 357
                        braille.setupKeyRanges(\
625 357
                            orca_state.activeScript.brailleBindings.keys())
626
627 13070
                    s.processObjectEvent(event)
628 12848
                    if retryCount:
629 0
                        debug.println(debug.LEVEL_WARNING,
630 0
                                      "  SUCCEEDED AFTER %d TRIES" % retryCount)
631 12848
                break
632 222
            except CORBA.COMM_FAILURE:
633 222
                debug.printException(debug.LEVEL_WARNING)
634 222
                debug.println(debug.LEVEL_WARNING,
635 222
                              "COMM_FAILURE above while processing: " \
636 222
                              + event.type)
637 222
                retryCount += 1
638 222
                if retryCount <= s.commFailureAttemptLimit:
639
                    # We want the old locus of focus to be reset so
640
                    # the proper stuff will be spoken if the locus
641
                    # of focus changed during our last attempt at
642
                    # handling this event.
643
                    #
644 185
                    orca_state.locusOfFocus = oldLocusOfFocus
645 185
                    debug.println(debug.LEVEL_WARNING,
646 185
                                  "  TRYING AGAIN (%d)" % retryCount)
647 185
                    time.sleep(s.commFailureWaitTime)
648
                else:
649 37
                    debug.println(debug.LEVEL_WARNING,
650 37
                                  "  GIVING UP AFTER %d TRIES" \
651 37
                                  % (retryCount - 1))
652 37
                    atspi.Accessible.deleteAccessible(event.source)
653 0
            except:
654 0
                debug.printException(debug.LEVEL_WARNING)
655 0
                break
656
657 1
    def _enqueueEvent(self, e):
658
        """Handles all events destined for scripts.
659
660
        Arguments:
661
        - e: an at-spi event.
662
        """
663
664 25766
        if settings.debugEventQueue:
665 0
            if self._enqueueEventCount:
666 0
                debug.println(debug.LEVEL_ALL,
667 0
                              "focus_tracking_presenter._enqueueEvent has " \
668
                              "been entered before exiting (count = %d)" \
669 0
                              % self._enqueueEventCount)
670 0
            self._enqueueEventCount += 1
671
672 25766
        event = None
673 25766
        if isinstance(e, input_event.KeyboardEvent):
674 193
            if e.type == atspi.Accessibility.KEY_PRESSED_EVENT:
675 97
                debug.println(debug.LEVEL_ALL,
676 97
                              "----------> QUEUEING KEYPRESS '%s' (%d)"
677 97
                              % (e.event_string, e.hw_code))
678 96
            elif e.type == atspi.Accessibility.KEY_RELEASED_EVENT:
679 96
                debug.println(debug.LEVEL_ALL,
680 96
                              "----------> QUEUEING KEYRELEASE '%s' (%d)"
681 96
                              % (e.event_string, e.hw_code))
682 193
            event = e
683 25573
        elif isinstance(e, input_event.BrailleEvent):
684 0
            debug.println(debug.LEVEL_ALL,
685 0
                          "----------> QUEUEING BRAILLE COMMAND %d" % e.event)
686 0
            event = e
687
        else:
688
            # We ignore defunct objects and let the atspi module take
689
            # care of them for us.
690
            #
691 25573
            if (e.type == "object:state-changed:defunct"):
692 2457
                if settings.debugEventQueue:
693 0
                    self._enqueueEventCount -= 1
694 2457
                return
695
696
            # We also generally do not like
697
            # object:property-change:accessible-parent events because
698
            # they indicate something is now whacked with the
699
            # hierarchy, so we just ignore them and let the atspi
700
            # module take care of it for us.
701
            #
702 23116
            if e.type == "object:property-change:accessible-parent":
703 3118
                if settings.debugEventQueue:
704 0
                    self._enqueueEventCount -= 1
705 3118
                return
706
707
            # At this point in time, we only care when objects are
708
            # removed from the desktop.
709
            #
710 19998
            if (e.type == "object:children-changed:remove") \
711 6294
                and (e.source != self.registry.desktop):
712 6146
                if settings.debugEventQueue:
713 0
                    self._enqueueEventCount -= 1
714 6146
                return
715
716
            # We create the event here because it will ref everything
717
            # we want it to ref, thus allowing things to survive until
718
            # they are processed on the gidle thread.
719
            #
720
            # If the event doesn't have a source or that source is not marked
721
            # valid, then we don't care about this event. Just return.
722
            #
723 13852
            event = atspi.Event(e)
724 13852
            if not event.source or not event.source.valid:
725 0
                debug.println(debug.LEVEL_FINEST,
726 0
                      "---------> IGNORING INVALID EVENT %s" % e.type)
727 0
                if settings.debugEventQueue:
728 0
                    self._enqueueEventCount -= 1
729 0
                return
730
731 13852
            if (not debug.eventDebugFilter) \
732 0
                or (debug.eventDebugFilter \
733 0
                    and debug.eventDebugFilter.match(e.type)):
734 13852
                debug.println(debug.LEVEL_ALL,
735 13852
                              "---------> QUEUEING EVENT %s" % e.type)
736
737 14045
        if event:
738 14045
            if settings.debugEventQueue:
739 0
                debug.println(debug.LEVEL_ALL,
740 0
                              "           acquiring lock...")
741 14045
            self._gidleLock.acquire()
742 14045
            if settings.debugEventQueue:
743 0
                debug.println(debug.LEVEL_ALL,
744 0
                              "           ...acquired")
745 0
                debug.println(debug.LEVEL_ALL,
746 0
                              "           calling queue.put...")
747 0
                debug.println(debug.LEVEL_ALL,
748 0
                              "           (full=%s)" % self._eventQueue.full())
749 14045
            self._eventQueue.put(event)
750 14045
            if settings.debugEventQueue:
751 0
                debug.println(debug.LEVEL_ALL,
752 0
                              "           ...put complete")
753 14045
            if settings.asyncMode and (not self._gidleId):
754 1818
                if settings.gilSleepTime:
755 1818
                    time.sleep(settings.gilSleepTime)
756 1818
                self._gidleId = gobject.idle_add(self._dequeueEvent)
757
758 14045
            if settings.debugEventQueue:
759 0
                debug.println(debug.LEVEL_ALL,
760 0
                              "           releasing lock...")
761 14045
            self._gidleLock.release()
762 14045
            if settings.debugEventQueue:
763 0
                debug.println(debug.LEVEL_ALL,
764 0
                              "           ...released")
765
766 14045
            if not settings.asyncMode:
767 0
                self._dequeueEvent()
768
769 14045
        if settings.debugEventQueue:
770 0
            self._enqueueEventCount -= 1
771
772
        # [[[TODO: HACK - on some hangs, we see the event queue growing,
773
        # but the thread to take the events off is hung.  We might be
774
        # able to 'recover' by quitting when we see this happen.]]]
775
        #
776
        #if self._eventQueue.qsize() > 500:
777
        #    print "Looks like something has hung.  Here's the threads:"
778
        #    for someThread in threading.enumerate():
779
        #        print someThread.getName(), someThread.isAlive()
780
        #    print "Quitting Orca."
781
        #    orca.shutdown()
782
783 1
    def _dequeueEvent(self):
784
        """Handles all events destined for scripts.  Called by the GTK
785
        idle thread.
786
        """
787
788 14045
        rerun = True
789
790 14045
        if settings.debugEventQueue:
791 0
            debug.println(debug.LEVEL_ALL,
792 0
                          "Entering focus_tracking_presenter._dequeueEvent" \
793 0
                          + " %d" % self._dequeueEventCount)
794 0
            self._dequeueEventCount += 1
795
796 14045
        try:
797 14045
            event = self._eventQueue.get_nowait()
798
799 14045
            if isinstance(event, input_event.KeyboardEvent):
800 193
                if event.type == atspi.Accessibility.KEY_PRESSED_EVENT:
801 97
                    debug.println(debug.LEVEL_ALL,
802 97
                                  "DEQUEUED KEYPRESS '%s' (%d) <----------" \
803 97
                                  % (event.event_string, event.hw_code))
804 97
                    pressRelease = "PRESS"
805 96
                elif event.type == atspi.Accessibility.KEY_RELEASED_EVENT:
806 96
                    debug.println(debug.LEVEL_ALL,
807 96
                                  "DEQUEUED KEYRELEASE '%s' (%d) <----------" \
808 96
                                  % (event.event_string, event.hw_code))
809 96
                    pressRelease = "RELEASE"
810 193
                debug.println(debug.eventDebugLevel,
811 193
                              "\nvvvvv PROCESS KEY %s EVENT %s vvvvv"\
812 193
                              % (pressRelease, event.event_string))
813 193
                self._processKeyboardEvent(event)
814 193
                debug.println(debug.eventDebugLevel,
815 193
                              "\n^^^^^ PROCESS KEY %s EVENT %s ^^^^^"\
816 193
                              % (pressRelease, event.event_string))
817 13852
            elif isinstance(event, input_event.BrailleEvent):
818 0
                debug.println(debug.LEVEL_ALL,
819 0
                              "DEQUEUED BRAILLE COMMAND %d <----------" \
820 0
                              % event.event)
821 0
                debug.println(debug.eventDebugLevel,
822 0
                              "\nvvvvv PROCESS BRAILLE EVENT %d vvvvv"\
823 0
                              % event.event)
824 0
                self._processBrailleEvent(event)
825 0
                debug.println(debug.eventDebugLevel,
826 0
                              "\n^^^^^ PROCESS BRAILLE EVENT %d ^^^^^"\
827 0
                              % event.event)
828
            else:
829 13852
                if (not debug.eventDebugFilter) \
830 0
                    or (debug.eventDebugFilter \
831 0
                        and debug.eventDebugFilter.match(event.type)):
832 13852
                    debug.println(debug.LEVEL_ALL,
833 13852
                                  "DEQUEUED EVENT %s <----------" \
834 13852
                                  % event.type)
835 13852
                    debug.println(debug.eventDebugLevel,
836 13852
                                  "\nvvvvv PROCESS OBJECT EVENT %s vvvvv" \
837 13852
                                  % event.type)
838 13852
                self._processObjectEvent(event)
839 13852
                if (not debug.eventDebugFilter) \
840 0
                    or (debug.eventDebugFilter \
841 0
                        and debug.eventDebugFilter.match(event.type)):
842 13852
                    debug.println(debug.eventDebugLevel,
843 13852
                                  "^^^^^ PROCESS OBJECT EVENT %s ^^^^^\n" \
844 13852
                                  % event.type)
845
846
            # [[[TODO: HACK - it would seem logical to only do this if we
847
            # discover the queue is empty, but this inroduces a hang for
848
            # some reason if done inside an acquire/release block for a
849
            # lock.  So...we do it here.]]]
850
            #
851 14045
            noFocus = (not orca_state.activeScript) \
852 14045
                      or ((not orca_state.locusOfFocus) \
853 431
                          and (self.noFocusTimestamp \
854 431
                               != orca_state.noFocusTimestamp))
855
856 14045
            self._gidleLock.acquire()
857 14045
            if self._eventQueue.empty():
858 1818
                if noFocus:
859 0
                    if settings.gilSleepTime:
860 0
                        time.sleep(settings.gilSleepTime)
861
                    # Translators: this is intended to be a short phrase to
862
                    # speak and braille to tell the user that no component
863
                    # has keyboard focus.
864
                    #
865 0
                    message = _("No focus")
866 0
                    if settings.brailleVerbosityLevel == \
867 0
                        settings.VERBOSITY_LEVEL_VERBOSE:
868 0
                        braille.displayMessage(message)
869 0
                    if settings.speechVerbosityLevel == \
870 0
                        settings.VERBOSITY_LEVEL_VERBOSE:
871 0
                        speech.speak(message)
872 0
                    self.noFocusTimestamp = orca_state.noFocusTimestamp
873 1818
                self._gidleId = 0
874 1818
                rerun = False # destroy and don't call again
875 14045
            self._gidleLock.release()
876 0
        except Queue.Empty:
877 0
            debug.println(debug.LEVEL_SEVERE,
878 0
                          "focus_tracking_presenter:_dequeueEvent: " \
879 0
                          + " the event queue is empty!")
880 0
        except:
881 0
            debug.printException(debug.LEVEL_SEVERE)
882
883 14045
        if settings.debugEventQueue:
884 0
            self._dequeueEventCount -= 1
885 0
            debug.println(debug.LEVEL_ALL,
886 0
                          "Leaving focus_tracking_presenter._dequeueEvent" \
887 0
                          + " %d" % self._dequeueEventCount)
888
889 14045
        return rerun
890
891 1
    def processKeyboardEvent(self, keyboardEvent):
892
        """Processes the given keyboard event based on the keybinding from the
893
        currently active script. This method is called synchronously from the
894
        at-spi registry and should be performant.  In addition, it must return
895
        True if it has consumed the event (and False if not).
896
897
        Arguments:
898
        - keyboardEvent: an instance of input_event.KeyboardEvent
899
900
        Returns True if the event should be consumed.
901
        """
902
903 3579
        if orca_state.activeScript \
904 3579
           and orca_state.activeScript.consumesKeyboardEvent(keyboardEvent):
905 193
            self._enqueueEvent(keyboardEvent)
906 193
            return True
907
        else:
908 3386
            return False
909
910 1
    def processBrailleEvent(self, brailleEvent):
911
        """Called whenever a cursor key is pressed on the Braille display.
912
913
        Arguments:
914
        - brailleEvent: an instance of input_event.BrailleEvent
915
916
        Returns True if the command was consumed; otherwise False
917
        """
918
919 0
        if orca_state.activeScript \
920 0
           and orca_state.activeScript.consumesBrailleEvent(brailleEvent):
921 0
            self._enqueueEvent(brailleEvent)
922 0
            return True
923
        else:
924 0
            return False
925
926 1
    def locusOfFocusChanged(self, event, oldLocusOfFocus, newLocusOfFocus):
927
        """Called when the visual object with focus changes.
928
929
        Arguments:
930
        - event: if not None, the Event that caused the change
931
        - oldLocusOfFocus: Accessible that is the old locus of focus
932
        - newLocusOfFocus: Accessible that is the new locus of focus
933
        """
934
935 903
        if orca_state.activeScript:
936 903
            orca_state.activeScript.locusOfFocusChanged(event,
937 903
                                                        oldLocusOfFocus,
938 903
                                                        newLocusOfFocus)
939
940 1
    def visualAppearanceChanged(self, event, obj):
941
        """Called when the visual appearance of an object changes.
942
        This method should not be called for objects whose visual
943
        appearance changes solely because of focus -- locusOfFocusChanged
944
        is used for that.  Instead, it is intended mostly for objects
945
        whose notional 'value' has changed, such as a checkbox changing
946
        state, a progress bar advancing, a slider moving, text inserted,
947
        caret moved, etc.
948
949
        Arguments:
950
        - event: if not None, the Event that caused this to happen
951
        - obj: the Accessible whose visual appearance changed.
952
        """
953
954 841
        if orca_state.activeScript:
955 841
            orca_state.activeScript.visualAppearanceChanged(event, obj)
956
957 1
    def _saveAppStates(self):
958
        """Saves script and application state information."""
959 1
        self._appStateInfo = []
960 5
        for script in self._knownScripts.values():
961 4
            self._appStateInfo.append([script.app, script.getAppState()])
962
963 1
    def _restoreAppStates(self):
964
        """Restores script and application state information."""
965 1
        try:
966 1
            for [app, appState] in self._appStateInfo:
967 0
                script = self._getScript(app)
968 0
                script.setAppState(appState)
969 1
        except:
970 1
            pass
971
972 1
        self._appStateInfo = None
973
974 1
    def activate(self):
975
        """Called when this presentation manager is activated."""
976
977 1
        self._listenerCounts = {}
978 1
        self._knownScripts   = {}
979 1
        self._knownAppSettings = {}
980 1
        self._oldAppSettings = None
981 1
        self._defaultScript  = None
982
983 1
        self._restoreAppStates()
984
985 1
        self.setActiveScript(self._getScript(None))
986
987
        # Tell BrlTTY which commands we care about.
988
        #
989 1
        braille.setupKeyRanges(orca_state.activeScript.brailleBindings.keys())
990
991 1
        self._registerEventListener("window:activate")
992 1
        self._registerEventListener("window:deactivate")
993 1
        self._registerEventListener("object:children-changed:remove")
994
995 1
        win = orca_state.activeScript.findActiveWindow()
996 1
        if win:
997
            # Generate a fake window activation event so the application
998
            # can tell the user about itself.
999
            #
1000 1
            e = atspi.Event()
1001 1
            e.source   = win._acc
1002 1
            e.type     = "window:activate"
1003 1
            e.detail1  = 0
1004 1
            e.detail2  = 0
1005 1
            e.any_data = None
1006 1
            self._enqueueEvent(e)
1007
1008 1
    def deactivate(self):
1009
        """Called when this presentation manager is deactivated."""
1010
1011 1
        self._saveAppStates()
1012
1013 28
        for eventType in self._listenerCounts.keys():
1014 27
            self.registry.deregisterEventListener(self._enqueueEvent,
1015 27
                                                  eventType)
1016 1
        self._listenerCounts = {}
1017 1
        self._knownScripts   = {}
1018 1
        self._knownAppSettings = {}
1019 1
        self._oldAppSettings = None
1020 1
        self._defaultScript  = None
1021
1022 1
        self.setActiveScript(None)