diff options
Diffstat (limited to 'build/scripts-2.7')
-rwxr-xr-x | build/scripts-2.7/dogtail-detect-session | 66 | ||||
-rwxr-xr-x | build/scripts-2.7/dogtail-logout | 28 | ||||
-rwxr-xr-x | build/scripts-2.7/dogtail-run-headless | 79 | ||||
-rwxr-xr-x | build/scripts-2.7/dogtail-run-headless-next | 319 | ||||
-rwxr-xr-x | build/scripts-2.7/sniff | 798 |
5 files changed, 1290 insertions, 0 deletions
diff --git a/build/scripts-2.7/dogtail-detect-session b/build/scripts-2.7/dogtail-detect-session new file mode 100755 index 00000000000..79bb6170203 --- /dev/null +++ b/build/scripts-2.7/dogtail-detect-session @@ -0,0 +1,66 @@ +#!/usr/bin/python +""" +dogtail-detect session + +This script checks for some main pieces of a running GNOME session, +specifically gnome-panel and metacity. + +It checks to see that the gnome-panel node has at least some child nodes. +For example, the main gnome-panel node by default has two direct descendents: +the top panel, and the bottom panel. +Metacity's existence is also checked. However, metacity currently never has +any child nodes. + +If a proper session is running, the scripts exits with a status of 0. +If no session is found, a non-zero exit code is returned. + +Author: Zack Cerza <zcerza@redhat.com> +""" + +__author__ = "Zack Cerza <zcerza@redhat.com>" + +from dogtail.procedural import * +import sys + + +def GNOME(): + """ + "Is an accessibility-enabled GNOME session running?" + """ + running = False + try: + assert focus.desktop + assert focus.desktop.children + + focus.application('gnome-panel') + assert focus.application.children + + focus.application('metacity') + print focus.application.node + assert focus.application.node + running = True + print "GNOME Session detected." + except AttributeError or AssertionError or FocusError: + print "ERROR: No session found." + return running + + +def KDE(): + """ + "Is an accessibility-enabled KDE session running?" + """ + running = False + return running + + +def JustSomeApps(): + """ + "Is at least one accessibility-enabled application running?" + """ + assert focus.desktop + assert focus.desktop.children + +if GNOME() or KDE() or JustSomeApps(): + sys.exit() +else: + sys.exit(1) diff --git a/build/scripts-2.7/dogtail-logout b/build/scripts-2.7/dogtail-logout new file mode 100755 index 00000000000..c76a4519e08 --- /dev/null +++ b/build/scripts-2.7/dogtail-logout @@ -0,0 +1,28 @@ +#!/usr/bin/python +# Logs out the full gnome session. Be sure to have your documents saved, as running +# may cause loosing the changes, or it may halt the logout process. + +from dogtail.tree import * +from dogtail.rawinput import pressKey +from time import sleep +import getpass + +# A gnome-shell object +shell = root.application('gnome-shell') +# Click onto a super menu label that we find under the g-s top panel object. +# We need these indexes as g-s a11y support is a wee bit messy. +shell[0][1][2].child(getpass.getuser(), roleName='label').click() +# We can child this all the way down from the app as there's no other Log +# Out... label +shell.child('Log Out...', roleName='label').click() +# This takes care of the 60 second dialog. +# Sometimes a dialog warning about unsaved work in gedit etc. pops out, but that has the same +# push button in which case this will take care of that dialog. If another dialog pops-out +# in the affected application however, that might put the logout process on hold again. Unfortunatelly +# we cannot do anything about that with dotail at that point as a11y registry got disabled already +# by the logout process. +shell[0][1].child(roleName='dialog', recursive=False).child( + 'Log Out', roleName='push button').click() + +# Give the session some time to end before we kill it. +sleep(10) diff --git a/build/scripts-2.7/dogtail-run-headless b/build/scripts-2.7/dogtail-run-headless new file mode 100755 index 00000000000..edab53ddeed --- /dev/null +++ b/build/scripts-2.7/dogtail-run-headless @@ -0,0 +1,79 @@ +#!/usr/bin/python +""" +dogtail-run-headless + +This script runs a session within an X server, allowing dogtail scripts to be +run on systems with no graphic cards, among other things. + +Scripts are run in the current directory. After they are finished, dogtail can +optionally log out of the session, which will also termninate the X server. +""" + +import optparse +from dogtail import sessions +import sys +import os.path + + +def findXServers(path="/usr/bin"): + l = [os.path.join(path, f) for f in os.listdir(path) if f[0] == 'X'] + s = set(os.path.realpath(p) for p in l) + return list(s) + + +def parse(): + yesno = ('y', 'n') + sessions = ("GNOME", "KDE") + usage = "usage: %prog: [options] {script [args]}" + parser = optparse.OptionParser(usage=usage) + + parser.add_option("-s", "--session", type="choice", + dest="session", + choices=sessions, + help="which session to use") + parser.add_option("-x", "--x-server", type="choice", + dest="xserver", + choices=findXServers(), help="which X server to use") + parser.add_option("-l", "--logout", type="choice", + dest="logout", + choices=yesno, + help="attempt to log out of the session gracefully after" + + "script completion") + parser.add_option("-t", "--terminate", type="choice", + dest="terminate", + choices=yesno, + help="after script completion, and after any attempt to log" + + "out, terminate the session") + + parser.set_defaults(session=sessions[0], logout='y', terminate='y') + options, args = parser.parse_args() + if not args: + parser.print_usage() + sys.exit(1) + return options, args + + +def main(): + options, args = parse() + if 'XDG_RUNTIME_DIR' in os.environ: + del os.environ['XDG_RUNTIME_DIR'] + if options.session == "GNOME": + session = sessions.Session(sessionBinary='/usr/bin/gnome-session', + scriptCmdList=args, scriptDelay=10) + if options.session == "KDE": + session = sessions.Session(sessionBinary='/usr/bin/startkde', + scriptCmdList=args, scriptDelay=25) + if options.xserver: + session.xserver.server = options.xserver + pid = session.start() + scriptExitCode = session.script.exitCode + if options.logout == 'y': + session.attemptLogout() + if options.terminate == 'y': + session.stop() + else: + session.wait() + sys.exit(scriptExitCode) + +if __name__ == "__main__": + main() diff --git a/build/scripts-2.7/dogtail-run-headless-next b/build/scripts-2.7/dogtail-run-headless-next new file mode 100755 index 00000000000..ae481cf1c44 --- /dev/null +++ b/build/scripts-2.7/dogtail-run-headless-next @@ -0,0 +1,319 @@ +#!/usr/bin/python +descr = """ +Unlike the original headless script this will make use of an Display Manager +(DM - currently gdm) to handle starting the X server and user session. It's motivated +by changes related to systemd - that disallows running a gnome session from an +environment spawned by 'su'. The original headless will not work in these cases +anymore on systemd systems + +Instead this script uses the AutoLogin feature of the DM, so that when it starts DM's +service the session will login for particular user at once. It then uses the +environment properties from the new session and runs the target script inthere. + +Will work with distros where 'service gdm/kdm start/stop' takes an effect, and quite +likely only on systemd systems that use systemd-logind service. + +Even if you are still able to use dogtail-run-headless in your usecase, you might +consider switching to this script - as making use of DM is likely to be more reliable +and less buggy compared to headless itself taking care of everything. +""" + +drop_overview = '''from dogtail.utils import absoluteMotion, keyPress +absoluteMotion(100,100) +keyPress('esc')''' + +import argparse +import sys +import os +import glob +import subprocess +import time +import ConfigParser +import shutil +import re +from dogtail.sessions import Script + +preserve_envs = ['PYTHONPATH', 'TEST'] + + +def getSessionEnvironment(sessionBinary): + + def isSessionProcess(fileName): + try: + if os.path.realpath(fileName + 'exe') != ('/usr/bin/plasma-desktop' + if sessionBinary.split('/')[-1] == 'startkde' + else sessionBinary): + return False + except OSError: + return False + pid = fileName.split('/')[2] + if pid == 'self' or pid == str(os.getpid()): + return False + return True + + def getEnvDict(fileName): + try: + envString = open(fileName, 'r').read() + except IOError: + return {} + envItems = envString.split('\x00') + envDict = {} + for item in envItems: + if not '=' in item: + continue + k, v = item.split('=', 1) + envDict[k] = v + return envDict + + def copyVars(envDict): + '''Copy a couple of old variables we want to preserve''' + for env in preserve_envs: + if os.environ.has_key(env): + envDict[env] = os.environ[env] + return envDict + + envDict = False + for path in glob.glob('/proc/*/'): + if not isSessionProcess(path): + continue + envFile = path + 'environ' + envDict = getEnvDict(envFile) + if not envDict: + raise RuntimeError("Can't find our environment!") + return copyVars(envDict) + + +def execCodeWithEnv(code, env=None): + with open("/tmp/execcode.dogtail", "w") as text_file: + text_file.write(code) + subprocess.Popen('python /tmp/execcode.dogtail'.split(), + env=(os.environ if env is None else env)).wait() + + +class DisplayManagerSession(object): + + gdm_config = '/etc/gdm/custom.conf' + kdm_config = '/etc/kde/kdm/kdmrc' + gdm_options = {'section': 'daemon', 'enable': + 'AutomaticLoginEnable', 'user': 'AutomaticLogin'} + kdm_options = {'section': 'X-:0-Core', 'enable': + 'AutoLoginEnable', 'user': 'AutoLoginUser'} + scriptDelay = 20 + user = 'test' + + def isProcessRunning(self, process): + '''Gives true if process can be greped out of full ps dump ''' + s = subprocess.Popen(["ps", "axw"], stdout=subprocess.PIPE) + for x in s.stdout: + if re.search(process, x): + return True + return False + + def waitForProcess(self, process, invert=False): + '''Waits until a process appears''' + while self.isProcessRunning(process) is invert: + time.sleep(1) + + def __init__(self, dm='gdm', session='gnome', session_binary='gnome-shell', user=None): + self.session_binary = session_binary + self.session = session + self.accountfile = '/var/lib/AccountsService/users/%s' % self.user + if user is not None: + self.user = user + if dm == 'gdm': + self.tmp_file = '/tmp/%s' % os.path.basename(self.gdm_config) + self.options = self.gdm_options + self.config = self.gdm_config + elif dm == 'kdm': + self.tmp_file = '/tmp/%s' % os.path.basename(self.kdm_config) + self.options = self.kdm_options + self.config = self.kdm_config + self.dm = dm + + def setup(self, restore=False): + shutil.copy(self.config, self.tmp_file) + config = ConfigParser.SafeConfigParser() + config.optionxform = str + config.read(self.tmp_file) + if not restore: + config.set(self.options['section'], self.options['enable'], 'true') + config.set( + self.options['section'], self.options['user'], self.user) + else: + config.remove_option( + self.options['section'], self.options['enable']) + config.remove_option(self.options['section'], self.options['user']) + output = open(self.tmp_file, 'w') + config.write(output) + output.flush() + subprocess.Popen('sudo mv -f %s %s' % + (self.tmp_file, self.config), shell=True).wait() + if not restore: + if 'kwin' in self.session_binary: + try: + os.makedirs(os.getenv('HOME') + '/.kde/env/') + except: + pass + subprocess.Popen( + 'echo "export QT_ACCESSIBILITY=1" > ~/.kde/env/qt-at-spi.sh', shell=True).wait() + if self.dm == 'gdm': + need_restart = False + tempfile = '/tmp/%s_headless' % self.user + subprocess.Popen('cp -f %s %s' % (self.accountfile, tempfile), shell=True).wait() + account_config = ConfigParser.SafeConfigParser() + account_config.optionxform = str + account_config.read(tempfile) + try: + saved_session = account_config.get('User', 'XSession') + if self.session is None: + if 'kde' in saved_session and 'gnome-shell' in self.session_binary: + self.session_binary = '/usr/bin/kwin' + elif 'gnome' in saved_session and 'kwin' in self.session_binary: + self.session_binary = '/usr/bin/gnome-shell' + elif saved_session != self.session: + account_config.set('User', 'XSession', self.session) + need_restart = True + except ConfigParser.NoSectionError: + account_config.add_section('User') + account_config.set('User', 'XSession', self.session if self.session else '') + account_config.set('User', 'SystemAccount', 'false') + need_restart = True + if need_restart: + output = open(tempfile, 'w') + account_config.write(output) + output.flush() + subprocess.Popen('sudo mv -f %s %s' % (tempfile, self.accountfile), shell=True).wait() + time.sleep(1) + os.system('sudo service accounts-daemon restart') + time.sleep(6) # prevent a possible race condition + os.system('sudo service systemd-logind restart') + time.sleep(6) # these are fast, but we still need to make sure no races happen + else: + subprocess.Popen('sudo rm -f %s' % tempfile, shell=True).wait() + elif self.dm == 'kdm': + if self.session is not None: + subprocess.Popen( + 'echo "[Desktop]\nSession=%s" > /home/%s/.dmrc' % (self.session, self.user), shell=True).wait() + + def start(self, restart=False): + if restart: + subprocess.Popen(('sudo service %s stop' % (self.dm)).split()) + time.sleep(0.5) + subprocess.Popen(('sudo service %s start' % (self.dm)).split()) + self.waitForProcess(self.session_binary.split('/')[-1]) + # some extra time for an environment (shell/kwin) to load all resources + # etc. + if self.dm == 'kdm': + time.sleep(10) # KDE keeps loading screen on untill all is loaded + else: + time.sleep(4) # GNOME shows stuff as it appears + + def setA11y(self, enable): + subprocess.Popen('/usr/bin/gsettings set org.gnome.desktop.interface toolkit-accessibility %s' + % ('true' if enable else 'false'), shell=True, env=os.environ) +# if enable: # mouse is at 0x0 at the start - which brings in the overview +# execCodeWithEnv(drop_overview, env = os.environ) +# time.sleep(2) # time for the overview to go away + + def stop(self): + subprocess.Popen(('sudo service %s stop' % (self.dm)).split()).wait() + self.waitForProcess('/usr/bin/%s' % self.dm, invert=True) + time.sleep(3) # extra safe time + # did i.e. gnome-shell get stuck running? + if self.isProcessRunning(self.session_binary.split('/')[-1]): + print( + 'dogtail-run-headless-next: WARNING: %s still running, proceeding with kill -9' % + self.session_binary.split('/')[-1]) + subprocess.Popen( + ('sudo pkill --signal 9 %s' % (self.session_binary.split('/')[-1])).split()).wait() + time.sleep(1) + + +def parse(): + parser = argparse.ArgumentParser( + prog='$ dogtail-run-headless-next', description=descr, formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument('script', help="""Command to execute the script""") + parser.add_argument('--session', required=False, + help="""What session to use. Not specifying results in system default session on first login + or test user's last session with follow-up logins. + Otherwise you can set any xsession desktop file name here (i.e. 'gnome-classic', 'gnome', 'lxde' etc.). + 'kde' defaults to 'kde-plasma for legacy reasons.'""") + parser.add_argument('--session-binary', required=False, + help="""Specify an in-session ever-running binary (full path) to get the environment from. Only needed for non-gnome/kde sessions.""") + parser.add_argument('--dm', required=False, + help="""What display manager to use for spawning session. Supported are 'gdm' (default) and 'kdm'.""") + parser.add_argument('--restart', action='store_true', + help="""Restart previously running display manager session before script execution.""") + parser.add_argument('--dont-start', action='store_true', + help="""Use already running session (doesn't have to be under Display Manager)""") + parser.add_argument('--dont-kill', action='store_true', + help="""Do not kill session when script exits.""") + parser.add_argument('--disable-a11y', action='store_true', + help="""Disable accessibility technologies on script(not session) exit.""") + return parser.parse_args() + + +def main(): + args = parse() + scriptCmdList = args.script.split() + + if args.session is None or 'gnome' in args.session: + if args.session_binary is None: + args.session_binary = '/usr/bin/gnome-shell' + elif 'kde' in args.session: + if args.session_binary is None: + args.session_binary = '/usr/bin/kwin' + if args.session == 'kde': + args.session = 'kde-plasma' + else: + if args.session_binary is None: + print('dogtail-run-headless-next: Need to specify a --session-binary ever-present in the session to get env from.') + sys.exit(-1) + if args.dm == 'gdm' or args.dm is None: + dm_name = 'gdm' + elif args.dm == 'kdm': + dm_name = 'kdm' + else: + print('dogtail-run-headless-next: I do not recognize the display manager!') + sys.exit(-1) + + print('dogtail-run-headless-next: Using display manager: %s' % dm_name) + + dm = DisplayManagerSession(dm_name, args.session, args.session_binary) + + if args.dont_start is not True: + dm.setup() + dm.start(restart=args.restart) + + print('dogtail-run-headless-next: Using %s to bind to the session' % dm.session_binary) + + try: + os.environ = getSessionEnvironment(dm.session_binary) + except: + print( + 'dogtail-run-headless-next: Could not get the environment from %s process' % + dm.session_binary) + dm.stop() + dm.setup(restore=True) + sys.exit(1) + + if dm_name == 'gdm': + dm.setA11y(True) + + script = Script(scriptCmdList) + scriptPid = script.start() + print('dogtail-run-headless-next: Started the script with PID %d' % scriptPid) + exitCode = script.wait() + print('dogtail-run-headless-next: The script has finnished with return code %d' % exitCode) + + if args.disable_a11y is True: + dm.setA11y(False) + + if args.dont_kill is False: + dm.stop() + dm.setup(restore=True) + + sys.exit(exitCode) + +if __name__ == "__main__": + main() diff --git a/build/scripts-2.7/sniff b/build/scripts-2.7/sniff new file mode 100755 index 00000000000..f55adfa9663 --- /dev/null +++ b/build/scripts-2.7/sniff @@ -0,0 +1,798 @@ +#!/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. +""" +import gi +gi.require_version('Gtk', '3.0') +gi.require_version('Gdk', '3.0') +import sys +from dogtail.config import config + +if config.checkForA11y: + from dogtail.utils import checkForA11yInteractively + checkForA11yInteractively() + +config.logDebugToFile = False +config.childrenLimit = 100000 + +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 +from gi.repository import GLib + +builder = Gtk.Builder() + + +class SniffApp(object): + appName = 'Sniff' + appAuthors = ['Zack Cerza <zcerza@redhat.com>', + 'David Malcolm <dmalcolm@redhat.com'] + + def __init__(self): + self.builder = builder + import os + if os.path.exists('sniff.ui'): + self.builder.add_from_file('sniff.ui') + else: + import os + path = os.path.abspath( + os.path.join(__file__, os.path.pardir, os.path.pardir)) + if path is '/': # in case the path is /bin/sniff + path = '/usr' + self.builder.add_from_file(path + + '/share/dogtail/glade/sniff.ui') + self.app = self.builder.get_object(self.appName) + try: + self.app.set_icon_from_file('../icons/dogtail-head.svg') + except Exception: + import os + path = os.path.abspath( + os.path.join(__file__, os.path.pardir, os.path.pardir)) + if path is '/': + path = '/usr' + self.app.set_icon_from_file(os.path.join(path, + 'share/icons/hicolor/scalable/apps/dogtail-head.svg')) + self.setUpWidgets() + self.connectSignals() + self.app.show_all() + Gtk.main() + + def setUpWidgets(self): + 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') + self.autoRefreshMenuItem = self.builder.get_object('autorefresh') + self.setRootMenuItem = self.builder.get_object('setRootMenuItem') + self.unsetRootMenuItem = self.builder.get_object('unsetRootMenuItem') + self.about = None + + self.tree = SniffController() + + 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.autoRefreshMenuItem.connect( + 'toggled', self.tree.toggleAutoRefresh) + self.setRootMenuItem.connect('activate', self.tree.changeRoot, True) + self.unsetRootMenuItem.connect('activate', self.tree.changeRoot, False) + + self.setStartupAutoRefresh() + + def setStartupAutoRefresh(self): + import os + if not os.path.exists('/tmp/sniff_refresh.lock'): + self.autoRefreshMenuItem.set_active(True) + + 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.ResponseType.CANCEL: + window.hide() + + def quit(self, *args): + Gtk.main_quit() + + +class SniffController(object): + invalidBufferCallbackID = None + + def __init__(self): + self.builder = builder + self.nameTextLabel = self.builder.get_object('nameTextLabel') + self.nameTextLabel.set_text('') + self.roleNameTextLabel = self.builder.get_object('roleNameTextLabel') + self.roleNameTextLabel.set_text('') + self.descTextLabel = self.builder.get_object('descTextLabel') + self.descTextLabel.set_text('') + self.actionsTextLabel = self.builder.get_object('actionsTextLabel') + self.actionsTextLabel.set_text('') + self.textTextView = self.builder.get_object('textTextView') + self.textTextViewBufferCallbackID = self.invalidBufferCallbackID + self.textTextView.set_sensitive(False) + self.textTextView.get_buffer().set_text('') + self.labelerButton = self.builder.get_object('labelerButton') + self.labelerButton.set_sensitive(False) + self.labeleeButton = self.builder.get_object('labeleeButton') + self.labeleeButton.set_sensitive(False) + self.stateView = self.builder.get_object('stateTreeView') + self.highlight1 = self.builder.get_object('highlight1') + self.autorefresh = self.builder.get_object('autorefresh') + self.stateModel = StateModel() + self.setUpStateView() + self.treeView = self.builder.get_object('treeTreeView') + self.treeSelection = self.treeView.get_selection() + self.treeModel = SniffModel() + self.setUpTreeView() + self.connectSignals() + self.refresh() + + 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.TreeViewColumnSizing.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.TreeViewColumnSizing.AUTOSIZE) + column.set_resizable(True) + self.treeView.show() + path = 0 + self.treeView.expand_all() + #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_first()) + self.treeView.expand_all() + self.treeView.expand_row(rootPath, False) + + def toggleAutoRefresh(self, *args): + if self.autorefresh.get_active() is True: + 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') + self.refresh() + else: + pyatspi.Registry.deregisterEventListener( + self.treeModel.nodeChanged, + 'object:children-changed') + pyatspi.Registry.deregisterEventListener( + self.treeModel.nodeChanged, + 'object:property-change:accessible-name') + pyatspi.Registry.deregisterEventListener( + self.treeModel.nodeChanged, + 'object:property-change:accessible-state') + pyatspi.Registry.deregisterEventListener( + self.treeModel.nodeChanged, + 'object:state-changed') + + 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) + self.refresh() + + def selectionChanged(self, treeSelection): + node = self.getSelectedNode() + if node: + self.setUpBottomPane(node) + if self.highlight1.get_active() is True: + 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 == Gdk.KEY_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: + self.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.doActionNamed, action) + menuItem.show() + self.menu.append(menuItem) + if not menuItem: + return + self.menu.show_all() + self.menu.popup(None, 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): + self.builder = builder + #self.autorefresh = self.builder.get_object('autorefresh') + Gtk.TreeStore.__init__(self, GObject.TYPE_PYOBJECT, + GObject.TYPE_STRING, GdkPixbuf.Pixbuf) + root = pyatspi.Registry.getDesktop(0) + self.rootNode = self.initialRootNode = root + self.appendAndPopulate(None, self.rootNode) + + def __contains__(self, item): + from dogtail.tree import Node + if isinstance(item, 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) + 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") + 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 + root = pyatspi.Registry.getDesktop(0) + 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(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): + # if self.autorefresh.get_active() is False: return + 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.IconTheme().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.IconLookupFlags.USE_BUILTIN) + except GObject.GError: + try: + return theme.load_icon(node.name.lower(), 24, + Gtk.IconLookupFlags.USE_BUILTIN) + except GObject.GError: + return None + elif node.parent: + return iconForRole[node.role] + else: + return theme.load_icon("user-desktop", 24, + Gtk.IconLookupFlags.USE_BUILTIN) + except Exception: + return theme.load_icon("dialog-error", 24, + Gtk.IconLookupFlags.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 = GdkPixbuf.Pixbuf.new_from_file('icons/' + iconName) + except GObject.GError: + import os + path = os.path.abspath( + os.path.join(__file__, os.path.pardir, os.path.pardir)) + if path is '/': + path = '/usr' + iconName = os.path.join(path, 'share/dogtail/icons/', iconName) + pixbuf = GdkPixbuf.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(): + from dogtail.utils import Lock + # We need this to prohibit sniff making(and removing on exit) + # sniff_refresh lock when importing Node + sniff_running = Lock(lockname='sniff_running.lock', randomize=False) + try: + sniff_running.lock() + except OSError: + pass + sniff = SniffApp() + +if __name__ == '__main__': + main() |