#!/usr/bin/python
# -*- coding: UTF8 -*-
"""
http://en.wikipedia.org/wiki/Model-view-controller

The SniffApp class sets up all of sniff's widgets.

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.
"""
from dogtail.config import config
if config.checkForA11y:
    from dogtail.utils import checkForA11yInteractively
    checkForA11yInteractively()

config.logDebugToFile = False
config.childrenLimit = 100000
from dogtail import tree
pyatspi = tree.pyatspi
import CORBA
from dogtail import utils
import gobject
import gtk

class SniffApp(object):
    appName = 'Sniff'
    appAuthors = ['Zack Cerza <zcerza@redhat.com>', \
            'David Malcolm <dmalcolm@redhat.com']

    def __init__(self):

        import gnome
        self.program = gnome.program_init(self.appName, '0.1')
        import gtk.glade

        import os
        if os.path.exists('sniff.glade'):
            self.x = gtk.glade.XML('sniff.glade')
        else:
            import sys
            exec_root = sys.argv[0].split("/bin/")[0]
            if exec_root[0] is not '/':
                exec_root = "/usr"
            self.x = gtk.glade.XML(exec_root + \
                    '/share/dogtail/glade/sniff.glade')

        self.app = self.x.get_widget(self.appName)

        try:
            self.app.set_icon_from_file('../icons/dogtail-head.svg')
        except Exception:
            self.app.set_icon_from_file( \
                    '/usr/share/icons/hicolor/scalable/apps/dogtail-head.svg')


        self.setUpWidgets()
        self.connectSignals()
        self.app.show_all()
        gtk.main()

    def setUpWidgets(self):
        self.quit1 = self.x.get_widget('quit1')
        self.expand_all1 = self.x.get_widget('expand_all1')
        self.collapse_all1 = self.x.get_widget('collapse_all1')
        self.about1 = self.x.get_widget('about1')
        self.refreshMenuItem = self.x.get_widget('refresh1')
        self.setRootMenuItem = self.x.get_widget('setRootMenuItem')
        self.unsetRootMenuItem = self.x.get_widget('unsetRootMenuItem')
        self.about = None

        self.tree = SniffController(self.x)

    def connectSignals(self):
        self.app.connect('delete_event', self.quit, self)
        self.quit1.connect('activate', self.quit, self)
        self.expand_all1.connect('activate', self.tree.expandAll, True)
        self.collapse_all1.connect('activate', self.tree.expandAll, False)
        self.about1.connect('activate', self.showAbout, self)
        self.refreshMenuItem.connect('activate', self.tree.refresh)
        self.setRootMenuItem.connect('activate', self.tree.changeRoot, True)
        self.unsetRootMenuItem.connect('activate', self.tree.changeRoot, False)

    def showAbout(self, *args):
        if not self.about:
            self.about = gtk.AboutDialog()
            self.about.set_name(self.appName)
            self.about.set_authors(self.appAuthors)
            self.about.set_comments('Explore your desktop with Dogtail')
            self.about.set_website('http://people.redhat.com/zcerza/dogtail/')
            self.about.connect("response", self.hideAbout)
        self.about.show_all()

    def hideAbout(self, window, response):
        if response == gtk.RESPONSE_CANCEL: window.hide()

    def quit(self, *args):
        gtk.main_quit()


class SniffController(object):
    invalidBufferCallbackID = None

    def __init__(self, gladeXML):
        self.x = gladeXML
        self.nameTextLabel = self.x.get_widget('nameTextLabel')
        self.nameTextLabel.set_text('')
        self.roleNameTextLabel = self.x.get_widget('roleNameTextLabel')
        self.roleNameTextLabel.set_text('')
        self.descTextLabel = self.x.get_widget('descTextLabel')
        self.descTextLabel.set_text('')
        self.actionsTextLabel = self.x.get_widget('actionsTextLabel')
        self.actionsTextLabel.set_text('')
        self.textTextView = self.x.get_widget('textTextView')
        self.textTextViewBufferCallbackID = self.invalidBufferCallbackID
        self.textTextView.set_sensitive(False)
        self.textTextView.get_buffer().set_text('')
        self.labelerButton = self.x.get_widget('labelerButton')
        self.labelerButton.set_sensitive(False)
        self.labeleeButton = self.x.get_widget('labeleeButton')
        self.labeleeButton.set_sensitive(False)
        self.stateView = self.x.get_widget('stateTreeView')
        self.stateModel = StateModel()
        self.setUpStateView()
        self.treeView = self.x.get_widget('treeTreeView')
        self.treeSelection = self.treeView.get_selection()
        self.treeModel = SniffModel()
        self.setUpTreeView()
        self.connectSignals()

    def setUpStateView(self):
        self.stateView.set_model(self.stateModel)
        cellRenderer = gtk.CellRendererText()
        col = gtk.TreeViewColumn('Present States', cellRenderer, \
                text=self.stateModel.stateColumn)
        col.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
        self.stateView.insert_column(col, -1)

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

        self.treeView.set_model(self.treeModel)

        col = gtk.TreeViewColumn()
        cellRenderer = gtk.CellRendererPixbuf()
        col.pack_start(cellRenderer, expand = False)
        col.add_attribute(cellRenderer, 'pixbuf', self.treeModel.pixbufColumn)

        cellRenderer = gtk.CellRendererText()
        col.pack_end(cellRenderer, expand = False)
        col.add_attribute(cellRenderer, 'text', self.treeModel.nameColumn)

        col.set_title('Name')

        self.treeView.insert_column(col, -1)

        for column in self.treeView.get_columns():
            column.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
            column.set_resizable(True)
        self.treeView.show()
        path = 0
        self.treeView.expand_row(path, False)
        self.rowExpanded(self.treeView, self.treeModel.get_iter(path), path)

    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)

    def refresh(self, menuItem = None, refreshModel = True, *args):
        if refreshModel: self.treeModel.refresh()
        rootPath = self.treeModel.get_path(self.treeModel.get_iter_root())
        self.treeView.expand_row(rootPath, False)

    def connectSignals(self):
        self.labelerButton.connect('clicked', self.showRelationTarget, \
                'labeler')
        self.labeleeButton.connect('clicked', self.showRelationTarget, \
                'labelee')
        self.treeView.connect('button-press-event', self.buttonPress)
        self.treeView.connect('key-press-event', self.keyPress)
        self.treeView.connect('row-expanded', self.rowExpanded, self.treeModel)
        self.treeView.connect('row-collapsed', self.rowCollapsed)
        self.treeSelection.connect('changed', self.selectionChanged)
        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')

    def selectionChanged(self, treeSelection):
        node = self.getSelectedNode()
        if node:
            self.setUpBottomPane(node)
            node.blink()

    def getSelectedNode(self):
        (store, iter) = self.treeView.get_selection().get_selected()
        if not iter: node = None
        else: node = self.treeModel.getNode(iter)
        return node

    def expandAll(self, widget, *args):
        if args[0] == True: self.treeView.expand_all()
        elif args[0] == False: self.treeView.collapse_all()

    def rowExpanded(self, treeview, iter, path, *userParams):
        row = self.treeModel[path]
        childRows = row.iterchildren()
        while True:
            try:
                childRow = childRows.next()
                self.treeModel.populateChildren(childRow.iter)
            except StopIteration: break

    def rowCollapsed(self, treeview, iter, path, *userParams):
        row = self.treeModel[path]
        childRows = row.iterchildren()
        try:
            while True:
                childRow = childRows.next()
                grandChildRows = childRow.iterchildren()
                try:
                    while True:
                        grandChildRow = grandChildRows.next()
                        self.treeModel.remove(grandChildRow.iter)
                except StopIteration: pass
        except StopIteration: pass

    def menuItemActivate(self, menuItem, *userParams):
        if len(userParams) < 2: return
        method = userParams[0]
        arg = userParams[1]
        method(arg)

    def keyPress(self, widget, event, *userParams):
        if event.keyval == gtk.keysyms.Return:
            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

    def buttonPress(self, widget, event, *userParams):
        try: path, treeViewCol, relX, relY = \
                        self.treeView.get_path_at_pos(int(event.x), \
                        int(event.y))
        except TypeError: return
        node = self.treeModel.getNode(self.treeModel.get_iter(path))
        if node == None: return

        if event.button == 3:
            menu = gtk.Menu()
            menuItem = None
            if node.actions:
                for action in node.actions.keys():
                    menuItem = gtk.MenuItem(action.capitalize())
                    menuItem.connect('activate', self.menuItemActivate, \
                            node.doAction, action)
                    menuItem.show()
                    menu.append(menuItem)
            if not menuItem: return
            menu.show()
            menu.popup(None, None, None, event.button, event.time)

    def showRelationTarget(self, button, relation, *args):
        target = getattr(self.getSelectedNode(), relation)
        if not target: return
        try: target.blink()
        except:
            import traceback
            traceback.print_exc()

    def setUpBottomPane(self, node):
        """Generic code for setting up the table under the TreeView"""
        if node == None: return
        self.nameTextLabel.set_text(node.name)
        self.roleNameTextLabel.set_text(node.roleName)
        self.descTextLabel.set_text(node.description)
        str = ''
        if node.actions: str = ' '.join(node.actions.keys())
        self.actionsTextLabel.set_text(str)

        # Have we connected this signal yet? 
        # If so, disconnect it before proceeding.
        if self.textTextViewBufferCallbackID != self.invalidBufferCallbackID:
            self.textTextView.get_buffer().disconnect( \
                    self.textTextViewBufferCallbackID)
            self.textTextViewBufferCallbackID = self.invalidBufferCallbackID

        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)
                self.textTextViewBufferCallbackID = \
                        buffer.connect('changed', self.changeText, node)
            except NotImplementedError:
                self.textTextView.set_sensitive(False)
        else:
            self.textTextView.get_buffer().set_text('')
            self.textTextView.set_sensitive(False)

        if node.labeler and not node.labeler.dead: 
            self.labelerButton.set_sensitive(True)
            self.labelerButton.show()
        #elif node.labeler and node.labeler.dead:
        #    print "labeler is dead", node.labeler
        else:
            self.labelerButton.set_sensitive(False)
            self.labelerButton.hide()
        if node.labelee and not node.labelee.dead: 
            self.labeleeButton.set_sensitive(True)
            self.labeleeButton.show()
        #elif node.labelee and node.labelee.dead:
        #    print "labelee is dead", node.labelee
        else:
            self.labeleeButton.set_sensitive(False)
            self.labeleeButton.hide()

        self.stateModel.setNode(node)

    def changeText(self, textBuffer, node):
        if node == None: return
        node.text = textBuffer.get_text(textBuffer.get_start_iter(), \
                        textBuffer.get_end_iter())


class SniffModel(gtk.TreeStore):
    nodeColumn = 0
    nameColumn = 1
    pixbufColumn = 2
    eventQueue = []
    cache = {}

    def __init__(self, root = tree.root):
        gtk.TreeStore.__init__(self, gobject.TYPE_PYOBJECT, \
                gobject.TYPE_STRING, gtk.gdk.Pixbuf)
        self.rootNode = self.initialRootNode = root
        self.appendAndPopulate(None, self.rootNode)

    def __contains__(self, item):
        if isinstance(item, tree.Node):
            if item in self.cache:
                row = self.cache[item]
                # If row is None, we need to call getPath() to be sure
                if not row: 
                    path = self.getPath(item)
                    return path is not None
                elif row in self: return True
            return False
        elif isinstance(item, gtk.TreeIter): 
            return self.iter_is_valid(item)
        elif isinstance(item, list) or isinstance(item, tuple):
            try: iter = self.get_iter(item)
            except ValueError: return False
            return iter in self
        elif isinstance(item, gtk.TreeRowReference):
            return item.valid() and item.get_path() in self
        else:
            raise TypeError

    def changeRoot(self, node):
        self.rootNode = node
        self.refresh()

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

    def refresh(self):
        self.cache.clear()
        self.clear()
        self.appendAndPopulate(None, self.rootNode)

    def append(self, parentIter, node):
        if node: self.cache[node] = None
        pb = self.getPixbufForNode(node)
        return gtk.TreeStore.append(self, parentIter, (node, node.name, pb))

    def remove(self, iter):
        node = self.getNode(iter)
        try: del self.cache[node]
        finally: return gtk.TreeStore.remove(self, iter)

    def populateChildren(self, iter):
        if not iter in self: return False
        result = True
        node = self.getNode(iter)
        for child in node.children: 
            if child in self: continue
            result = result and self.append(iter, child)
        return result

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

    def getNode(self, iter):
        if not iter in self: return None
        return self.get_value(iter, self.nodeColumn)

    def getPath(self, node):
        if not node: raise ValueError
        try: indexInParent = node.indexInParent
        except LookupError: return None
        row = self.cache.get(node, None)
        path = []
        needParent = True
        if row:
            if row in self: path = row.get_path()
            else: del self.cache[node]
        elif node == self.rootNode:
            indexInParent = 0
            needParent = False
        elif node.role == pyatspi.ROLE_APPLICATION or node.roleName == \
                'application':
            path = [0]
            indexInParent = list(tree.root.children).index(node)
            needParent = False
        elif not node.parent: return None
        elif (0 <= indexInParent <= (len(node.parent) - 1)) and \
                node.parent[indexInParent] != node:
            return None
            siblings = node.parent.children
            sibIndex = siblings.index(node)
            try: 
                if siblings[sibIndex] != node: return None
                else: indexInParent = sibIndex
            except ValueError: return None
        if type(path) == list:
            if needParent:
                parentPath = self.getPath(node.parent)
                if parentPath is None: return None
                else: path = list(parentPath)
            path.append(indexInParent)

        path = tuple(path)
        try:
            nodeByPath = self.getNode(self.get_iter(path))
            if node != nodeByPath:
                #print "%s is not %s!" % (node, nodeByPath)
                return None
        except ValueError:
            #print "I smell a bug in %s..." % node.getApplication()
            return None

        self.cache[node] = gtk.TreeRowReference(self, path)
        return path

    def processEvents(self):
        if not len(self.eventQueue): return
        queueCopy = self.eventQueue[:]
        for event in queueCopy:
            self.processChangedNode(event)
            self.eventQueue.remove(event)
        return False

    def nodeChanged(self, event):
        node = event.source
        if not node or not node in self: return
        app = event.host_application
        if app and app.name == 'sniff': return
        self.eventQueue.append(event)
        gobject.idle_add(self.processEvents)

    def processChangedNode(self, event):
        node = event.source
        if not node or not node in self: return
        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":
                print str(event)
        elif event.type.major == "state-changed":
            print str(event)
        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.
                            gobject.timeout_add(1000, \
                                    self.__addNodeCB, iter, child)
            elif event.type.minor == 'remove':
                self.__removeNodeCB(iter, node, path)

    def __addNodeCB(self, iter, parent):
        self.appendAndPopulate(iter, parent)
        return False

    def __removeNodeCB(self, iter, parent, path):
        childRow = self.iter_children(iter)
        while childRow is not None:
            node = self.getNode(childRow)
            if node is None: break
            if node and self.getNode(childRow) not in parent:
                self.remove(childRow)
            else: childRow = self.iter_next(childRow)

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

    def getPixbufForNode(self, node):
        theme = gtk.icon_theme_get_default()
        try:
            if node.role == pyatspi.ROLE_APPLICATION:
                # FIXME: Use the pixbuf from libwcnk (if available):
                # wnckApp = Application(node).getWnckApplication()
                # if wnckApp
                try: 
                    return theme.load_icon(node.name, 24, \
                            gtk.ICON_LOOKUP_USE_BUILTIN)
                except gobject.GError:
                    try: 
                        return theme.load_icon(node.name.lower(), 24, \
                            gtk.ICON_LOOKUP_USE_BUILTIN)
                    except gobject.GError: 
                        return None
            elif node.parent:
                return iconForRole[node.role]
            else:
                return theme.load_icon("gnome-fs-desktop", 24, \
                        gtk.ICON_LOOKUP_USE_BUILTIN)
        except Exception:
            return theme.load_icon("gtk-dialog-error", 24, \
                    gtk.ICON_LOOKUP_USE_BUILTIN)

class StateModel(gtk.ListStore):
    stateColumn = 0
    statesSupported = ['checked', 'focusable', 'focused', 'sensitive', \
            'showing']

    def __init__(self):
        gtk.ListStore.__init__(self, gobject.TYPE_STRING)

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


def loadIcon(iconName):
    try:
        pixbuf = gtk.gdk.pixbuf_new_from_file('icons/' + iconName)
    except gobject.GError:
        iconName = '/usr/share/dogtail/icons/' + iconName
        pixbuf = gtk.gdk.pixbuf_new_from_file(iconName)
    return pixbuf

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")

iconForRole = { \
    pyatspi.ROLE_INVALID : None, \
    # pyatspi doesn't have the following... not even sure if it exists 
    # anywhere.
    #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, \
    pyatspi.ROLE_LIST_ITEM : None, \
    pyatspi.ROLE_MENU : menuitem_xpm, \
    pyatspi.ROLE_MENU_BAR : menubar_xpm, \
    pyatspi.ROLE_MENU_ITEM : menuitem_xpm, \
    pyatspi.ROLE_OPTION_PANE : None, \
    pyatspi.ROLE_PAGE_TAB : notebook_xpm, \
    pyatspi.ROLE_PAGE_TAB_LIST : notebook_xpm, \
    pyatspi.ROLE_PANEL : viewport_xpm, \
    pyatspi.ROLE_PASSWORD_TEXT : None, \
    pyatspi.ROLE_POPUP_MENU : None, \
    pyatspi.ROLE_PROGRESS_BAR : None, \
    pyatspi.ROLE_PUSH_BUTTON : button_xpm, \
    pyatspi.ROLE_RADIO_BUTTON : None, \
    pyatspi.ROLE_RADIO_MENU_ITEM : None, \
    pyatspi.ROLE_ROOT_PANE : viewport_xpm, \
    pyatspi.ROLE_ROW_HEADER : None, \
    pyatspi.ROLE_SCROLL_BAR : vscrollbar_xpm, \
    pyatspi.ROLE_SCROLL_PANE : scrolledwindow_xpm, \
    pyatspi.ROLE_SEPARATOR : vseparator_xpm, \
    pyatspi.ROLE_SLIDER : None, \
    pyatspi.ROLE_SPIN_BUTTON : spinbutton_xpm, \
    pyatspi.ROLE_SPLIT_PANE : None, \
    pyatspi.ROLE_STATUS_BAR : statusbar_xpm, \
    pyatspi.ROLE_TABLE : table_xpm, \
    pyatspi.ROLE_TABLE_CELL : treeitem_xpm, \
    pyatspi.ROLE_TABLE_COLUMN_HEADER : None, \
    pyatspi.ROLE_TABLE_ROW_HEADER : None, \
    pyatspi.ROLE_TEAROFF_MENU_ITEM : None, \
    pyatspi.ROLE_TERMINAL : None, \
    pyatspi.ROLE_TEXT : text_xpm, \
    pyatspi.ROLE_TOGGLE_BUTTON : None, \
    pyatspi.ROLE_TOOL_BAR : toolbar_xpm, \
    pyatspi.ROLE_TOOL_TIP : None, \
    pyatspi.ROLE_TREE : tree_xpm, \
    pyatspi.ROLE_TREE_TABLE : tree_xpm, \
    pyatspi.ROLE_UNKNOWN : unknown_xpm, \
    pyatspi.ROLE_VIEWPORT : viewport_xpm, \
    pyatspi.ROLE_WINDOW : window_xpm, \
    pyatspi.ROLE_EXTENDED : None, \
    pyatspi.ROLE_HEADER : None, \
    pyatspi.ROLE_FOOTER : None, \
    pyatspi.ROLE_PARAGRAPH : None, \
    pyatspi.ROLE_RULER : None, \
    pyatspi.ROLE_APPLICATION : None, \
    pyatspi.ROLE_AUTOCOMPLETE : None, \
    pyatspi.ROLE_EDITBAR : None, \
    pyatspi.ROLE_EMBEDDED : None, \
    pyatspi.ROLE_LAST_DEFINED: None }


def main():
    sniff = SniffApp()

if __name__ == '__main__': 
    main()
