Coverage Report - orca.focus_tracking_presenter

ModuleCoverage %
orca.focus_tracking_presenter
71%
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 2126 2007-03-06 21:35:17Z richb $"
23 1
__version__   = "$Revision: 2126 $"
24 1
__date__      = "$Date: 2007-03-06 13:35:17 -0800 (Tue, 06 Mar 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
    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
        orca_state.activeScript = 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 1537
        if self._listenerCounts.has_key(eventType):
97 1510
            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 1378
        self._listenerCounts[eventType] -= 1
111 1378
        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 1593
        for eventType in script.listeners.keys():
125 1534
            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 1431
        for eventType in script.listeners.keys():
136 1378
            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 58
        script = None
156
157 58
        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 58
            scriptPackages = settings.scriptPackages
167
168 58
            moduleName = settings.getScriptModuleName(app)
169 58
            module = None
170
171 58
            if moduleName and len(moduleName):
172 150
                for package in scriptPackages:
173 116
                    if len(package):
174 116
                        name = package + "." + moduleName
175
                    else:
176 0
                        name = moduleName
177 116
                    try:
178 116
                        debug.println(debug.LEVEL_FINEST,
179 116
                                      "Looking for script at %s.py..." % name)
180 116
                        module = __import__(name,
181 116
                                            globals(),
182 116
                                            locals(),
183 116
                                            [''])
184 24
                        debug.println(debug.LEVEL_FINEST,
185 24
                                      "...found %s.py" % name)
186 24
                        break
187 92
                    except ImportError:
188 92
                        debug.println(debug.LEVEL_FINEST,
189 92
                                      "...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 58
            if module:
195 24
                try:
196 24
                    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 58
        if (not script) \
211
            and app \
212
            and app.__dict__.has_key("toolkitName") \
213
            and app.toolkitName:
214
215 10
            try:
216 10
                debug.println(
217
                    debug.LEVEL_FINE,
218 10
                    "Looking for toolkit script %s.py..." % app.toolkitName)
219 10
                module = __import__(app.toolkitName,
220 10
                                    globals(),
221 10
                                    locals(),
222 10
                                    [''])
223 0
                script = module.Script(app)
224 0
                debug.println(debug.LEVEL_FINE,
225 0
                              "...found %s.py" % name)
226 10
            except ImportError:
227 10
                debug.println(
228
                    debug.LEVEL_FINE,
229 10
                    "...could not find %s.py" % app.toolkitName)
230 0
            except:
231 0
                debug.printException(debug.LEVEL_SEVERE)
232 0
                debug.println(
233
                    debug.LEVEL_SEVERE,
234 0
                    "While attempting to import %s" % app.toolkitName)
235
236 58
        if not script:
237 34
            script = default.Script(app)
238
239 58
        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 20598
        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 20597
        elif self._knownScripts.has_key(app):
260 20539
            script = self._knownScripts[app]
261
        else:
262 58
            script = self._createScript(app)
263 58
            self._knownScripts[app] = script
264 58
            self._registerEventListeners(script)
265
266 20598
        return script
267
268 1
    def _reclaimScripts(self):
269
        """Compares the list of known scripts to the list of known apps,
270
        deleting any scripts as necessary.
271
        """
272
273
        # Sometimes the desktop can become unavailable.  This happens
274
        # often when synaptic is used to load new modules (see the bug
275
        # report http://bugzilla.gnome.org/show_bug.cgi?id=342022).
276
        # So...if this happens, we'll just move along.  The next
277
        # successful call to _reclaimScripts will reclaim anything we
278
        # didn't reclaim this time.
279
        #
280 96
        try:
281 96
            apps = []
282 96
            desktop = self.registry.desktop
283 1261
            for i in range(0, desktop.childCount):
284 1165
                acc = desktop.getChildAtIndex(i)
285 1165
                app = atspi.Accessible.makeAccessible(acc)
286 1165
                if app:
287 1159
                    apps.insert(0, app)
288
289 463
            for app in self._knownScripts.keys():
290 367
                if apps.count(app) == 0:
291 53
                    script = self._knownScripts[app]
292 53
                    self._deregisterEventListeners(script)
293 53
                    del self._knownScripts[app]
294 53
                    del script
295 0
        except:
296 0
            debug.printException(debug.LEVEL_FINEST)
297
298
    ########################################################################
299
    #                                                                      #
300
    # METHODS FOR KEEPING TRACK OF APPLICATION SETTINGS.                   #
301
    #                                                                      #
302
    ########################################################################
303
304 1
    def loadAppSettings(self, app):
305
        """Load the users application specific settings for an app.
306
307
        Arguments:
308
        - app: the Python app
309
        """
310
311 194
        settingsPackages = settings.settingsPackages
312 194
        moduleName = settings.getScriptModuleName(app)
313 194
        module = None
314
315 194
        if moduleName and len(moduleName):
316 388
            for package in settingsPackages:
317 194
                if len(package):
318 194
                    name = package + "." + moduleName
319
                else:
320 0
                    name = moduleName
321 194
                try:
322 194
                    debug.println(debug.LEVEL_FINEST,
323 194
                                  "Looking for settings at %s.py..." % name)
324 194
                    if self. _knownAppSettings.has_key(name):
325 0
                        reload(self._knownAppSettings[name])
326
                    else:
327 194
                        self._knownAppSettings[name] = \
328
                            __import__(name, globals(), locals(), [''])
329 0
                    debug.println(debug.LEVEL_FINEST,
330 0
                                  "...found %s.py" % name)
331 0
                    break
332 194
                except ImportError:
333 194
                    debug.println(debug.LEVEL_FINEST,
334 194
                                  "...could not find %s.py" % name)
335
336
    ########################################################################
337
    #                                                                      #
338
    # METHODS FOR PRE-PROCESSING AND MASSAGING AT-SPI OBJECT EVENTS        #
339
    # for processing by the rest of Orca.                                  #
340
    #                                                                      #
341
    ########################################################################
342
343 1
    def _processKeyboardEvent(self, keyboardEvent):
344
        """Processes the given keyboard event based on the keybinding from the
345
        currently active script.
346
347
        Arguments:
348
        - keyboardEvent: an instance of input_event.KeyboardEvent
349
        """
350 154
        if orca_state.activeScript:
351 154
            try:
352 154
                orca_state.activeScript.processKeyboardEvent(keyboardEvent)
353 0
            except:
354 0
                debug.printException(debug.LEVEL_WARNING)
355 0
                debug.printStack(debug.LEVEL_WARNING)
356
357 1
    def _processBrailleEvent(self, brailleEvent):
358
        """Called whenever a cursor key is pressed on the Braille display.
359
360
        Arguments:
361
        - brailleEvent: an instance of input_event.BrailleEvent
362
        """
363 0
        if orca_state.activeScript:
364 0
            try:
365 0
                orca_state.activeScript.processBrailleEvent(brailleEvent)
366 0
            except:
367 0
                debug.printException(debug.LEVEL_WARNING)
368 0
                debug.printStack(debug.LEVEL_WARNING)
369
370 1
    def _processObjectEvent(self, event):
371
        """Handles all events destined for scripts.
372
373
        Arguments:
374
        - e: an at-spi event.
375
        """
376
377 20864
        debug.printObjectEvent(debug.LEVEL_FINEST, event)
378
379
        # [[[TODO: WDW - HACK to prevent gnome-panel from killing
380
        # itself.  It seems to do so after it issues some tool tip
381
        # events and Orca attempts to process them.  We're not really
382
        # doing anything with tool tips right now, so we just ignore
383
        # them.  Note that this is just a bandaid to the problem.  We
384
        # should do something better.  Please refer to bug 368626
385
        # http://bugzilla.gnome.org/show_bug.cgi?id=368626 to follow
386
        # this problem.]]]
387
        #
388 20864
        try:
389 20864
            if event.source.role == rolenames.ROLE_TOOL_TIP:
390 6
                return
391 3
        except:
392 3
            pass
393
394
        # Reclaim (delete) any scripts when desktop children go away.
395
        # The idea here is that a desktop child is an app. We also
396
        # generally do not like object:children-changed:remove events,
397
        # either.
398
        #
399 20858
        if event.type == "object:children-changed:remove":
400 96
            if event.source == atspi.Accessible.makeAccessible(
401
                                   self.registry.desktop):
402 96
                self._reclaimScripts()
403
                #import gc
404
                #gc.collect()
405
                #print "In process, garbage:", gc.garbage
406
                #for obj in gc.garbage:
407
                #    print "   referrers:", obj, gc.get_referrers(obj)
408 96
            return
409
410 20762
        try:
411
            # We don't want to touch a defunct object.  It's useless and it
412
            # can cause hangs.
413
            #
414 20762
            if event.source \
415
               and event.source.state.count(atspi.Accessibility.STATE_DEFUNCT):
416 359
                debug.println(debug.LEVEL_FINEST,
417 359
                              "IGNORING DEFUNCT OBJECT")
418 359
                atspi.Accessible.deleteAccessible(event.source)
419 359
                return
420
421 20403
            if (not debug.eventDebugFilter) \
422
                or (debug.eventDebugFilter \
423
                    and debug.eventDebugFilter.match(event.type)):
424 20403
                debug.printDetails(debug.LEVEL_FINEST, "    ", event.source)
425
426 0
        except CORBA.COMM_FAILURE:
427 0
            debug.printException(debug.LEVEL_WARNING)
428 0
            debug.println(debug.LEVEL_FINEST,
429 0
                          "COMM_FAILURE above while processing event: " \
430
                          + event.type)
431 0
        except CORBA.OBJECT_NOT_EXIST:
432 0
            debug.printException(debug.LEVEL_WARNING)
433 0
            debug.println(debug.LEVEL_WARNING,
434 0
                          "OBJECT_NOT_EXIST above while processing event: " \
435
                          + event.type)
436 0
            atspi.Accessible.deleteAccessible(event.source)
437 0
            return
438 0
        except:
439 0
            debug.printException(debug.LEVEL_WARNING)
440 0
            return
441
442 20403
        if not event.source:
443 0
            debug.println(debug.LEVEL_WARNING,
444 0
                          "ERROR: received an event with no source.")
445 0
            return
446
447
        # We can sometimes get COMM_FAILURES even if the object has not
448
        # gone away.  This happens a lot with the Java access bridge.
449
        # So...we will try a few times before giving up.
450
        #
451
        # [[[TODO: WDW - might want to consider re-introducing the reload
452
        # feature of scripts somewhere around here.  I pulled it out as
453
        # part of the big refactor to make scripts object-oriented. Logged
454
        # as bugzilla bug 319777.]]]
455
        #
456 20403
        retryCount = 0
457 20403
        oldLocusOfFocus = orca_state.locusOfFocus
458 20403
        try:
459 20403
            s = self._getScript(event.source.app)
460 0
        except:
461 0
            s = None
462 0
            debug.printException(debug.LEVEL_WARNING)
463 0
            debug.println(debug.LEVEL_WARNING,
464 0
                          "ERROR: received an event, but Script is None")
465
466 20585
        while s and retryCount <= s.commFailureAttemptLimit:
467 20555
            try:
468 20555
                if not event.source.state.count( \
469
                                      atspi.Accessibility.STATE_ICONIFIED):
470
471
                    # [[[TODO: WDW - HACK we look for frame that get
472
                    # focus: as a means to detect active scripts
473
                    # because yelp does this.  Actually, yelp is a bit
474
                    # odd in that it calls itself 'yelp' then changes
475
                    # its application name and id to the Gecko toolkit
476
                    # in use, and then issues a focus: event on the
477
                    # main window, which is a frame.]]]
478
                    #
479 20555
                    if (event.type == "window:activate") \
480
                       or ((event.type == "focus:") \
481
                           and (event.source.role == rolenames.ROLE_FRAME)):
482
483
                        # We'll let someone else decide if it's important
484
                        # to stop speech or not.
485
                        #speech.stop()
486 194
                        orca_state.activeScript = \
487
                            self._getScript(event.source.app)
488 194
                        debug.println(debug.LEVEL_FINE, "ACTIVE SCRIPT: " \
489
                                      + orca_state.activeScript.name)
490
491
                        # If old ("factory") settings don't exist yet, save
492
                        # a set, else restore the old application settings.
493
                        #
494 194
                        if not self._oldAppSettings:
495 1
                            self._oldAppSettings = \
496
                                  orca_state.activeScript.saveOldAppSettings()
497
                        else:
498 193
                            orca_state.activeScript.restoreOldAppSettings( \
499
                                                         self._oldAppSettings)
500
501
                        # Load in the application specific settings for the
502
                        # app for this event (if found).
503
                        #
504 194
                        appSettings = self.loadAppSettings(event.source.app)
505
506
                        # Tell BrlTTY which commands we care about.
507
                        #
508 194
                        braille.setupKeyRanges(\
509
                            orca_state.activeScript.brailleBindings.keys())
510
511 20555
                    s.processObjectEvent(event)
512 20350
                    if retryCount:
513 2
                        debug.println(debug.LEVEL_WARNING,
514 2
                                      "  SUCCEEDED AFTER %d TRIES" % retryCount)
515 20350
                break
516 205
            except CORBA.COMM_FAILURE:
517 182
                debug.printException(debug.LEVEL_WARNING)
518 182
                debug.println(debug.LEVEL_WARNING,
519 182
                              "COMM_FAILURE above while processing: " \
520
                              + event.type)
521 182
                retryCount += 1
522 182
                if retryCount <= s.commFailureAttemptLimit:
523
                    # We want the old locus of focus to be reset so
524
                    # the proper stuff will be spoken if the locus
525
                    # of focus changed during our last attempt at
526
                    # handling this event.
527
                    #
528 152
                    orca_state.locusOfFocus = oldLocusOfFocus
529 152
                    debug.println(debug.LEVEL_WARNING,
530 152
                                  "  TRYING AGAIN (%d)" % retryCount)
531 152
                    time.sleep(s.commFailureWaitTime)
532
                else:
533 30
                    debug.println(debug.LEVEL_WARNING,
534 30
                                  "  GIVING UP AFTER %d TRIES" \
535
                                  % (retryCount - 1))
536 30
                    atspi.Accessible.deleteAccessible(event.source)
537 23
            except:
538 23
                debug.printException(debug.LEVEL_WARNING)
539 23
                break
540
541 1
    def _enqueueEvent(self, e):
542
        """Handles all events destined for scripts.
543
544
        Arguments:
545
        - e: an at-spi event.
546
        """
547
548
        # Uncomment these lines if you want to see what it's like without
549
        # the queue.
550
        #
551
        #if isinstance(e, input_event.KeyboardEvent):
552
        #    self._processKeyboardEvent(e)
553
        #elif isinstance(e, input_event.BrailleEvent):
554
        #    self._processBrailleEvent(e)
555
        #else:
556
        #    self._processObjectEvent(atspi.Event(e))
557
        #return
558
559 27120
        if settings.debugEventQueue:
560 0
            if self._enqueueEventCount:
561 0
                debug.println(debug.LEVEL_ALL,
562 0
                              "focus_tracking_presenter._enqueueEvent has " \
563
                              "been entered before exiting (count = %d)" \
564
                              % self._enqueueEventCount)
565 0
            self._enqueueEventCount += 1
566
567 27120
        event = None
568 27120
        if isinstance(e, input_event.KeyboardEvent):
569 154
            if e.type == atspi.Accessibility.KEY_PRESSED_EVENT:
570 77
                debug.println(debug.LEVEL_FINEST,
571 77
                              "----------> QUEUEING KEYPRESS '%s' (%d)"
572
                              % (e.event_string, e.hw_code))
573 77
            elif e.type == atspi.Accessibility.KEY_RELEASED_EVENT:
574 77
                debug.println(debug.LEVEL_FINEST,
575 77
                              "----------> QUEUEING KEYRELEASE '%s' (%d)"
576
                              % (e.event_string, e.hw_code))
577 154
            event = e
578 26966
        elif isinstance(e, input_event.BrailleEvent):
579 0
            debug.println(debug.LEVEL_FINEST,
580 0
                          "----------> QUEUEING BRAILLE COMMAND %d" % e.event)
581 0
            event = e
582
        else:
583
            # We ignore defunct objects and let the atspi module take
584
            # care of them for us.
585
            #
586 26966
            if (e.type == "object:state-changed:defunct"):
587 1490
                if settings.debugEventQueue:
588 0
                    self._enqueueEventCount -= 1
589 1490
                return
590
591
            # We also generally do not like
592
            # object:property-change:accessible-parent events because
593
            # they indicate something is now whacked with the
594
            # hierarchy, so we just ignore them and let the atspi
595
            # module take care of it for us.
596
            #
597 25476
            if e.type == "object:property-change:accessible-parent":
598 2211
                if settings.debugEventQueue:
599 0
                    self._enqueueEventCount -= 1
600 2211
                return
601
602
            # At this point in time, we only care when objects are
603
            # removed from the desktop.
604
            #
605 23265
            if (e.type == "object:children-changed:remove") \
606
                and (e.source != self.registry.desktop):
607 2370
                if settings.debugEventQueue:
608 0
                    self._enqueueEventCount -= 1
609 2370
                return
610
611
            # We create the event here because it will ref everything
612
            # we want it to ref, thus allowing things to survive until
613
            # they are processed on the gidle thread.
614
            #
615
            # If the event doesn't have a source or that source is not marked
616
            # valid, then we don't care about this event. Just return.
617
            #
618 20895
            event = atspi.Event(e)
619 20895
            if not event.source or not event.source.valid:
620 0
                debug.println(debug.LEVEL_FINEST,
621 0
                      "---------> IGNORING INVALID EVENT %s" % e.type)
622 0
                if settings.debugEventQueue:
623 0
                    self._enqueueEventCount -= 1
624 0
                return
625
626 20895
            if (not debug.eventDebugFilter) \
627
                or (debug.eventDebugFilter \
628
                    and debug.eventDebugFilter.match(e.type)):
629 20895
                debug.println(debug.LEVEL_FINEST,
630 20895
                              "---------> QUEUEING EVENT %s" % e.type)
631
632 21049
        if event:
633 21049
            if settings.debugEventQueue:
634 0
                debug.println(debug.LEVEL_ALL,
635 0
                              "           acquiring lock...")
636 21049
            self._gidleLock.acquire()
637 21049
            if settings.debugEventQueue:
638 0
                debug.println(debug.LEVEL_ALL,
639 0
                              "           ...acquired")
640 21049
            if settings.debugEventQueue:
641 0
                debug.println(debug.LEVEL_ALL,
642 0
                              "           calling queue.put...")
643 21049
            if settings.debugEventQueue:
644 0
                debug.println(debug.LEVEL_ALL,
645 0
                              "           (full=%s)" % self._eventQueue.full())
646 21049
            self._eventQueue.put(event)
647 21049
            if settings.debugEventQueue:
648 0
                debug.println(debug.LEVEL_ALL,
649 0
                              "           ...put complete")
650 21049
            if not self._gidleId:
651 4009
                if settings.gilSleepTime:
652 4009
                    time.sleep(settings.gilSleepTime)
653 4009
                self._gidleId = gobject.idle_add(self._dequeueEvent)
654
655 21049
            if settings.debugEventQueue:
656 0
                debug.println(debug.LEVEL_ALL,
657 0
                              "           releasing lock...")
658 21049
            self._gidleLock.release()
659 21049
            if settings.debugEventQueue:
660 0
                debug.println(debug.LEVEL_ALL,
661 0
                              "           ...released")
662 21049
        if settings.debugEventQueue:
663 0
            self._enqueueEventCount -= 1
664
665
        # [[[TODO: HACK - on some hangs, we see the event queue growing,
666
        # but the thread to take the events off is hung.  We might be
667
        # able to 'recover' by quitting when we see this happen.]]]
668
        #
669
        #if self._eventQueue.qsize() > 500:
670
        #    print "Looks like something has hung.  Here's the threads:"
671
        #    for someThread in threading.enumerate():
672
        #        print someThread.getName(), someThread.isAlive()
673
        #    print "Quitting Orca."
674
        #    orca.shutdown()
675
676 1
    def _dequeueEvent(self):
677
        """Handles all events destined for scripts.  Called by the GTK
678
        idle thread.
679
        """
680
681 21018
        rerun = True
682
683 21018
        if settings.debugEventQueue:
684 0
            debug.println(debug.LEVEL_ALL,
685 0
                          "Entering focus_tracking_presenter._dequeueEvent" \
686
                          + " %d" % self._dequeueEventCount)
687 0
            self._dequeueEventCount += 1
688
689 21018
        try:
690 21018
            event = self._eventQueue.get_nowait()
691
692 21018
            if isinstance(event, input_event.KeyboardEvent):
693 154
                if event.type == atspi.Accessibility.KEY_PRESSED_EVENT:
694 77
                    debug.println(debug.LEVEL_FINEST,
695 77
                                  "DEQUEUED KEYPRESS '%s' (%d) <----------" \
696
                                  % (event.event_string, event.hw_code))
697 77
                    pressRelease = "PRESS"
698 77
                elif event.type == atspi.Accessibility.KEY_RELEASED_EVENT:
699 77
                    debug.println(debug.LEVEL_FINEST,
700 77
                                  "DEQUEUED KEYRELEASE '%s' (%d) <----------" \
701
                                  % (event.event_string, event.hw_code))
702 77
                    pressRelease = "RELEASE"
703 154
                debug.println(debug.eventDebugLevel,
704 154
                              "\nvvvvv PROCESS KEY %s EVENT %s vvvvv"\
705
                              % (pressRelease, event.event_string))
706 154
                self._processKeyboardEvent(event)
707 154
                debug.println(debug.eventDebugLevel,
708 154
                              "\n^^^^^ PROCESS KEY %s EVENT %s ^^^^^"\
709
                              % (pressRelease, event.event_string))
710 20864
            elif isinstance(event, input_event.BrailleEvent):
711 0
                debug.println(debug.LEVEL_FINEST,
712 0
                              "DEQUEUED BRAILLE COMMAND %d <----------" \
713
                              % event.event)
714 0
                debug.println(debug.eventDebugLevel,
715 0
                              "\nvvvvv PROCESS BRAILLE EVENT %d vvvvv"\
716
                              % event.event)
717 0
                self._processBrailleEvent(event)
718 0
                debug.println(debug.eventDebugLevel,
719 0
                              "\n^^^^^ PROCESS BRAILLE EVENT %d ^^^^^"\
720
                              % event.event)
721
            else:
722 20864
                if (not debug.eventDebugFilter) \
723
                    or (debug.eventDebugFilter \
724
                        and debug.eventDebugFilter.match(event.type)):
725 20864
                    debug.println(debug.LEVEL_FINEST,
726 20864
                                  "DEQUEUED EVENT %s <----------" \
727
                                  % event.type)
728 20864
                    debug.println(debug.eventDebugLevel,
729 20864
                                  "\nvvvvv PROCESS OBJECT EVENT %s vvvvv" \
730
                                  % event.type)
731 20864
                self._processObjectEvent(event)
732 20864
                if (not debug.eventDebugFilter) \
733
                    or (debug.eventDebugFilter \
734
                        and debug.eventDebugFilter.match(event.type)):
735 20864
                    debug.println(debug.eventDebugLevel,
736 20864
                                  "^^^^^ PROCESS OBJECT EVENT %s ^^^^^\n" \
737
                                  % event.type)
738
739
            # [[[TODO: HACK - it would seem logical to only do this if we
740
            # discover the queue is empty, but this inroduces a hang for
741
            # some reason if done inside an acquire/release block for a
742
            # lock.  So...we do it here.]]]
743
            #
744 21018
            noFocus = (not orca_state.activeScript) \
745
                      or ((not orca_state.locusOfFocus) \
746
                          and (self.noFocusTimestamp \
747
                               != orca_state.noFocusTimestamp))
748
749 21018
            self._gidleLock.acquire()
750 21018
            if self._eventQueue.empty():
751 4008
                if noFocus:
752 0
                    if settings.gilSleepTime:
753 0
                        time.sleep(settings.gilSleepTime)
754 0
                    message = _("No focus")
755 0
                    if settings.brailleVerbosityLevel == \
756
                        settings.VERBOSITY_LEVEL_VERBOSE:
757 0
                        braille.displayMessage(message)
758 0
                    if settings.speechVerbosityLevel == \
759
                        settings.VERBOSITY_LEVEL_VERBOSE:
760 0
                        speech.speak(message)
761 0
                    self.noFocusTimestamp = orca_state.noFocusTimestamp
762 4008
                self._gidleId = 0
763 4008
                rerun = False # destroy and don't call again
764 21018
            self._gidleLock.release()
765 0
        except Queue.Empty:
766 0
            debug.println(debug.LEVEL_SEVERE,
767 0
                          "focus_tracking_presenter:_dequeueEvent: " \
768
                          + " the event queue is empty!")
769 0
        except:
770 0
            debug.printException(debug.LEVEL_SEVERE)
771
772 21018
        if settings.debugEventQueue:
773 0
            self._dequeueEventCount -= 1
774 0
            debug.println(debug.LEVEL_ALL,
775 0
                          "Leaving focus_tracking_presenter._dequeueEvent" \
776
                          + " %d" % self._dequeueEventCount)
777
778 21018
        return rerun
779
780 1
    def processKeyboardEvent(self, keyboardEvent):
781
        """Processes the given keyboard event based on the keybinding from the
782
        currently active script. This method is called synchronously from the
783
        at-spi registry and should be performant.  In addition, it must return
784
        True if it has consumed the event (and False if not).
785
786
        Arguments:
787
        - keyboardEvent: an instance of input_event.KeyboardEvent
788
789
        Returns True if the event should be consumed.
790
        """
791
792 3257
        if orca_state.activeScript \
793
           and orca_state.activeScript.consumesKeyboardEvent(keyboardEvent):
794 154
            self._enqueueEvent(keyboardEvent)
795 154
            return True
796
        else:
797 3103
            return False
798
799 1
    def processBrailleEvent(self, brailleEvent):
800
        """Called whenever a cursor key is pressed on the Braille display.
801
802
        Arguments:
803
        - brailleEvent: an instance of input_event.BrailleEvent
804
805
        Returns True if the command was consumed; otherwise False
806
        """
807
808 0
        if orca_state.activeScript \
809
           and orca_state.activeScript.consumesBrailleEvent(brailleEvent):
810 0
            self._enqueueEvent(brailleEvent)
811 0
            return True
812
        else:
813 0
            return False
814
815 1
    def locusOfFocusChanged(self, event, oldLocusOfFocus, newLocusOfFocus):
816
        """Called when the visual object with focus changes.
817
818
        Arguments:
819
        - event: if not None, the Event that caused the change
820
        - oldLocusOfFocus: Accessible that is the old locus of focus
821
        - newLocusOfFocus: Accessible that is the new locus of focus
822
        """
823
824 960
        if orca_state.activeScript:
825 960
            orca_state.activeScript.locusOfFocusChanged(event,
826 960
                                                        oldLocusOfFocus,
827 960
                                                        newLocusOfFocus)
828
829 1
    def visualAppearanceChanged(self, event, obj):
830
        """Called when the visual appearance of an object changes.
831
        This method should not be called for objects whose visual
832
        appearance changes solely because of focus -- locusOfFocusChanged
833
        is used for that.  Instead, it is intended mostly for objects
834
        whose notional 'value' has changed, such as a checkbox changing
835
        state, a progress bar advancing, a slider moving, text inserted,
836
        caret moved, etc.
837
838
        Arguments:
839
        - event: if not None, the Event that caused this to happen
840
        - obj: the Accessible whose visual appearance changed.
841
        """
842
843 685
        if orca_state.activeScript:
844 685
            orca_state.activeScript.visualAppearanceChanged(event, obj)
845
846 1
    def activate(self):
847
        """Called when this presentation manager is activated."""
848
849 1
        speech.speak(_("Switching to focus tracking mode."))
850
851 1
        self._listenerCounts = {}
852 1
        self._knownScripts   = {}
853 1
        self._knownAppSettings = {}
854 1
        self._oldAppSettings = None
855 1
        self._defaultScript  = None
856
857 1
        orca_state.activeScript = self._getScript(None)
858
859
        # Tell BrlTTY which commands we care about.
860
        #
861 1
        braille.setupKeyRanges(orca_state.activeScript.brailleBindings.keys())
862
863 1
        self._registerEventListener("window:activate")
864 1
        self._registerEventListener("window:deactivate")
865 1
        self._registerEventListener("object:children-changed:remove")
866
867 1
        win = orca_state.activeScript.findActiveWindow()
868 1
        if win:
869
            # Generate a fake window activation event so the application
870
            # can tell the user about itself.
871
            #
872 0
            e = atspi.Event()
873 0
            e.source   = win._acc
874 0
            e.type     = "window:activate"
875 0
            e.detail1  = 0
876 0
            e.detail2  = 0
877 0
            e.any_data = None
878 0
            self._enqueueEvent(e)
879
880 1
    def deactivate(self):
881
        """Called when this presentation manager is deactivated."""
882
883 28
        for eventType in self._listenerCounts.keys():
884 27
            self.registry.deregisterEventListener(self._enqueueEvent,
885 27
                                                  eventType)
886 1
        self._listenerCounts = {}
887 1
        self._knownScripts   = {}
888 1
        self._knownAppSettings = {}
889 1
        self._oldAppSettings = None
890 1
        self._defaultScript  = None
891
892 1
        orca_state.activeScript = None