sniff 28.8 KB
Newer Older
Zack Cerza's avatar
Zack Cerza committed
1
#!/usr/bin/env python
2 3
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, print_function, unicode_literals
4 5
"""
http://en.wikipedia.org/wiki/Model-view-controller
Zack Cerza's avatar
Zack Cerza committed
6

7
The SniffApp class sets up all of sniff's widgets.
Zack Cerza's avatar
Zack Cerza committed
8

9 10 11 12
Data storage is handled by the SniffModel class.
There is no SniffView class; we just use a GtkTreeView.
Data display is handled by the SniffController class.
"""
13

14 15 16 17
import gi
gi.require_version('Gtk', '3.0')
gi.require_version('Gdk', '3.0')
import sys
18
from dogtail.config import config
19

20 21 22
if config.checkForA11y:
    from dogtail.utils import checkForA11yInteractively
    checkForA11yInteractively()
23

24
config.logDebugToFile = False
25
config.childrenLimit = 100000
26 27 28 29 30 31 32 33

import pyatspi
import Accessibility
from gi.repository import Gtk
from gi.repository import Gdk
from gi.repository import Gio
from gi.repository import GdkPixbuf
from gi.repository import GObject
34
from gi.repository import GLib
35

36 37
builder = Gtk.Builder()

Vadim Rutkovsky's avatar
Vadim Rutkovsky committed
38

39 40
class SniffApp(object):
    appName = 'Sniff'
Vadim Rutkovsky's avatar
Vadim Rutkovsky committed
41
    appAuthors = ['Zack Cerza <zcerza@redhat.com>',
42
                  'David Malcolm <dmalcolm@redhat.com>']
43 44

    def __init__(self):
45
        self.builder = builder
46
        import os
47 48
        if os.path.exists('sniff.ui'):
            self.builder.add_from_file('sniff.ui')
49
        else:
50 51 52
            import os
            path = os.path.abspath(
                os.path.join(__file__, os.path.pardir, os.path.pardir))
53
            if path == '/':  # in case the path is /bin/sniff
54
                path = '/usr'
55
            self.builder.add_from_file(path + '/share/dogtail/glade/sniff.ui')
56
        self.app = self.builder.get_object(self.appName)
Zack Cerza's avatar
Zack Cerza committed
57
        try:
58 59
            self.app.set_icon_from_file('../icons/dogtail-head.svg')
        except Exception:
60
            import os
Vadim Rutkovsky's avatar
Vadim Rutkovsky committed
61 62
            path = os.path.abspath(
                os.path.join(__file__, os.path.pardir, os.path.pardir))
63 64
            if path is '/':
                path = '/usr'
65 66 67 68
            try:
                self.app.set_icon_from_file(os.path.join(path, 'share/icons/hicolor/scalable/apps/dogtail-head.svg'))
            except Exception: # fallback for wierd installations, when sniff is neither in /bin or /usr/bin
                self.app.set_icon_from_file('/usr/share/icons/hicolor/scalable/apps/dogtail-head.svg')
69 70 71
        self.setUpWidgets()
        self.connectSignals()
        self.app.show_all()
72
        Gtk.main()
73 74

    def setUpWidgets(self):
75 76 77 78 79
        self.quit1 = self.builder.get_object('quit1')
        self.expand_all1 = self.builder.get_object('expand_all1')
        self.collapse_all1 = self.builder.get_object('collapse_all1')
        self.about1 = self.builder.get_object('about1')
        self.refreshMenuItem = self.builder.get_object('refresh1')
80
        self.autoRefreshMenuItem = self.builder.get_object('autorefresh')
81 82
        self.setRootMenuItem = self.builder.get_object('setRootMenuItem')
        self.unsetRootMenuItem = self.builder.get_object('unsetRootMenuItem')
83 84
        self.about = None

85
        self.tree = SniffController()
86 87 88 89

    def connectSignals(self):
        self.app.connect('delete_event', self.quit, self)
        self.quit1.connect('activate', self.quit, self)
90 91
        self.expand_all1.connect('activate', self.tree.expandAll, True)
        self.collapse_all1.connect('activate', self.tree.expandAll, False)
92
        self.about1.connect('activate', self.showAbout, self)
93
        self.refreshMenuItem.connect('activate', self.tree.refresh)
94
        self.autoRefreshMenuItem.connect('toggled', self.tree.toggleAutoRefresh)
95 96
        self.setRootMenuItem.connect('activate', self.tree.changeRoot, True)
        self.unsetRootMenuItem.connect('activate', self.tree.changeRoot, False)
97

98 99 100 101 102 103
        self.setStartupAutoRefresh()

    def setStartupAutoRefresh(self):
        import os
        if not os.path.exists('/tmp/sniff_refresh.lock'):
            self.autoRefreshMenuItem.set_active(True)
104 105 106
        else:
            print("Sniff warning: A running 'dogtail' script has been detected.")
            print("Disabling 'Auto Refresh', however this still is not recommended.")
107

108 109
    def showAbout(self, *args):
        if not self.about:
110
            self.about = Gtk.AboutDialog()
111
            self.about.set_name(self.appName)
112 113
            self.about.set_logo(self.app.get_icon())
            self.about.set_icon(self.app.get_icon())
114 115
            self.about.set_authors(self.appAuthors)
            self.about.set_comments('Explore your desktop with Dogtail')
116
            self.about.set_website('http://dogtail.fedorahosted.org/')
117
            self.about.connect("response", self.hideAbout)
118
            self.about.set_transient_for(self.app)
119 120 121
        self.about.show_all()

    def hideAbout(self, window, response):
Vadim Rutkovsky's avatar
Vadim Rutkovsky committed
122 123
        if response == Gtk.ResponseType.CANCEL:
            window.hide()
124 125

    def quit(self, *args):
Vitezslav Humpa's avatar
Vitezslav Humpa committed
126
        Gtk.main_quit()
127 128 129


class SniffController(object):
130 131
    invalidBufferCallbackID = None

132
    def __init__(self):
133 134
        self.builder = builder
        self.nameTextLabel = self.builder.get_object('nameTextLabel')
135
        self.nameTextLabel.set_text('')
136
        self.roleNameTextLabel = self.builder.get_object('roleNameTextLabel')
137
        self.roleNameTextLabel.set_text('')
138
        self.descTextLabel = self.builder.get_object('descTextLabel')
139
        self.descTextLabel.set_text('')
140
        self.actionsTextLabel = self.builder.get_object('actionsTextLabel')
141
        self.actionsTextLabel.set_text('')
142
        self.textTextView = self.builder.get_object('textTextView')
143
        self.textTextViewBufferCallbackID = self.invalidBufferCallbackID
144 145
        self.textTextView.set_sensitive(False)
        self.textTextView.get_buffer().set_text('')
146
        self.labelerButton = self.builder.get_object('labelerButton')
147
        self.labelerButton.set_sensitive(False)
148
        self.labeleeButton = self.builder.get_object('labeleeButton')
149
        self.labeleeButton.set_sensitive(False)
150
        self.stateView = self.builder.get_object('stateTreeView')
151
        self.highlight1 = self.builder.get_object('highlight1')
152
        self.autorefresh = self.builder.get_object('autorefresh')
153 154
        self.stateModel = StateModel()
        self.setUpStateView()
155
        self.treeView = self.builder.get_object('treeTreeView')
156 157 158
        self.treeSelection = self.treeView.get_selection()
        self.treeModel = SniffModel()
        self.setUpTreeView()
159
        self.connectSignals()
160
        self.refresh()
161

162 163
    def setUpStateView(self):
        self.stateView.set_model(self.stateModel)
164
        cellRenderer = Gtk.CellRendererText()
165
        col = Gtk.TreeViewColumn('Present States', cellRenderer, text=self.stateModel.stateColumn)
166
        col.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)
167 168 169 170
        self.stateView.insert_column(col, -1)

    def setUpTreeView(self):
        self.treeView.set_enable_tree_lines(True)
171

172
        self.treeView.set_model(self.treeModel)
173

174 175
        col = Gtk.TreeViewColumn()
        cellRenderer = Gtk.CellRendererPixbuf()
Vadim Rutkovsky's avatar
Vadim Rutkovsky committed
176
        col.pack_start(cellRenderer, expand=False)
177
        col.add_attribute(cellRenderer, 'pixbuf', self.treeModel.pixbufColumn)
178

179
        cellRenderer = Gtk.CellRendererText()
Vadim Rutkovsky's avatar
Vadim Rutkovsky committed
180
        col.pack_end(cellRenderer, expand=False)
181
        col.add_attribute(cellRenderer, 'text', self.treeModel.nameColumn)
182 183 184

        col.set_title('Name')

185
        self.treeView.insert_column(col, -1)
186

187
        for column in self.treeView.get_columns():
188
            column.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)
189
            column.set_resizable(True)
190
        self.treeView.show()
191
        path = 0
192
        self.treeView.expand_all()
193
        #self.rowExpanded(self.treeView, self.treeModel.get_iter(path), path)
194

Vadim Rutkovsky's avatar
Vadim Rutkovsky committed
195 196 197 198 199 200 201 202 203 204
    def changeRoot(self, menuItem, toSelected=True, *args):
        if toSelected:
            node = self.getSelectedNode()
        if toSelected and node:
            self.treeModel.changeRoot(node)
        elif not toSelected:
            self.treeModel.reset()
        else:
            return
        self.refresh(refreshModel=False)
205

Vadim Rutkovsky's avatar
Vadim Rutkovsky committed
206 207 208
    def refresh(self, menuItem=None, refreshModel=True, *args):
        if refreshModel:
            self.treeModel.refresh()
Vitezslav Humpa's avatar
Vitezslav Humpa committed
209
        rootPath = self.treeModel.get_path(self.treeModel.get_iter_first())
210
        self.treeView.expand_all()
211
        self.treeView.expand_row(rootPath, False)
212

213 214
    def toggleAutoRefresh(self, *args):
        if self.autorefresh.get_active() is True:
Vadim Rutkovsky's avatar
Vadim Rutkovsky committed
215 216 217 218 219 220 221 222
            pyatspi.Registry.registerEventListener(self.treeModel.nodeChanged,
                                                   'object:children-changed')
            pyatspi.Registry.registerEventListener(self.treeModel.nodeChanged,
                                                   'object:property-change:accessible-name')
            pyatspi.Registry.registerEventListener(self.treeModel.nodeChanged,
                                                   'object:property-change:accessible-state')
            pyatspi.Registry.registerEventListener(self.treeModel.nodeChanged,
                                                   'object:state-changed')
223 224
            self.refresh()
        else:
Vadim Rutkovsky's avatar
Vadim Rutkovsky committed
225 226
            pyatspi.Registry.deregisterEventListener(
                self.treeModel.nodeChanged,
227
                'object:children-changed')
Vadim Rutkovsky's avatar
Vadim Rutkovsky committed
228 229
            pyatspi.Registry.deregisterEventListener(
                self.treeModel.nodeChanged,
230
                'object:property-change:accessible-name')
Vadim Rutkovsky's avatar
Vadim Rutkovsky committed
231 232
            pyatspi.Registry.deregisterEventListener(
                self.treeModel.nodeChanged,
233
                'object:property-change:accessible-state')
Vadim Rutkovsky's avatar
Vadim Rutkovsky committed
234 235
            pyatspi.Registry.deregisterEventListener(
                self.treeModel.nodeChanged,
236 237
                'object:state-changed')

238
    def connectSignals(self):
239 240
        self.labelerButton.connect('clicked', self.showRelationTarget, 'labeler')
        self.labeleeButton.connect('clicked', self.showRelationTarget, 'labelee')
241
        self.treeView.connect('button-press-event', self.buttonPress)
242
        self.treeView.connect('key-press-event', self.keyPress)
243 244
        self.treeView.connect('row-expanded', self.rowExpanded, self.treeModel)
        self.treeView.connect('row-collapsed', self.rowCollapsed)
245
        self.treeSelection.connect('changed', self.selectionChanged)
246
        self.refresh()
247 248 249

    def selectionChanged(self, treeSelection):
        node = self.getSelectedNode()
250
        if node:
251
            self.setUpBottomPane(node)
252
            if self.highlight1.get_active():
253
                node.blink()
254 255

    def getSelectedNode(self):
256
        (store, iter) = self.treeView.get_selection().get_selected()
Vadim Rutkovsky's avatar
Vadim Rutkovsky committed
257 258 259 260
        if not iter:
            node = None
        else:
            node = self.treeModel.getNode(iter)
261 262 263
        return node

    def expandAll(self, widget, *args):
264
        if args[0]:
Vadim Rutkovsky's avatar
Vadim Rutkovsky committed
265
            self.treeView.expand_all()
266
        else:
Vadim Rutkovsky's avatar
Vadim Rutkovsky committed
267
            self.treeView.collapse_all()
268 269

    def rowExpanded(self, treeview, iter, path, *userParams):
270
        row = self.treeModel[path]
271
        childRows = row.iterchildren()
272 273
        while True:
            try:
274
                childRow = next(childRows)
275
                self.treeModel.populateChildren(childRow.iter)
Vadim Rutkovsky's avatar
Vadim Rutkovsky committed
276 277
            except StopIteration:
                break
278 279

    def rowCollapsed(self, treeview, iter, path, *userParams):
280
        row = self.treeModel[path]
281 282 283
        childRows = row.iterchildren()
        try:
            while True:
284
                childRow = next(childRows)
285 286 287
                grandChildRows = childRow.iterchildren()
                try:
                    while True:
288
                        grandChildRow = next(grandChildRows)
289
                        self.treeModel.remove(grandChildRow.iter)
Vadim Rutkovsky's avatar
Vadim Rutkovsky committed
290 291 292 293
                except StopIteration:
                    pass
        except StopIteration:
            pass
294 295

    def menuItemActivate(self, menuItem, *userParams):
Vadim Rutkovsky's avatar
Vadim Rutkovsky committed
296 297
        if len(userParams) < 2:
            return
298 299 300 301
        method = userParams[0]
        arg = userParams[1]
        method(arg)

302
    def keyPress(self, widget, event, *userParams):
303
        if event.keyval == Gdk.KEY_Return:
304 305 306 307 308 309 310
            path = self.treeSelection.get_selected_rows()[1][0]
            if self.treeView.row_expanded(path):
                self.treeView.collapse_row(path)
            else:
                self.treeView.expand_row(path, False)
        return False

311
    def buttonPress(self, widget, event, *userParams):
Vadim Rutkovsky's avatar
Vadim Rutkovsky committed
312
        try:
313
            path, treeViewCol, relX, relY = self.treeView.get_path_at_pos(int(event.x), int(event.y))
Vadim Rutkovsky's avatar
Vadim Rutkovsky committed
314 315
        except TypeError:
            return
316
        node = self.treeModel.getNode(self.treeModel.get_iter(path))
317
        if node is None:
Vadim Rutkovsky's avatar
Vadim Rutkovsky committed
318
            return
319 320

        if event.button == 3:
321
            self.menu = Gtk.Menu()
322 323
            menuItem = None
            if node.actions:
324 325 326
                for action in list(node.actions.keys()):
                    menuItem = Gtk.MenuItem(label=action.capitalize())
                    menuItem.connect('activate', self.menuItemActivate, node.doActionNamed, action)
327
                    menuItem.show()
328
                    self.menu.append(menuItem)
Vadim Rutkovsky's avatar
Vadim Rutkovsky committed
329 330
            if not menuItem:
                return
331 332
            self.menu.show_all()
            self.menu.popup(None, None, None, None, event.button, event.time)
333

334 335
    def showRelationTarget(self, button, relation, *args):
        target = getattr(self.getSelectedNode(), relation)
Vadim Rutkovsky's avatar
Vadim Rutkovsky committed
336 337 338 339
        if not target:
            return
        try:
            target.blink()
340 341 342
        except:
            import traceback
            traceback.print_exc()
343

344
    def setUpBottomPane(self, node):
345
        """Generic code for setting up the table under the TreeView"""
346
        if node is None:
Vadim Rutkovsky's avatar
Vadim Rutkovsky committed
347
            return
348
        self.nameTextLabel.set_text(node.name if len(node.name) <= 128 else "%s..." % node.name[:128])
349 350 351
        self.roleNameTextLabel.set_text(node.roleName)
        self.descTextLabel.set_text(node.description)
        str = ''
Vadim Rutkovsky's avatar
Vadim Rutkovsky committed
352
        if node.actions:
353
            str = ' '.join(list(node.actions.keys()))
354 355
        self.actionsTextLabel.set_text(str)

356
        # Have we connected this signal yet?
357
        # If so, disconnect it before proceeding.
358
        if self.textTextViewBufferCallbackID != self.invalidBufferCallbackID:
359
            self.textTextView.get_buffer().disconnect(self.textTextViewBufferCallbackID)
360
            self.textTextViewBufferCallbackID = self.invalidBufferCallbackID
361 362 363 364 365 366 367 368

        if node.text is not None:
            buffer = self.textTextView.get_buffer()
            buffer.set_text(node.text)
            try:
                node.queryEditableText()
                # Remember the handler ID of this connection.
                self.textTextView.set_sensitive(True)
369
                self.textTextViewBufferCallbackID = buffer.connect('changed', self.changeText, node)
370 371 372 373 374 375
            except NotImplementedError:
                self.textTextView.set_sensitive(False)
        else:
            self.textTextView.get_buffer().set_text('')
            self.textTextView.set_sensitive(False)

376
        if node.labeler and not node.labeler.dead:
377
            self.labelerButton.set_sensitive(True)
378
            self.labelerButton.show()
Vadim Rutkovsky's avatar
Vadim Rutkovsky committed
379
        # elif node.labeler and node.labeler.dead:
380 381 382 383
        #    print "labeler is dead", node.labeler
        else:
            self.labelerButton.set_sensitive(False)
            self.labelerButton.hide()
384
        if node.labelee and not node.labelee.dead:
385
            self.labeleeButton.set_sensitive(True)
386
            self.labeleeButton.show()
Vadim Rutkovsky's avatar
Vadim Rutkovsky committed
387
        # elif node.labelee and node.labelee.dead:
388 389 390 391
        #    print "labelee is dead", node.labelee
        else:
            self.labeleeButton.set_sensitive(False)
            self.labeleeButton.hide()
392

393 394
        self.stateModel.setNode(node)

395
    def changeText(self, textBuffer, node):
396
        if node is None:
Vadim Rutkovsky's avatar
Vadim Rutkovsky committed
397
            return
398
        node.text = textBuffer.get_text(textBuffer.get_start_iter(), textBuffer.get_end_iter())
Vadim Rutkovsky's avatar
Vadim Rutkovsky committed
399

400

401
class SniffModel(Gtk.TreeStore):
402
    nodeColumn = 0
403 404
    nameColumn = 1
    pixbufColumn = 2
405
    eventQueue = []
406
    cache = {}
407

408
    def __init__(self):
409 410
        self.builder = builder
        #self.autorefresh = self.builder.get_object('autorefresh')
411
        Gtk.TreeStore.__init__(self, GObject.TYPE_PYOBJECT, GObject.TYPE_STRING, GdkPixbuf.Pixbuf)
412
        root = pyatspi.Registry.getDesktop(0)
413 414
        self.rootNode = self.initialRootNode = root
        self.appendAndPopulate(None, self.rootNode)
415 416

    def __contains__(self, item):
417 418
        from dogtail.tree import Node
        if isinstance(item, Node):
419 420 421
            if item in self.cache:
                row = self.cache[item]
                # If row is None, we need to call getPath() to be sure
422
                if not row:
423 424
                    path = self.getPath(item)
                    return path is not None
Vadim Rutkovsky's avatar
Vadim Rutkovsky committed
425 426
                elif row in self:
                    return True
427
            return False
428
        elif isinstance(item, Gtk.TreeIter):
429 430
            return self.iter_is_valid(item)
        elif isinstance(item, list) or isinstance(item, tuple):
Vadim Rutkovsky's avatar
Vadim Rutkovsky committed
431 432 433 434
            try:
                iter = self.get_iter(item)
            except ValueError:
                return False
435
            return iter in self
436
        elif isinstance(item, Gtk.TreeRowReference):
437
            return item.valid() and item.get_path() in self
438 439
        else:
            raise TypeError
440

441 442 443 444 445 446 447 448 449
    def changeRoot(self, node):
        self.rootNode = node
        self.refresh()

    def reset(self):
        self.rootNode = self.initialRootNode
        self.refresh()

    def refresh(self):
450
        self.cache.clear()
451
        self.clear()
452
        self.appendAndPopulate(None, self.rootNode)
453 454

    def append(self, parentIter, node):
Vadim Rutkovsky's avatar
Vadim Rutkovsky committed
455 456
        if node:
            self.cache[node] = None
457
        pb = self.getPixbufForNode(node)
458
        return Gtk.TreeStore.append(self, parentIter, (node, node.name, pb))
459 460 461

    def remove(self, iter):
        node = self.getNode(iter)
Vadim Rutkovsky's avatar
Vadim Rutkovsky committed
462 463 464 465
        try:
            del self.cache[node]
        finally:
            return Gtk.TreeStore.remove(self, iter)
466 467

    def populateChildren(self, iter):
468
        if not iter in self:
469
            return False
470
        result = True
471
        node = self.getNode(iter)
472 473 474 475 476 477 478 479
        try:
            for child in node.children:
                if child in self:
                    continue
                result = result and self.append(iter, child)
        except GLib.GError as e:
            if 'name :1.0 was not provided' in e.message:
                print("Dogtail: warning: omiting possibly broken at-spi application record")
480
        return result
481 482 483

    def appendAndPopulate(self, iter, node):
        childIter = self.append(iter, node)
484
        return self.populateChildren(childIter)
485 486

    def getNode(self, iter):
Vadim Rutkovsky's avatar
Vadim Rutkovsky committed
487 488
        if not iter in self:
            return None
489 490
        return self.get_value(iter, self.nodeColumn)

491
    def getPath(self, node):
Vadim Rutkovsky's avatar
Vadim Rutkovsky committed
492 493 494 495 496 497
        if not node:
            raise ValueError
        try:
            indexInParent = node.indexInParent
        except LookupError:
            return None
498
        root = pyatspi.Registry.getDesktop(0)
499 500
        row = self.cache.get(node, None)
        path = []
501 502
        needParent = True
        if row:
Vadim Rutkovsky's avatar
Vadim Rutkovsky committed
503 504 505 506
            if row in self:
                path = row.get_path()
            else:
                del self.cache[node]
507 508 509
        elif node == self.rootNode:
            indexInParent = 0
            needParent = False
510
        elif node.role == pyatspi.ROLE_APPLICATION or node.roleName == 'application':
511
            path = [0]
512
            indexInParent = list(root.children).index(node)
513
            needParent = False
Vadim Rutkovsky's avatar
Vadim Rutkovsky committed
514 515
        elif not node.parent:
            return None
516
        elif (0 <= indexInParent <= (len(node.parent) - 1)) and node.parent[indexInParent] != node:
517 518 519
            return None
            siblings = node.parent.children
            sibIndex = siblings.index(node)
520
            try:
Vadim Rutkovsky's avatar
Vadim Rutkovsky committed
521 522 523 524 525 526
                if siblings[sibIndex] != node:
                    return None
                else:
                    indexInParent = sibIndex
            except ValueError:
                return None
527 528 529
        if type(path) == list:
            if needParent:
                parentPath = self.getPath(node.parent)
Vadim Rutkovsky's avatar
Vadim Rutkovsky committed
530 531 532 533
                if parentPath is None:
                    return None
                else:
                    path = list(parentPath)
534 535
            path.append(indexInParent)

536 537 538 539
        path = tuple(path)
        try:
            nodeByPath = self.getNode(self.get_iter(path))
            if node != nodeByPath:
Vadim Rutkovsky's avatar
Vadim Rutkovsky committed
540
                # print "%s is not %s!" % (node, nodeByPath)
541
                return None
542
        except ValueError:
Vadim Rutkovsky's avatar
Vadim Rutkovsky committed
543
            # print "I smell a bug in %s..." % node.getApplication()
544 545
            return None

546
        #self.cache[node] = Gtk.TreeRowReference(self, path)
547 548
        return path

549
    def processEvents(self):
Vadim Rutkovsky's avatar
Vadim Rutkovsky committed
550 551
        if not len(self.eventQueue):
            return
552 553 554 555 556 557
        queueCopy = self.eventQueue[:]
        for event in queueCopy:
            self.processChangedNode(event)
            self.eventQueue.remove(event)
        return False

558
    def nodeChanged(self, event):
Vadim Rutkovsky's avatar
Vadim Rutkovsky committed
559
        # if self.autorefresh.get_active() is False: return
560
        node = event.source
Vadim Rutkovsky's avatar
Vadim Rutkovsky committed
561 562
        if not node or not node in self:
            return
563
        app = event.host_application
Vadim Rutkovsky's avatar
Vadim Rutkovsky committed
564 565
        if app and app.name == 'sniff':
            return
566
        self.eventQueue.append(event)
567
        GLib.idle_add(self.processEvents)
568 569 570

    def processChangedNode(self, event):
        node = event.source
Vadim Rutkovsky's avatar
Vadim Rutkovsky committed
571 572
        if not node or not node in self:
            return
573 574 575 576 577 578 579 580 581 582
        path = self.getPath(node)
        try:
            iter = self.get_iter(path)
        except (ValueError, TypeError):
            return
        if event.type.major == "property-change":
            if event.type.minor == "accessible-name":
                node = self.getNode(iter)
                self.set_value(iter, self.nameColumn, node.name)
            elif event.type.minor == "accessible-state":
583
                print(str(event))
584
        elif event.type.major == "state-changed":
585
            print(str(event))
586 587 588 589 590 591 592 593 594
        elif event.type.major == "children-changed":
            if event.type.minor == 'add':
                for child in node.children:
                    if not child in self:
                        if len(child) > 0:
                            self.appendAndPopulate(iter, child)
                        else:
                            # If it has no children now, give it a sec
                            # to come up with some.
595
                            GObject.timeout_add(1000, self.__addNodeCB, iter, child)
596 597 598
            elif event.type.minor == 'remove':
                self.__removeNodeCB(iter, node, path)

599 600 601 602 603 604
    def __addNodeCB(self, iter, parent):
        self.appendAndPopulate(iter, parent)
        return False

    def __removeNodeCB(self, iter, parent, path):
        childRow = self.iter_children(iter)
Zack Cerza's avatar
Zack Cerza committed
605
        while childRow is not None:
606
            node = self.getNode(childRow)
Vadim Rutkovsky's avatar
Vadim Rutkovsky committed
607 608
            if node is None:
                break
609 610
            if node and self.getNode(childRow) not in parent:
                self.remove(childRow)
Vadim Rutkovsky's avatar
Vadim Rutkovsky committed
611 612
            else:
                childRow = self.iter_next(childRow)
613 614 615 616 617 618

    def __populateCB(self, iter):
        self.populateChildren(iter)
        return False

    def getPixbufForNode(self, node):
619
        theme = Gtk.IconTheme().get_default()
620 621 622 623 624
        try:
            if node.role == pyatspi.ROLE_APPLICATION:
                # FIXME: Use the pixbuf from libwcnk (if available):
                # wnckApp = Application(node).getWnckApplication()
                # if wnckApp
625
                try:
626
                    return theme.load_icon(node.name, 24, Gtk.IconLookupFlags.USE_BUILTIN)
627 628
                except GObject.GError:
                    try:
629
                        return theme.load_icon(node.name.lower(), 24, Gtk.IconLookupFlags.USE_BUILTIN)
630
                    except GObject.GError:
631 632 633 634
                        return None
            elif node.parent:
                return iconForRole[node.role]
            else:
635
                return theme.load_icon("user-desktop", 24, Gtk.IconLookupFlags.USE_BUILTIN)
636
        except Exception:
637
            return theme.load_icon("dialog-error", 24, Gtk.IconLookupFlags.USE_BUILTIN)
Vadim Rutkovsky's avatar
Vadim Rutkovsky committed
638

639

640
class StateModel(Gtk.ListStore):
641
    stateColumn = 0
642
    statesSupported = ['checked', 'focusable', 'focused', 'sensitive', 'showing', 'visible']
643 644

    def __init__(self):
645
        Gtk.ListStore.__init__(self, GObject.TYPE_STRING)
646 647 648 649 650 651 652

    def setNode(self, node):
        self.clear()
        for stateName in self.statesSupported:
            if getattr(node, stateName) is True:
                self.append((stateName.capitalize(),))

Zack Cerza's avatar
Zack Cerza committed
653

David Malcolm's avatar
David Malcolm committed
654
def loadIcon(iconName):
655
    try:
656 657
        pixbuf = GdkPixbuf.Pixbuf.new_from_file('icons/' + iconName)
    except GObject.GError:
658
        import os
Vadim Rutkovsky's avatar
Vadim Rutkovsky committed
659 660
        path = os.path.abspath(
            os.path.join(__file__, os.path.pardir, os.path.pardir))
661
        if path == '/':
662
                path = '/usr'
663
        iconName = os.path.join(path, 'share/dogtail/icons/', iconName)
664
        pixbuf = GdkPixbuf.Pixbuf.new_from_file(iconName)
665
    return pixbuf
David Malcolm's avatar
David Malcolm committed
666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691

button_xpm = loadIcon("button.xpm")
checkbutton_xpm = loadIcon("checkbutton.xpm")
checkmenuitem_xpm = loadIcon("checkmenuitem.xpm")
colorselection_xpm = loadIcon("colorselection.xpm")
combo_xpm = loadIcon("combo.xpm")
dialog_xpm = loadIcon("dialog.xpm")
image_xpm = loadIcon("image.xpm")
label_xpm = loadIcon("label.xpm")
menubar_xpm = loadIcon("menubar.xpm")
menuitem_xpm = loadIcon("menuitem.xpm")
notebook_xpm = loadIcon("notebook.xpm")
scrolledwindow_xpm = loadIcon("scrolledwindow.xpm")
spinbutton_xpm = loadIcon("spinbutton.xpm")
statusbar_xpm = loadIcon("statusbar.xpm")
table_xpm = loadIcon("table.xpm")
text_xpm = loadIcon("text.xpm")
toolbar_xpm = loadIcon("toolbar.xpm")
tree_xpm = loadIcon("tree.xpm")
treeitem_xpm = loadIcon("treeitem.xpm")
unknown_xpm = loadIcon("unknown.xpm")
viewport_xpm = loadIcon("viewport.xpm")
vscrollbar_xpm = loadIcon("vscrollbar.xpm")
vseparator_xpm = loadIcon("vseparator.xpm")
window_xpm = loadIcon("window.xpm")

Vadim Rutkovsky's avatar
Vadim Rutkovsky committed
692 693
iconForRole = {
    pyatspi.ROLE_INVALID: None,
694
    # pyatspi doesn't have the following... not even sure if it exists
695
    # anywhere.
Vadim Rutkovsky's avatar
Vadim Rutkovsky committed
696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725
    # atspi.SPI_ROLE_ACCEL_LABEL : label_xpm,
    pyatspi.ROLE_ALERT: None,
    pyatspi.ROLE_ANIMATION: None,
    pyatspi.ROLE_ARROW: None,
    pyatspi.ROLE_CALENDAR: None,
    pyatspi.ROLE_CANVAS: None,
    pyatspi.ROLE_CHECK_BOX: checkbutton_xpm,
    pyatspi.ROLE_CHECK_MENU_ITEM: checkmenuitem_xpm,
    pyatspi.ROLE_COLOR_CHOOSER: colorselection_xpm,
    pyatspi.ROLE_COLUMN_HEADER: None,
    pyatspi.ROLE_COMBO_BOX: combo_xpm,
    pyatspi.ROLE_DATE_EDITOR: None,
    pyatspi.ROLE_DESKTOP_ICON: None,
    pyatspi.ROLE_DESKTOP_FRAME: None,
    pyatspi.ROLE_DIAL: None,
    pyatspi.ROLE_DIALOG: dialog_xpm,
    pyatspi.ROLE_DIRECTORY_PANE: None,
    pyatspi.ROLE_DRAWING_AREA: None,
    pyatspi.ROLE_FILE_CHOOSER: None,
    pyatspi.ROLE_FILLER: None,
    pyatspi.ROLE_FONT_CHOOSER: None,
    pyatspi.ROLE_FRAME: window_xpm,
    pyatspi.ROLE_GLASS_PANE: None,
    pyatspi.ROLE_HTML_CONTAINER: None,
    pyatspi.ROLE_ICON: image_xpm,
    pyatspi.ROLE_IMAGE: image_xpm,
    pyatspi.ROLE_INTERNAL_FRAME: None,
    pyatspi.ROLE_LABEL: label_xpm,
    pyatspi.ROLE_LAYERED_PANE: viewport_xpm,
    pyatspi.ROLE_LIST: None,
726
    pyatspi.ROLE_LIST_BOX: None,
Vadim Rutkovsky's avatar
Vadim Rutkovsky committed
727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743