aboutsummaryrefslogtreecommitdiffstats
path: root/build/scripts-2.7
diff options
context:
space:
mode:
Diffstat (limited to 'build/scripts-2.7')
-rwxr-xr-xbuild/scripts-2.7/dogtail-detect-session66
-rwxr-xr-xbuild/scripts-2.7/dogtail-logout28
-rwxr-xr-xbuild/scripts-2.7/dogtail-run-headless79
-rwxr-xr-xbuild/scripts-2.7/dogtail-run-headless-next319
-rwxr-xr-xbuild/scripts-2.7/sniff798
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()