From 4384051284e982fd504a1991d86135312f0f9e34 Mon Sep 17 00:00:00 2001 From: Tim McNamara Date: Sat, 14 Aug 2010 01:08:00 +0000 Subject: initial commit --- diff --git a/Experior.Activity/MANIFEST b/Experior.Activity/MANIFEST new file mode 100644 index 0000000..1115bb7 --- /dev/null +++ b/Experior.Activity/MANIFEST @@ -0,0 +1,42 @@ +test_sugarbot.pyc +test_sbexecutionengine.pyc +sblog.pyc +sbexecutionengine.py +test_widgetIdentifier.py +test_sbgui.pyc +sugarbot.py +test_sbgui.py +sbgui.py +test_widgetIdentifier.pyc + +script_terminal.py +sbrpcserver.pyc +sbconfig_sample.pyc +MANIFEST +test_rpcserver.pyc +MANIFEST~ +sbrpcserver.py +sbpython_script.py +activity/activity.info +script_calculate.py +sbexecutionengine.pyc +setup.py +test_sbexecutionengine.py +sugarbot.pyc +sbdecorators.pyc +make-manifest +sbgui.pyc +widgetIdentifier.pyc +sugarbotlauncher.py +script_terminal.pyc +sblog.py +master.cfg +sbdecorators.py +sbpython.pyc +test_rpcserver.py +script_calculate.pyc +test_sugarbot.py +modded-main.py +widgetIdentifier.py +sbpython.py +sbconfig_sample.py diff --git a/Experior.Activity/activity/activity.info b/Experior.Activity/activity/activity.info new file mode 100644 index 0000000..75dbed0 --- /dev/null +++ b/Experior.Activity/activity/activity.info @@ -0,0 +1,7 @@ +[Activity] +name = sugarbot +service_name = org.laptop.sugarbot +class = sugarbot.sugarbot +icon = sugarbot +activity_version = 1 +show_launcher = yes diff --git a/Experior.Activity/activity/sugarbot.svg b/Experior.Activity/activity/sugarbot.svg new file mode 100644 index 0000000..c5ba0c2 --- /dev/null +++ b/Experior.Activity/activity/sugarbot.svg @@ -0,0 +1,64 @@ + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/Experior.Activity/make-manifest b/Experior.Activity/make-manifest new file mode 100755 index 0000000..69e5c8a --- /dev/null +++ b/Experior.Activity/make-manifest @@ -0,0 +1,2 @@ +find . -type f | sed 's,^./,,g' > MANIFEST +chmod -R a+x *.py \ No newline at end of file diff --git a/Experior.Activity/master.cfg b/Experior.Activity/master.cfg new file mode 100644 index 0000000..ff0d4c5 --- /dev/null +++ b/Experior.Activity/master.cfg @@ -0,0 +1,78 @@ +# -*- python -*- +# ex: set syntax=python: + +# This is a sample buildmaster config file. It must be installed as +# 'master.cfg' in your buildmaster's base directory (although the filename +# can be changed with the --basedir option to 'mktap buildbot master'). + +# It has one job: define a dictionary named BuildmasterConfig. This +# dictionary has a variety of keys to control different aspects of the +# buildmaster. They are documented in docs/config.xhtml . + +import os.path +#from buildbot.changes.freshcvs import FreshCVSSource +from buildbot.scheduler import Scheduler, Periodic +from buildbot.process import buildstep, factory +from buildbot.status import html +from buildbot.buildslave import BuildSlave +from buildbot.changes.pb import PBChangeSource +from buildbot.scheduler import Scheduler, Periodic +from buildbot.process import buildstep +from buildbot.steps.shell import ShellCommand +from buildbot.steps.source import SVN +from buildbot.status import html +s = factory.s +c = BuildmasterConfig = {} + +####### BUILDSLAVES +c['slaves'] = [BuildSlave("bot1name", "bot1passwd")] +c['slavePortnum'] = 9989 + +c['change_source'] = PBChangeSource() + +####### SCHEDULING + +periodic = Periodic("every_6_hours", ["sugarbot_trunk"], 6*60*60) +# periodic = Periodic("every_30_minutes", ["sugarbot_trunk"], 30*60) +c['schedulers'] = [periodic] + +####### SVN +source = s(SVN, mode='update', +baseURL='http://sugarbot.googlecode.com/svn/', +defaultBranch='trunk/sugarbot') + +####### NOSE +class NoseTest(ShellCommand): + name = "nose tests" + description = ["running nose tests"] + descriptionDone = [name] + +nose_tests = s(NoseTest, command="/usr/bin/nosetests") + +###### SUGARBOT +class SugarbotTest(ShellCommand): + name = "sugarbot tests" + description = ["running sugarbot tests"] + description = [name] +sugarbot_tests = s(SugarbotTest, command="/usr/bin/env python sugarbotlauncher.py") + +####### BUILDFACTORY +f = factory.BuildFactory([source,nose_tests,sugarbot_tests]) + +c['builders'] = [ +{'name':'sugarbot_trunk', +'slavename':'sugarbot', +'builddir':'test-APP-linux', +'factory':f }, +] + +c['status'] = [] + +####### STATUS +c['status'].append(html.WebStatus(http_port=8010)) + +####### PROJECT INFO +c['projectName'] = "sugarbot" +c['projectURL'] = "http://code.google.com/p/sugarbot/" +c['buildbotURL'] = "http://localhost:8010/" +c['bots']=[("sugarbot","password")] diff --git a/Experior.Activity/modded-main.py b/Experior.Activity/modded-main.py new file mode 100755 index 0000000..e700e85 --- /dev/null +++ b/Experior.Activity/modded-main.py @@ -0,0 +1,157 @@ +# Copyright (C) 2006, Red Hat, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import sys +import os +from ConfigParser import ConfigParser +import gettext + +# HACK we need to import numpy before gtk otherwise we traceback in +# some locales. See http://dev.laptop.org/ticket/5559. +import numpy + +import pygtk +pygtk.require('2.0') +import gtk +import gobject + +from sugar import env +from sugar import logger +from sugar.profile import get_profile + +from view.Shell import Shell +from model.shellmodel import ShellModel +from shellservice import ShellService +from hardware import hardwaremanager +from intro import intro +import logsmanager +import config + +def _start_matchbox(): + cmd = ['matchbox-window-manager'] + + cmd.extend(['-use_titlebar', 'no']) + cmd.extend(['-theme', 'sugar']) + cmd.extend(['-kbdconfig', os.path.join(config.data_path, 'kbdconfig')]) + + gobject.spawn_async(cmd, flags=gobject.SPAWN_SEARCH_PATH) + +def _save_session_info(): + # Save our DBus Session Bus address somewhere it can be found + # + # WARNING!!! this is going away at some near future point, do not rely on it + # + session_info_file = os.path.join(env.get_profile_path(), "session.info") + f = open(session_info_file, "w") + + cp = ConfigParser() + cp.add_section('Session') + cp.set('Session', 'dbus_address', os.environ['DBUS_SESSION_BUS_ADDRESS']) + cp.set('Session', 'display', gtk.gdk.display_get_default().get_name()) + cp.write(f) + + f.close() + +def _setup_translations(): + locale_path = os.path.join(config.prefix, 'share', 'locale') + domain = 'sugar' + + gettext.bindtextdomain(domain, locale_path) + gettext.textdomain(domain) + +def check_cm(bus_name): + try: + import dbus + bus = dbus.SessionBus() + bus_object = bus.get_object('org.freedesktop.DBus', '/org/freedesktop/DBus') + name = bus_object.GetNameOwner(bus_name, dbus_interface='org.freedesktop.DBus') + if name: + return True + except dbus.DBusException: + pass + return False + +def _shell_started_cb(): + # Unfreeze the display + hw_manager = hardwaremanager.get_manager() + hw_manager.set_dcon_freeze(0) + +def main(): + gobject.idle_add(_shell_started_cb) + + logsmanager.setup() + logger.start('shell') + + _save_session_info() + _start_matchbox() + _setup_translations() + + hw_manager = hardwaremanager.get_manager() + hw_manager.startup() + + icons_path = os.path.join(config.data_path, 'icons') + gtk.icon_theme_get_default().append_search_path(icons_path) + + # Do initial setup if needed + if not get_profile().is_valid(): + win = intro.IntroWindow() + win.show_all() + gtk.main() + + if os.environ.has_key("SUGAR_TP_DEBUG"): + # Allow the user time to start up telepathy connection managers + # using the Sugar DBus bus address + import time + from telepathy.client import ManagerRegistry + + registry = ManagerRegistry() + registry.LoadManagers() + + debug_flags = os.environ["SUGAR_TP_DEBUG"].split(',') + for cm_name in debug_flags: + if cm_name not in ["gabble", "salut"]: + continue + + try: + cm = registry.services[cm_name] + except KeyError: + print RuntimeError("%s connection manager not found!" % cm_name) + + while not check_cm(cm['busname']): + print "Waiting for %s on: DBUS_SESSION_BUS_ADDRESS=%s" % \ + (cm_name, os.environ["DBUS_SESSION_BUS_ADDRESS"]) + try: + time.sleep(5) + except KeyboardInterrupt: + print "Got Ctrl+C, continuing..." + break + + model = ShellModel() + shell = Shell(model) + service = ShellService(shell) + + if os.environ.has_key("SUGARBOT_EMULATOR"): + sys.path.append(os.environ['SUGARBOT_PATH']) + from sugarbotlauncher import SugarbotLauncher + sbLauncher = SugarbotLauncher(shell, model) + + try: + gtk.main() + except KeyboardInterrupt: + print 'Ctrl+C pressed, exiting...' + + session_info_file = os.path.join(env.get_profile_path(), "session.info") + os.remove(session_info_file) \ No newline at end of file diff --git a/Experior.Activity/rm b/Experior.Activity/rm new file mode 100644 index 0000000..1d5c9ee --- /dev/null +++ b/Experior.Activity/rm @@ -0,0 +1,2 @@ +./.svn +./activity/.svn diff --git a/Experior.Activity/sbRpcServer.log b/Experior.Activity/sbRpcServer.log new file mode 100644 index 0000000..e71d6c7 --- /dev/null +++ b/Experior.Activity/sbRpcServer.log @@ -0,0 +1,46 @@ +08-07 18:48 sbRpcServer INFO Listening on port 54321 +08-07 18:48 sbRpcServer INFO Kill: True Restart: True +08-07 18:48 sbRpcServer INFO Added script script_calculate.py [Activity Calculate] +08-07 18:48 sbRpcServer INFO Added script script_terminal.py [Activity Terminal] +08-07 18:55 sbRpcServer INFO Listening on port 54321 +08-07 18:55 sbRpcServer INFO Kill: True Restart: True +08-07 18:55 sbRpcServer INFO Added script script_calculate.py [Activity Calculate] +08-07 18:55 sbRpcServer INFO Added script script_calculate.pyc [Activity Calculate] +08-07 18:55 sbRpcServer INFO Added script script_terminal.py [Activity Terminal] +08-07 18:55 sbRpcServer INFO Added script script_terminal.pyc [Activity Terminal] +08-08 11:29 sbRpcServer INFO Listening on port 54321 +08-08 11:29 sbRpcServer INFO Kill: True Restart: True +08-08 11:29 sbRpcServer INFO Added script script_calculate.py [Activity Calculate] +08-08 11:29 0 INFO Starting script_calculate.py +08-08 11:38 sbRpcServer INFO Listening on port 54321 +08-08 11:38 sbRpcServer INFO Kill: True Restart: True +08-08 11:38 sbRpcServer INFO Added script script_calculate.py [Activity Calculate] +08-08 11:38 0 INFO Starting script_calculate.py +08-08 11:39 0 INFO Starting script_calculate.py +08-08 11:40 0 INFO Starting script_calculate.py +08-08 11:40 0 INFO Starting script_calculate.py +08-08 11:42 0 INFO Starting script_calculate.py +08-08 11:43 0 INFO Getting Script: script_calculate.py +08-08 11:43 0 ERROR Execution failed: +Traceback (most recent call last): + File "/home/tim/Activities/sugarbot.activity/sbexecutionengine.py", line 86, in executePy + sugarbot_main(self.widgets) + File "Sugarbot Script: 'script_calculate.py'", line 8, in sugarbot_main + File "/home/tim/Activities/sugarbot.activity/sbpython.py", line 548, in __getitem__ + raise WidgetDoesNotExist, which +WidgetDoesNotExist: 'The widget Share with: could not be identified.' + +08-14 11:23 sbRpcServer INFO Listening on port 54321 +08-14 11:23 sbRpcServer INFO Kill: True Restart: True +08-14 11:23 sbRpcServer INFO Added script script_calculate.py [Activity Calculate] +08-14 11:24 0 INFO Starting script_calculate.py +08-14 11:24 0 INFO Getting Script: script_calculate.py +08-14 11:24 0 ERROR Execution failed: +Traceback (most recent call last): + File "/home/tim/Activities/sugarbot.activity/sbexecutionengine.py", line 86, in executePy + sugarbot_main(self.widgets) + File "Sugarbot Script: 'script_calculate.py'", line 8, in sugarbot_main + File "/home/tim/Activities/sugarbot.activity/sbpython.py", line 548, in __getitem__ + raise WidgetDoesNotExist, which +WidgetDoesNotExist: 'The widget Share with: could not be identified.' + diff --git a/Experior.Activity/sbconfig.py b/Experior.Activity/sbconfig.py new file mode 100755 index 0000000..dfd84b4 --- /dev/null +++ b/Experior.Activity/sbconfig.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python +# encoding: utf-8 + +# The host that the sugarbot clients should connect to in order to retrieve +# the commands that they need to execute. +host = "localhost" + +# The port to connect over. This is the same port that the XML-RPC server +# will be listening on. +port = 54321 + +# Client name. +clientName = "testClientName" \ No newline at end of file diff --git a/Experior.Activity/sbconfig.pyc b/Experior.Activity/sbconfig.pyc new file mode 100644 index 0000000..910601e --- /dev/null +++ b/Experior.Activity/sbconfig.pyc Binary files differ diff --git a/Experior.Activity/sbconfig_sample.py b/Experior.Activity/sbconfig_sample.py new file mode 100755 index 0000000..dfd84b4 --- /dev/null +++ b/Experior.Activity/sbconfig_sample.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python +# encoding: utf-8 + +# The host that the sugarbot clients should connect to in order to retrieve +# the commands that they need to execute. +host = "localhost" + +# The port to connect over. This is the same port that the XML-RPC server +# will be listening on. +port = 54321 + +# Client name. +clientName = "testClientName" \ No newline at end of file diff --git a/Experior.Activity/sbconfig_sample.pyc b/Experior.Activity/sbconfig_sample.pyc new file mode 100644 index 0000000..d88c2a6 --- /dev/null +++ b/Experior.Activity/sbconfig_sample.pyc Binary files differ diff --git a/Experior.Activity/sbdecorators.py b/Experior.Activity/sbdecorators.py new file mode 100755 index 0000000..fcc85c9 --- /dev/null +++ b/Experior.Activity/sbdecorators.py @@ -0,0 +1,148 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +sbpython.py + +This file is part of sugarbot. + +sugarbot is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +sugarbot is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with sugarbot. If not, see . +""" + +""" +One of three degrees of enforcement may be specified by passing +the 'debug' keyword argument to the decorator: + 0 -- NONE: No type-checking. Decorators disabled. + 1 -- MEDIUM: Print warning message to stderr. (Default) + 2 -- STRONG: Raise TypeError with message. +If 'debug' is not passed to the decorator, the default level is used. + +Example usage: + >>> NONE, MEDIUM, STRONG = 0, 1, 2 + >>> + >>> @accepts(int, int, int) + ... @returns(float) + ... def average(x, y, z): + ... return (x + y + z) / 2 + ... + >>> average(5.5, 10, 15.0) + TypeWarning: 'average' method accepts (int, int, int), but was given + (float, int, float) + 15.25 + >>> average(5, 10, 15) + TypeWarning: 'average' method returns (float), but result is (int) + 15 + +Needed to cast params as floats in function def (or simply divide by 2.0). + + >>> TYPE_CHECK = STRONG + >>> @accepts(int, debug=TYPE_CHECK) + ... @returns(int, debug=TYPE_CHECK) + ... def fib(n): + ... if n in (0, 1): return n + ... return fib(n-1) + fib(n-2) + ... + >>> fib(5.3) + Traceback (most recent call last): + ... + TypeError: 'fib' method accepts (int), but was given (float) + +""" +import sys + +def accepts(*types, **kw): + """ Function decorator. Checks that inputs given to decorated function + are of the expected type. + + Parameters: + types -- The expected types of the inputs to the decorated function. + Must specify type for each parameter. + kw -- Optional specification of 'debug' level (this is the only valid + keyword argument, no other should be given). + debug = ( 0 | 1 | 2 ) + + """ + if not kw: + # default level: MEDIUM + debug = 1 + else: + debug = kw['debug'] + try: + def decorator(f): + def newf(*args): + if debug == 0: + return f(*args) + assert len(args) == len(types) + argtypes = tuple(map(type, args)) + if argtypes != types: + msg = info(f.__name__, types, argtypes, 0) + if debug == 1: + print >> sys.stderr, 'TypeWarning: ', msg + elif debug == 2: + raise TypeError, msg + return f(*args) + newf.__name__ = f.__name__ + return newf + return decorator + except KeyError, key: + raise KeyError, key + "is not a valid keyword argument" + except TypeError, msg: + raise TypeError, msg + +def returns(ret_type, **kw): + """ Function decorator. Checks that return value of decorated function + is of the expected type. + + Parameters: + ret_type -- The expected type of the decorated function's return value. + Must specify type for each parameter. + kw -- Optional specification of 'debug' level (this is the only valid + keyword argument, no other should be given). + debug=(0 | 1 | 2) + + """ + try: + if not kw: + # default level: MEDIUM + debug = 1 + else: + debug = kw['debug'] + def decorator(f): + def newf(*args): + result = f(*args) + if debug == 0: + return result + res_type = type(result) + if res_type != ret_type: + msg = info(f.__name__, (ret_type,), (res_type,), 1) + if debug == 1: + print >> sys.stderr, 'TypeWarning: ', msg + elif debug == 2: + raise TypeError, msg + return result + newf.__name__ = f.__name__ + return newf + return decorator + except KeyError, key: + raise KeyError, key + "is not a valid keyword argument" + except TypeError, msg: + raise TypeError, msg + +def info(fname, expected, actual, flag): + """ Convenience function returns nicely formatted error/warning msg. """ + format = lambda types: ', '.join([str(t).split("'")[1] for t in types]) + expected, actual = format(expected), format(actual) + msg = "'%s' method " % fname \ + + ("accepts", "returns")[flag] + " (%s), but " % expected\ + + ("was given", "result is")[flag] + " (%s)" % actual + return msg diff --git a/Experior.Activity/sbdecorators.pyc b/Experior.Activity/sbdecorators.pyc new file mode 100644 index 0000000..2edb9c5 --- /dev/null +++ b/Experior.Activity/sbdecorators.pyc Binary files differ diff --git a/Experior.Activity/sbexecutionengine.py b/Experior.Activity/sbexecutionengine.py new file mode 100755 index 0000000..bb67fe0 --- /dev/null +++ b/Experior.Activity/sbexecutionengine.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +sbexecutionengine.py + +This file is part of sugarbot. + +sugarbot is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +sugarbot is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with sugarbot. If not, see . +""" +import gobject +import logging +import os +import pygtk +import sys +import threading +import time +import traceback + +pygtk.require('2.0') +import gtk + +from sbpython import * + +class sbExecutionEngine: + """ + Responsible for communications with the XML-RPC server regarding command + retrieval, as well as parsing and execution of those commands. + """ + def __init__(self, sbGUI, xmlRpcServer): + self.sbgui = sbGUI + self.rpc = xmlRpcServer + self.widgets = sbwidgets + + self.delayedExecution = False + self.executionComplete = False + self.lastCommand = None + + self.log = logging.getLogger("ExecutionEngine") + + try: + self.id = os.environ['SUGARBOT_ID'] + except KeyError: + self.log.error("Sugarbot ID is not set. Using default 0. ") + self.id = 0 + + def killSugarbot(self): + """ + Kills the sugarbot activity. + """ + self.log.info("Attempting to kill sugarbot") + sys.exit(0) + + def executePy(self): + """ + Executes the Sugarbot p◊ython script. + + Executes the Sugarbot python script provided by the XML-RPC server. + Regards any unhandled exceptions as fatal errors, and reports them + to the server. Also handles the termination of Sugarbot if the + auto-restart flag has been set by the XML-RPC server. + """ + self.script = self.rpc.getScript(self.id) + self.log.info("Executing Script:\n%s" % self.script) + + self.widgets.gui = self.sbgui + + # Execute the actual python script. + try: + exec self.script + + # t = threading.Thread(args=(self.widgets,)) + # t.run = sugarbot_main + # t.start() + + sugarbot_main(self.widgets) + self.rpc.success(self.id) + + # Something bad happened. Report it, and log it. + except: + text = "Execution failed:\n%s" % traceback.format_exc() + text = self.rpc.fail(text, self.id) + self.log.error( text ) + + # Regarless of the success status, check to see if we need to restart + # sugarbot or not. + finally: + restart = self.rpc.getRestartFlag() + self.log.info("Got restart flag: " + str(restart)) + + if restart: + self.killSugarbot() + + + def isComplete(self): + """ + Returns true if execution is complete. + + Returns true if execution is complete, i.e. there are no more + commands that can be executed. + """ + return self.executionComplete \ No newline at end of file diff --git a/Experior.Activity/sbexecutionengine.pyc b/Experior.Activity/sbexecutionengine.pyc new file mode 100644 index 0000000..3fc714f --- /dev/null +++ b/Experior.Activity/sbexecutionengine.pyc Binary files differ diff --git a/Experior.Activity/sbgui.py b/Experior.Activity/sbgui.py new file mode 100755 index 0000000..3593f38 --- /dev/null +++ b/Experior.Activity/sbgui.py @@ -0,0 +1,321 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +sbgui.py + +This file is part of sugarbot. + +sugarbot is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +sugarbot is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with sugarbot. If not, see . +""" +import gobject +import os +import pygtk +import sys +import time +pygtk.require('2.0') +import gtk +import logging +import sugar + +from gobject import idle_add +from gtk import gdk +from gtk.gdk import event_handler_set +from sbexecutionengine import sbExecutionEngine +from sugar import graphics +from sugar.graphics.toolbutton import Palette +from widgetIdentifier import * + +class sbGUI(gobject.GObject): + """ + Responsible for tracking all identifiable* widgets instantiated by GTK. + + *Identifiable widgets are widgets for which a valid identifier can be + found, via any of the identifiers enumerated by widgetIdentifier.identifiers. + """ + def __init__(self,sugarbotInstance,rpcServer): + """ + Initialize internal variables, and register self as an event handler. + """ + # Necessary, due to inheritance + gobject.GObject.__init__(self) + + # Get the RPC server connection and the sugarbot instance, as well + # as the execution engine. + self.sugarbot = sugarbotInstance + self.rpc = rpcServer + self.engine = sbExecutionEngine(self,self.rpc) + self.log = logging.getLogger('sbGUI') + + # Keep track of the amount of idle time... + self.idletime = 0 + self.idletimeout= 6 + + # In order to keep track of all of the windows, we set up a + # dict of Windows. For each window, the key is its id (as provided + # by id(theWindowObject). The value of each dictionary entry is a + # dictionary of Widgets, set up the same way -- the id() of the + # widget is the key, and the + self.trackedWidgets = {} # Maps id(object) -> widget + self.names = {} # Maps identifier -> widget + + # Register one of our methods to intercept all GTK events + self.registerEventHandler() + + + def eventHandler(self,event=None): + """ Intercepts all GDK events. We then send them off to a separate + handler method (to keep things clean), and then have GTK execute + whatever the event is supposed to do. + """ + if event is not None: + gtk.main_do_event(event) + self.handleEvent(event) + + if event.type is not gtk.gdk.EXPOSE: + self.idletime = 0 + + return True + + + def getWidgetIdentifier(self,widget): + """ + Returns the Widget's unique identifier (for example, a button's + label, or a gkt.Entry's name), or None if it does not have one. + Also, this function filters out many uninitialized identifiers, + such as "GtkToolbar" or "GtkButton" + @param widget - The widget whose identifier is to be retrieved. + """ + if not isinstance(widget,gtk.Widget): + raise "_getWidgetName must take a gtk.Widget object as its argument" + + # ---- GENERIC APPROACH ---- + # Assuming that the widget was named explicitly by the developers, + # getting the name should be very straightforward, with no specialized + # cases or special name-detecting. + widId = widgetIdentifier(widget) + ident = widId.getIdentifier() + + if ident is not None: + return ident + + # ---- SPECIALIZED APPROACH ---- + # Check to see if we have an identifier for the specific type + # of widget before we iterate through all of the different identifiers + # hoping to get a hit. + if widgetIdentifier.identifiers.has_key(widget.__class__): + widId = widgetIdentifier.identifiers[widget.__class__](widget) + ident = widId.getIdentifier() + + if ident is not None: + return ident + + # ---- BRUTE FORCE ---- + # The widget was not named explicitly by the developers, and we do + # not have a case for the specific widget class. + # Iterate through all of our potential identifiers, since we + # very likely have a widget class that it inherits from. + # This method is undesirable, since the identifiers in the dictionary + # widgetIdentifiers may be in a different order each time the + # program is run, due to the non-ordered nature of dictionaries. + for identifier in widgetIdentifier.identifiers: + if isinstance(widget, identifier): + widId = widgetIdentifier.identifiers[identifier](widget) + ident = widId.getIdentifier() + + if ident is not None: + break + + # At this point, we either have a valid widget identifier, or None. + return ident + + + def addWidget(self,widget): + """ + Add a widget to be tracked internally. Widgets are tracked by their + id(). Additionally, add it to the names{} dictionary, so that we + can quickly look up widgets by the name. + """ + + # Don't do anything if we already have this widget by ID + if self.trackedWidgets.has_key(id(widget)): + return + + # Make sure we are working on a widget + if not isinstance(widget, gtk.Widget): + return + + # Containers might have children. Check them all. + if isinstance(widget, gtk.Container): # gtk.Container can have + for child in widget.get_children(): # any number of children + self.addWidget(child) + + if isinstance(widget, gtk.Bin): # gtk.Bin can only have + self.addWidget(widget.get_child()) # one child. + + if isinstance(widget, gtk.Notebook): # gtk.Notebook can have + numPages = widget.get_n_pages() # many children. + for count in range(0, numPages): + page = widget.get_nth_page(count) + self.addWidget(page) + return + + + # Get the widget's identifier & id. If the widget cannot be reliably + # identified, there is no use in tracking it. + identifier = self.getWidgetIdentifier(widget) + _id = id(widget) + if identifier is None: + return + + # Simply keep track of the widgets by ID + self.trackedWidgets[_id]=widget + + # Keep track of the widgets by identifier. + # Check to see if it's already being tracked. + if self.names.has_key(identifier): + raise KeyError, "Already tracking a widget by identifier %s" \ + % identifier + + # Track the little bugger + else: + self.log.debug("Tracking widget id %i by identifier %s" % (_id, identifier)) + self.names[identifier] = widget + + + def delWidget(self,widget): + """ + Remove a widget from internal tracking. Widgets can be removed by + their id() or by the gtk.Widget object. + + TODO: I don't think this function ever gets called. + """ + if not isinstance(widget, gtk.Widget): + raise "Called delWidget on non-Widget object" + return + + # Get id(widget) + identifier = self.getWidgetIdentifier(widget) + _id = id(widget) + + # Remove the widget from the trackedWidgets dict if it is tracked. + if self.trackedWidgets.has_key(_id): + del self.trackedWidgets[_id] + del self.names[identifier] + + else: # Tried to call delWidget on non-tracked Widget + return + + def getWidgetByName(self,widgetName): + """ + Checks to see if we are tracking a Widget with a given name. + If so, return the Widget. If not, return None. + """ + if widgetName in self.names: + return self.names[widgetName] + return None + + def handleEvent_firehose(self,event): + """ + For exploratory testing. Don't bother trying to figure out what this + does, it will change without notification. + Pretty much just outputs a firehose of events. Useful for exploratory + testing, and that's pretty much it. + """ + if not hasattr(self,'classes'): + self.classes = [] + + eventType = event.type + print "----------------------------------------" + print event.type + if event.window: + # print event.window + try: + widget = event.window.get_user_data() + print widget.__class__ + self.classes[widget.__class__] = 1 + except: + raise + print "----------------------------------------" + print "" + print "" + + def handleEvent(self,event): + """ + Handles all GDK events. We first filter them so that we know + they pertain to a window, and then we further drill down based on the + type of action. This method is used to achieve two goals: + [1] Build a database of all windows and widgets + [2] Allow us to see actions as they happen. This could lead to + recording functionality in the future. + """ + try: + # Does the event have a Window that it belongs to? + if (not event.window): + return + + # Get some information on the widget. If it doesn't work, just + # gracefully fail. Exceptions happen with the following events: + # (maybe more, but these are what I've observed): + # - GDK_OWNER_CHANGE + widget = event.window.get_user_data() + eventType = event.type + + # -------- HANDLE WIDGET INSTANTIATION -------- + # MAP events are generated when a widget is initially displayed + # on the screen. In most cases, any naming or configuration that + # is going to be performed *has been* performed. + if eventType == gdk.MAP: + self.addWidget(widget) + + # -------- HANDLE WIDGET DESTRUCTION -------- + # UNMAP events are generated when a widget + # is being taken off the screen. Generally, this will happen at + # the end of an application's execution. However, to maintain + # flexibility (e.g. the possibility of dialog windows), handle + # the UNMAP event here. + elif eventType == gdk.UNMAP: + self.delWidget(widget) + except ValueError: + pass + except: # Oops! + raise + + def idleHandler(self, event=None): + + if self.engine.isComplete(): + return False + + if self.idletime is 0: + self.idletime = time.time() + self.lasttime = self.idletime + + if self.lasttime + 1 < time.time(): + self.lasttime += 1 + + if self.idletime + self.idletimeout < time.time(): + self.engine.executePy() + self.idletime = 0 + + return True + + def registerEventHandler(self): + """ + Registers the method self.eventHandler as the function that + will receive all GDK events. This allows us to snoop on GDK. + """ + if not event_handler_set: + raise NotImplementedError + else: + gobject.idle_add(self.idleHandler) + event_handler_set(self.eventHandler) diff --git a/Experior.Activity/sbgui.pyc b/Experior.Activity/sbgui.pyc new file mode 100644 index 0000000..85c5d38 --- /dev/null +++ b/Experior.Activity/sbgui.pyc Binary files differ diff --git a/Experior.Activity/sblog.py b/Experior.Activity/sblog.py new file mode 100755 index 0000000..928c96c --- /dev/null +++ b/Experior.Activity/sblog.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +sblog.py + +This file is part of sugarbot. + +sugarbot is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +sugarbot is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with sugarbot. If not, see . +""" +import logging + +log = logging.getLogger("") +log.setLevel(logging.DEBUG) diff --git a/Experior.Activity/sblog.pyc b/Experior.Activity/sblog.pyc new file mode 100644 index 0000000..621892c --- /dev/null +++ b/Experior.Activity/sblog.pyc Binary files differ diff --git a/Experior.Activity/sbpython.py b/Experior.Activity/sbpython.py new file mode 100755 index 0000000..b80554f --- /dev/null +++ b/Experior.Activity/sbpython.py @@ -0,0 +1,561 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +sbpython.py + +This file is part of sugarbot. + +sugarbot is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +sugarbot is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with sugarbot. If not, see . +""" + +import sys +import time +import gtk +import logging +from gtk import gdk +import gobject +from sbdecorators import * + +import sugar +from sugar import graphics +from sugar.graphics.toolcombobox import ToolComboBox + +class NotSupportedError(NotImplementedError): + def __init__(self, widget=None, command=None): + self.widget = widget + self.command = command + def __str__(self): + return repr('The widget %s [%s] does not support the command(s) %s' \ + % (self.widget, self.widget.__class__, self.command)) + +class WidgetDoesNotExist(ValueError): + def __init__(self, widget=None): + self.widget = widget + def __str__(self): + return repr('The widget %s could not be identified.' % self.widget) + + +class wrappedWidget(object): + raiseExceptions = True + + def __init__(self, widget, name): + self.widget = widget + self.name = name + self.log = logging.getLogger('w(%s)' % name) + + def __getitem__(self, index): + """ + Returns the indexed item. + + For ComboBox and similar items, this will effectively be the n'th object. + Uses a helper function for each class type. + """ + indexMethods = \ + { + gtk.ComboBox: self.getList_GtkComboBox, + ToolComboBox: self.getList_SugarGraphicsCombo, + # gtk.Container: self.getList_Container + } + + for classType in indexMethods: + if isinstance(self.widget, classType): + return indexMethods[classType]()[index] + + def notSupportedError(self, args): + """ + Wrapper for raising NotSupportedError exceptions. + """ + if self.raiseExceptions: + raise NotSupportedError, (self.widget, args) + return None + + def supportsSignal(self, signalName): + """ + Checks to see if a GTK object supports a signal. + + Checks to see if a GTK object supports a signal, via the + gobject.signal_lookup method. Returns True if it is supported, + false otherwise. + """ + return gobject.signal_lookup(signalName, self.widget.__class__) != 0 + + def click(self): + """ + Simulates a user click. + + Simulates a user click by: + 1) Emitting a 'clicked' signal + 2) Calling the 'activate' method + """ + self.log.info("Clicking %s" % self.name) + + widgetClass = self.widget.__class__ + + clickedSignal = 'clicked' + activateMethod = 'activate' + + # Attempt to emit the 'clicked' signal. + if self.supportsSignal(clickedSignal): + self.widget.emit(clickedSignal) + return True + + # Attempt to simply 'activate' the widget. + elif hasattr(self.widget, activateMethod): + getattr(self.widget, activateMethod)() + return True + + # Fail gracefully + return self.notSupportedError((clickedSignal, activateMethod)) + + + def getText(self): + """ + Returns the text in the widget. + + Returns whatever text is being stored by the widget. This function + gives priority to Widget functions in the following order: + 1) get_text method + 2) label (same as getLabel) + 3) title (same as getTitle) + """ + self.log.info("Getting text for %s" % self.name) + + getText = "get_text" + + # Get the text from the widget + # try: return self.__simpleGetter(getText) + # except NotSupportedError: pass + if hasattr(self.widget, getText): + return getattr(self.widget, getText)() + + # Special handling for sugar ToolComboBox + if isinstance(self.widget, ToolComboBox): + logging.fatal("YES!!!") + return self.widget.combo.get_active_item()[0] + + # Try the label + try: return self.label + except NotSupportedError: pass + + # Try the title + try: return self.title + except NotSupportedError: pass + + # Fail + return self.notSupportedError((getText,"label","title")) + + def typeText(self, val): + """ + Simulates user text entry. + + Simulates a user typing into a widget. Inserts the text at the + current insertion location, via: + 1) Emits the 'insert-at-cursor' signal. + """ + self.log.info("Adding text for %s to %s" % (self.name, val)) + + terminalMethod = "feed_child" + insertAtCursor = 'insert-at-cursor' + + if self.supportsSignal(insertAtCursor): + self.widget.emit(insertAtCursor, val) + return True + + # --- TERMINAL.ACTIVITY HACK --- + # vte.Terminal-specific typing + if hasattr(self.widget, terminalMethod): + self.widget.feed_child(val) + return True + + return self.notSupportedError(insertAtCursor, terminalMethod) + + def setText(self, val): + """ + Sets the text for the widget. + + Sets the text for the widget to the user-provided string. + Uses the following procedures to attempt to set the text: + 1) Calls 'set_text' method + 2) Set the 'label' property + 3) Set the 'title' property + 4) A Terminal-specific method, 'feed_child' + 5) Set the text via 'typeText' + """ + self.log.info("Setting text for %s to %s" % (self.name, val)) + + setText = "set_text" + + # --- GENERAL APPROACH --- + # Try simply setting the text... + try: + self.__simpleSetter(setText, val) + return True + except NotSupportedError: pass + # if hasattr(self.widget, setText): + # getattr(self.widget, setText)(val) + # return True + + # Try setting the label + try: + self.label = val + return True + except NotSupportedError: pass + + # Try setting the title + try: + self.title = val + return True + except NotSupportedError: pass + + # --- TEXT APPENDING --- + # Try to just insert the text at the given point... + try: + self.typeText(val) + return True + except NotSupportedError: + pass + + # Fail + return self.notSupportedError((setText,"label","title")) + + def delete(self, numberOfTimes, deleteType): + """ + Simulates user pressing the 'delete' key. + + Simulates user pressing the 'delete' key a given number of times, + with a flexible deletion type (e.g. characters, words, lines) + """ + # Note that we must negate numberOfTimes here, because it is + # re-negated inside of the 'backspace' call. + return backspace(-numberOfTimes, deleteType) + + def backspace(self, numberOfTimes=1, deleteType=gtk.DELETE_CHARS): + """ + Simulates the backspace keypress. + + Simulates the backspace keypress. numberOfTimes times. + If numberOfTimes is negative, simulate the 'delete' keypress. + """ + backspace = 'backspace' + deleteFromCursor = 'delete-from-cursor' + + # Try the 'deleteFromCursor' approach, as it is very flexible in the + # number of ways to delete content. + if self.supportsSignal(deleteFromCursor): + self.widget.emit(deleteFromCursor, deleteType, -numberOfTimes) + + # Try the standard 'backspace' command. Note that this will not work + # for negative quantities. + elif self.supportsSignal(backspace) and numberOfTimes >= 0: + for i in range(0, numberOfTimes): + self.widget.emit(backspace) + return True + + else: + return self.notSupportedError((backspace, deleteFromCursor)) + + def doFocus(self, setFocus=None): + """ + Either sets or gets the focus for the widget. + """ + if setFocus is None: + return self.widget.flags() & gtk.HAS_FOCUS + elif setFocus: + self.widget.grab_focus() + return True + + def getInfo(self): + """ + Returns a bunch of information about the widget. + """ + def getClasses(object): + """ + Returns a list containing the class that the object is an instance of, + and all of the classes that the instance inherits from. + For example: + >>> class A: pass + >>> class B(A): pass + >>> class C(B): pass + >>> x = C() + >>> list = getClasses(x) + >>> list + [, , + ] + >>> for i in list: + ... print "Is instance of " + str(i) + "? " + str(isinstance(x,i)) + ... + Is instance of __main__.C? True + Is instance of __main__.B? True + Is instance of __main__.A? True + """ + # If the passed object is an instance of a class, it will have a + # __class__ attribute. + if hasattr(object,"__class__"): + recursionData = [object.__class__] + + # Include the lowest-level class in the heirarchy tree + # Iterate through all of the base classes + for _class in object.__class__.__bases__: + if not _class is object: + recursionData.append(_class) + recursionData.extend(getClasses(_class)) + # Otherwise, the object -is- a class, and it will have a __bases__ + # attribute. + elif hasattr(object,"__bases__"): + # Iterate through all of the base classes. Note that we do NOT add + # the class itself here, as that is only done above, on the first + # call. + for _class in object.__bases__: + recursionData.append(_class) + recursionData.extend(getClasses(_class)) + return recursionData + + # ------ BEGIN INFO() METHOD ------- + infoStr = "\n%s" % getClasses(self.widget) + + l = dir(self.widget) + for i in l: + infoStr+= "\n> %s" % i + + return infoStr + + def __simpleGetter(self, method): + """ + Wrapper for simple 'get' methods + + Wrapper for simple 'get' methods (like get_label), that fails gracefully + if the method is not available for the widget object. + """ + self.log.info("Getting %s for %s" % (method, self.name)) + if hasattr(self.widget, method): + return getattr(self.widget, method) + + return self.notSupportedError(method) + + def __simpleSetter(self, method, val): + """ + Wrapper for simple 'set' methods + + Wrapper for simple 'set' methods (like get_label), that fails + gracefully if the method is not available for the widget object. + """ + self.log.info("Setting %s to %s for %s" % (method, val, self.name)) + if hasattr(self.widget, method): + getattr(self.widget, method)(val) + return True + + return self.notSupportedError(method) + + def getTitle(self): + """ + Returns the widget's title. + """ + return self.__simpleGetter('get_title') + + def setTitle(self, val): + """ + Sets the widget's title + """ + return self.__simpleSetter('set_title', val) + + def getLabel(self): + """ + Returns the widget's label. + """ + return self.__simpleGetter('get_label') + + def setLabel(self, val): + """ + Sets the widget's label + """ + return self.__simpleSetter('set_label', val) + + def getListFormat(self, item): + """ + Converts a ComboBox or ToolComboBox's entries into a list + + Converts a ComboBox or ToolComboBox's entries into a list. This is + useful in enumerating the entries in a ComboBox for selection. + """ + retVal = [] + + if isinstance(item, ToolComboBox): + retVal = self.getList_GtkComboBox(item) + elif isinstance(item, gtk.ComboBox): + retVal = self.getList_SugarGraphicsCombo() + + return retVal + + def getList_GtkComboBox(self, combo): + """ + getListFormat handler for gtk.ComboBo objects. + """ + index = combo.get_active() + if index == -1: + index = 0 + + model = combo.get_model() + row = model.iter_nth_child(None, index) + + if not row: + return None + + listOfValues = [] + + for i in range(0, len(model)): + try: + item = model[i] + listOfValues.append(item) + except IndexError: + break + return listOfValues + + def getList_SugarGraphicsCombo(self): + """ + getListFormat handler for sugar.graphics.ComboBox objects. + """ + tempList = self.getList_GtkComboBox(self.widget.combo) + + returnList = [] + for entry in tempList: + returnList.append(entry[1]) + + return returnList + + def getSelected_SugarGraphicsCombo(self): + """ + getSelected handler for sugar.graphics.combo* objects. + """ + active = self.getSelected_ComboBox(self.widget.combo) + + if len(active) > 1: + return active[1] + elif len(active) == 1: + return active[0] + return active + + def getSelected_ComboBox(self, combo=None): + """ + getSelected handler for gtk.ComboBox objects. + """ + if combo is None: + combo = self.widget + + index = combo.get_active() + if index == -1: + index = 0 + + row = combo.get_model().iter_nth_child(None, index) + if not row: + return None + return combo.get_model()[row] + + def getSelected(self): + """ + Returns the item selected in a gtk.ComboBox or sugar.graphics.ToolComboBox + """ + self.log.info("Getting selected entry") + # Handle for the ComboBox... + if isinstance(self.widget, gtk.ComboBox): + return self.getSelected_ComboBox(self.widget) + + # Special handling for sugar ToolComboBox + if isinstance(self.widget, ToolComboBox): + return self.getSelected_SugarGraphicsCombo() + + try: return self.__simpleGetter('get_active_text') + except NotSupportedError: pass + + try: + i = self.widget.get_active() + mod = self.widget.get_model() + return mod[i] + except: + pass + + return self.notSupportedError('get_active_text', 'get_model') + + def setSelected(self, val): + """ + Sets the item selected in a gtk.ComboBox or sugar.graphics.ToolComboBox + """ + self.log.info("Setting selected entry") + # If textual: Enumerate all of the selections + # If numeric: set selected + combo = self.widget + + # Special handling for ToolComboBox + if isinstance(self.widget, ToolComboBox): + combo = self.widget.combo + + if isinstance(combo, gtk.ComboBox): + # If we were provided with a string, get the numerical offset of + # the provided entry. + if isinstance(val, str): + l = self.getListFormat(combo) + if val in l: + val = l.index(val) + + # Use the index to set the active combobox item + if isinstance(val, int): + combo.set_active(val) + return + + return self.notSupportedError('set_active') + + + text = property(getText, setText) + title = property(getTitle, setTitle) + label = property(getLabel, setLabel) + + focus = property(doFocus, doFocus) + selected = property(getSelected, setSelected) + info = property(getInfo) + + +class widgetRegistry(): + gui = None + + def __init__(self): + self.widgets = {} + self.log = logging.getLogger('wrappedWidget') + self.log.info("Instantiated widgetRegistry") + + def __getitem__(self, which): + """ + Allows us to select widgets like a dictionary. + """ + + # If we do not already have the Widget wrapped in a wrappedWidget, + # do so now. + if which not in self.widgets: + widget = self.gui.getWidgetByName(which) + + self.log.info("Fetched widget %s [%s]" % (which, which.__class__)) + + if widget is None: + raise WidgetDoesNotExist, which + + wrapped = wrappedWidget(widget, which) + self.setWidget(which, wrapped) + + # Return the wrappedWidget object. + return self.widgets[which] + + def setWidget(self, which, value): + self.widgets[which]=value + +# Create the 'sbwidgets' instance. +if not globals().has_key("sbwidgets"): + sbwidgets = widgetRegistry() \ No newline at end of file diff --git a/Experior.Activity/sbpython.pyc b/Experior.Activity/sbpython.pyc new file mode 100644 index 0000000..a62324e --- /dev/null +++ b/Experior.Activity/sbpython.pyc Binary files differ diff --git a/Experior.Activity/sbpython_script.py b/Experior.Activity/sbpython_script.py new file mode 100755 index 0000000..e69de29 --- /dev/null +++ b/Experior.Activity/sbpython_script.py diff --git a/Experior.Activity/sbrpcserver.py b/Experior.Activity/sbrpcserver.py new file mode 100755 index 0000000..e521a18 --- /dev/null +++ b/Experior.Activity/sbrpcserver.py @@ -0,0 +1,419 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +sbrpcserver.py + +This file is part of sugarbot. + +sugarbot is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +sugarbot is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with sugarbot. If not, see . +""" +import binascii +import logging +import optparse +import os +import random +import sblog +import sys +import time +import xmlrpclib + +from SimpleXMLRPCServer import SimpleXMLRPCServer +try: + from sbconfig import host, port +except ImportError: + from sbconfig_sample import host, port + +def proxyString(): + return "http://%s:%s/" % (host, str(port)) + +def rebuildMarshalledObject(obj, dictionary): + for key in dictionary: + setattr(obj, key, dictionary[key]) + +class sugarbotSession(): + def __init__(self,ID=0): + self.id = ID + # self.responses = [] + self.responses = {} + self.currentScript = -1 + self.scriptName = "" + self.failureText = {} + # self.log = logging.getLogger("%s" % ID) + + def getSuccessValue(self): + retval = 0 + + failed = [self.responses[k] for k in self.responses if not self.responses[k]] + if failed: + retval = 1 + return retval + + log = property(lambda self: logging.getLogger(str(self.id))) + +class sbRpcServer(SimpleXMLRPCServer): + sugarbotActivityVar = 'sugarActivityName' + + """ + Sugarbot RPC Server + + Responsible for listening on the XML-RPC port (defined as + sbconfig.port). The object loads the scripts provided + on the command-line as individual text lines. It then provides those + lines of text over XML-RPC to the XML-RPC client. The client is + responsible for parsing the lines into meaningful objects/functions. + + The list of files, as well as each file's contents, are kept entirely in + memory. This may lead to issues in the future with particularly large + script files, but this is currently not a problem. + """ + def __init__(self, args=[], xmlport=port, kill=False, restart=False): + # Random port? + if xmlport is None: + xmlport = random.randint(1024,65000) + self.port = xmlport + + # Create the RPC server + SimpleXMLRPCServer.__init__(self, (host, xmlport), + allow_none=True, logRequests=False) + + # Create the logger + self.log = self.initializeLogging() + self.log.info("Listening on port %i" % xmlport) + + # A listing of the filenames for each script + self.listOfScripts = [] + + # Keep list of all of the clients + self.clients = {0: sugarbotSession(0)} + + # Misc other stuffs... + self.kill = kill + self.restart = restart + self.log.info("Kill: %s\tRestart: %s" % (kill,restart)) + + # All of the arguments should be filenames for files to parse. + for arg in args: + self.addScript(arg) + + # Register all of the functions so that callers can use them + self.registerFunctions() + + # Initialize the random seed for generating client ID's + self.initRandomSeed() + + def testConnectivity(self): + """ + This function exists so that the client can test the connection to the + XML-RPC server. The xmlrpclib throws a socket.error if a function is + called when the ServerProxy object is not connected. + This provides a simple method so that we don't have to worry + about screwing with the internal state of other stuff. + """ + pass + + def initRandomSeed(self): + """ + Initializes the random seed using os.urandom. + """ + # Initialize the random seed... + seed = 0 + try: + seed = long(binascii.hexlify(os.urandom(64)), 16) + except NotImplementedError: + seed = time.time() + random.seed(seed) + + def generateSessionID(self): + """ + In the event a client does not specify an identifier, provide one. + """ + return random.randint(0, sys.maxint) + + def initializeLogging(self): + """ + Initialize logging for the sbRpcServer object. This provides logging + of INFO and above to the terminal, and all statements go to + a file named sbRpcServer.log. + """ + if __name__ == '__main__': + logging.basicConfig(level=logging.DEBUG, + format='%(asctime)s %(name)-12s %(levelname)-8s %(message)s', + datefmt='%m-%d %H:%M', + filename='sbRpcServer.log') + + # define a Handler which writes INFO messages or higher to the sys.stderr + console = logging.StreamHandler() + console.setLevel(logging.INFO) + # set a format which is simpler for console use + formatter = logging.Formatter('%(name)-12s: %(levelname)-8s %(message)s') + # tell the handler to use this format + console.setFormatter(formatter) + # add the handler to the root logger + logging.getLogger('').addHandler(console) + + return logging.getLogger('sbRpcServer') + + def registerFunctions(self): + """ + Register the functions for use with the XML server. + Recommended internal use only. + """ + self.register_function(self.addScript) + self.register_function(self.completionStatus) + self.register_function(self.fail) + self.register_function(self.generateSessionID) + self.register_function(self.getActivityName) + self.register_function(self.getKillFlag) + self.register_function(self.getRestartFlag) + self.register_function(self.getScript) + self.register_function(self.numberOfScripts) + self.register_function(self.resetClientState) + self.register_function(self.startScript) + self.register_function(self.success) + self.register_function(self.testConnectivity) + + def numberOfScripts(self): + """ + Returns the number of scripts for execution. + """ + return len(self.listOfScripts) + + def haveClient(self,ID): + """ + Determine whether or not we have a given client in tracking. + """ + return self.clients.has_key(ID) + + def getClient(self, ID): + """ + Returns the sugarbotSession object corresponding to the provided ID. + + If such a sugarbotSession object does not exist, it is created. + """ + if not self.haveClient(ID): + self.clients[ID] = sugarbotSession(ID) + + return self.clients[ID] + + def completionStatus(self,ID=0): + """ + Returns the completion status of all of the scripts as a list. + For example, if there were a total of three scripts, and the second + script had a command that did not complete successfully, the return + value would be: + [True, False, True] + + If no failures were reported: + [True, True, True] + + All values are initialized to False. If a script is still running, + then at least one of the result-values will be False. + + Removes the client from tracking. When the client re-connects, it + will effectively have a clean slate. + """ + retval = [] + if self.haveClient(ID): + retval = self.clients[ID] + self.resetClientState(ID) + else: + retval = None + + return retval + + def resetClientState(self, ID=0): + """ + Clears a client from tracking. + + When the client re-connects, it will start with a clean slate. + """ + if self.haveClient(ID): + client = self.getClient(ID) + + # Get a completed ratio + r = client.responses + successes = len([r[k] for k in r if r[k] is True]) + completed = len(r) + overall = len(self.listOfScripts) + self.clients[ID].log.info("Disconnected [%s\%s\%s]" % \ + (successes, completed, overall)) + del self.clients[ID] + + def scriptOutOfBounds(self,ID): + """ + Checks to see if a client's script index is out-of-bounds. + """ + client = self.getClient(ID) + + if client.currentScript < 0: + return True + + if client.currentScript >= len(self.listOfScripts): + return True + + def success(self, ID=0): + """ + This method is called after the successful completion of a script + """ + client = self.getClient(ID) + client.log.info("Success (%s)" % client.scriptName) + client.responses[client.scriptName] = True + + def modifyTraceBackText(self, text, ID=0): + """ + Replaces '' with the script name in a backtrace report. + + This is useful in determining _which_ script failed execution. + """ + client = self.getClient(ID) + + if "File \"\"" in text: + # Get the text.. + replacementText = "Sugarbot Script: '%s'" % client.scriptName + + # Replace the string with the filename + text = text.replace("", replacementText) + return text + + def fail(self, status="No status provided", ID=0): + """ + This method is called if, for some reason, the client needs to disconnect + prematurely. A status message/reason is required. + """ + client = self.getClient(ID) + + # If the client is sending us exception text, replace that select + # portions of the text to be more useful. + status = self.modifyTraceBackText(status, ID) + + # Print the error + client.log.error("%s" % status) + client.responses[client.scriptName] = False + client.failureText[client.scriptName] = status + + return status + + def startScript(self,ID=0): + """ + Increments the internal script counter, looping back to the first + script if necessary. This should be the first method that a client + calls. + """ + # Get the client. This will create it if it does not exist. + client = self.getClient(ID) + + # Next script! (Note that the value is initialized to be -1) + client.currentScript += 1 + + # Prevent overflows if, for some reason, we loop back to script 0 + if self.scriptOutOfBounds(ID): + client.currentScript = 0 + + # Update the client's script name + client.scriptName = self.listOfScripts[client.currentScript] + + # Pretty status message... + client.log.info("Starting %s" % client.scriptName) + + # Make room for another response (default to failure) + client.responses[client.scriptName] = False + + return True + + def getScript(self,ID=0): + """ + Returns the script that the client should execute. + """ + client = self.getClient(ID) + client.log.info("Getting Script: %s" % client.scriptName ) + + f = open(self.listOfScripts[client.currentScript]) + listOfLines = f.readlines() + longString = '' + for line in listOfLines: + longString += line + + f.close() + + return longString + + def getActivityName(self, ID=0, which=None): + """ + Imports from the specified script file in order to + """ + client = self.getClient(ID) + if which is None: + which = client.currentScript + + if which == -1: + raise IndexError, "Invalid script index. If calling " + \ + "getActivityNameMake without arguments, make sure that " + \ + "startScript gets called first." + + try: + moduleName = self.listOfScripts[which] + index = moduleName.find(".py") + if index is not -1: + moduleName = moduleName[:index] + + execStr = 'from %s import %s' % \ + (moduleName, self.sugarbotActivityVar) + + evalStr = '%s' % self.sugarbotActivityVar + + exec execStr + return eval(evalStr) + except: + raise + + def getKillFlag(self): + return self.kill + + def getRestartFlag(self): + return self.restart + + def addScript(self,scriptPath): + """ + Adds a script to the internal list of scripts. This causes the parser + to load the script contents into memory for fast/easy access. + """ + self.listOfScripts.append(scriptPath) + + activityName = self.getActivityName(which=len(self.listOfScripts)-1 ) + self.log.info("Added script %s [Activity %s]" \ + % (scriptPath, activityName) ) + + +def main(argv=None): + p = optparse.OptionParser() + p.add_option('--no-restart', dest='restart', action='store_false',default=True, + help='do NOT restart sugarbot after execution has completed') + p.add_option('--no-kill', dest='kill', action='store_false',default=True, + help='do NOT kill sugar after all sugarbot scripts have executed') + + (options,args) = p.parse_args() + + if len(args) < 1: + p.print_help() + return 1 + + server = sbRpcServer(args, kill=options.kill, restart=options.restart) + + try: + server.serve_forever() + except KeyboardInterrupt: + print 'Ctrl+C pressed, exiting...' + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/Experior.Activity/sbrpcserver.pyc b/Experior.Activity/sbrpcserver.pyc new file mode 100644 index 0000000..4704065 --- /dev/null +++ b/Experior.Activity/sbrpcserver.pyc Binary files differ diff --git a/Experior.Activity/script_calculate.py b/Experior.Activity/script_calculate.py new file mode 100755 index 0000000..93f0fa4 --- /dev/null +++ b/Experior.Activity/script_calculate.py @@ -0,0 +1,35 @@ +import time +import logging + +sugarActivityName = 'Calculate' + +def sugarbot_main(widgets): + # Test 'selected' functionality. + assert widgets['Share with:'].selected == "Private" + widgets['Share with:'].selected = "My Neighborhood" + assert widgets['Share with:'].selected == "My Neighborhood" + + # Test widget fetching/assignment + one = widgets['1'] + plus = widgets['+'] + enter = widgets['enter'] + + for i in range(0,5): + # Test click + one.click() + plus.click() + one.click() + + # Test Entry text assignment + assert widgets['TextEntry'].text == '1+1' + + enter.click() + + assert len(widgets['TextEntry'].text) == 0 + + time.sleep(1) + + # More Entry text assignment + widgets['TextEntry'].text = "1+5" + assert widgets['TextEntry'].text == '1+5' + enter.click() diff --git a/Experior.Activity/script_calculate.pyc b/Experior.Activity/script_calculate.pyc new file mode 100644 index 0000000..e079466 --- /dev/null +++ b/Experior.Activity/script_calculate.pyc Binary files differ diff --git a/Experior.Activity/script_terminal.py b/Experior.Activity/script_terminal.py new file mode 100755 index 0000000..f45e6eb --- /dev/null +++ b/Experior.Activity/script_terminal.py @@ -0,0 +1,11 @@ +import time +import logging + +sugarActivityName = 'Terminal' + +def sugarbot_main(widgets): + #assert 1 + term = widgets['VteTerminal'] + term.typeText("whoami\r") + term.typeText("ls -la\r") + term.typeText("exit\r") \ No newline at end of file diff --git a/Experior.Activity/script_terminal.pyc b/Experior.Activity/script_terminal.pyc new file mode 100644 index 0000000..9c63aa1 --- /dev/null +++ b/Experior.Activity/script_terminal.pyc Binary files differ diff --git a/Experior.Activity/setup.py b/Experior.Activity/setup.py new file mode 100755 index 0000000..a74cffd --- /dev/null +++ b/Experior.Activity/setup.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +setup.py + +This file is part of sugarbot. + +sugarbot is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +sugarbot is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with sugarbot. If not, see . +""" +import sys + +from sugar.activity import bundlebuilder +bundlebuilder.start() + + +#try: + #if len(sys.argv) < 2 or sys.argv[1] == "install": + #from sugar.activity import bundlebuilder + #bundlebuilder.start("sugarbot") + #elif sys.argv[1] == "test": + ## nosetests + #pass +#except ImportError: + #import os + #os.system("find ./ | sed 's,^./,sugarbot.activity/,g' > MANIFEST") + #os.system('rm sugarbot.xo') + #os.chdir('..') + #os.system('zip -r sugarbot.xo sugarbot.activity') + #os.system('mv sugarbot.xo ./sugarbot.activity') + #os.chdir('sugarbot.activity') diff --git a/Experior.Activity/sugarbot.py b/Experior.Activity/sugarbot.py new file mode 100755 index 0000000..717ffed --- /dev/null +++ b/Experior.Activity/sugarbot.py @@ -0,0 +1,207 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +sugarbot.py + +This file is part of sugarbot. + +sugarbot is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +sugarbot is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with sugarbot. If not, see . +""" +import gobject +import gtk +import logging +import os +import sblog +import socket +import sys + +from xmlrpclib import ServerProxy + +from sugar.activity import activity +#from sugar.activity import registry +from jarabe.model.bundleregistry import get_registry + +from sbrpcserver import sbRpcServer, proxyString + +# For reading GDK.Event's +from sbgui import sbGUI + +class sugarbot(activity.Activity): + def __dynamicImport(self, fullname, path=None): + """ + Handles emulation of the target Activity. + + Dynamically modifies the module-search path, and imports an object from + a module. Some fancy work is done to get that to work properly. + @param fullname: The module to import, example calculate.Calculate. + @param path: If necessary, the path to the imported module. + """ + module = None + module_name = "" + class_name = "" + + if fullname is None: + return self.__parentClass + + try: + # ========================================================= + # This could probably be cleaned up or refactored into separate + # functions. However, the code is short enough that it is not + # likely worth it. + if path != None: + sys.path.append(path) + + splitted_module = fullname.rsplit('.', 1) + + if len(splitted_module) >= 2: + module_name = splitted_module[0] + class_name = splitted_module[1] + + if module_name is not None and class_name is not None and \ + len(module_name) > 0 and len(class_name) > 0: + module = __import__(module_name) + + for comp in module_name.split('.')[1:]: + module = getattr(module, comp) + # ======================================================== + except ImportError: + raise ImportError, \ + "Error in sugarbot._dynamicImport(%s,%s)" % (fullname,path) + + finally: + if hasattr(module, class_name): + self.__parentClass = getattr(module, class_name) + else: # Failsafe + self.__parentClass = activity.Activity + + return self.__parentClass + + def __cloneActivity(self,sugarHandle): + """ + Starts the cloned activity, where self.__parentClass is the activity + class. This is performed by [1] inheriting from the parentClass, + and [2] calling parentClass.__init__. + """ + if self.__parentClass is None: + raise AttributeError, "self.__parentClass not defined properly." + + else: + sugarbot.__bases__ = (self.__parentClass,) + self.__parentClass.__init__(self,sugarHandle) + + def __getActivityList(self): + """ + Gets a list of activities from the registry. Stores this list in + self.__activityList. + """ + # Prevent looking up all of the activities multiple times. + if not hasattr(self,"__activityList"): + #self.__activityList = registry.get_registry().get_activities() + self.__activityList = get_registry()._bundles + return self.__activityList + + # If we didn't get any activities, something went wrong. + if len(self.__activityList) < 1: + raise "Activity list is empty. Cannot get activity info!" + + return None + + def __selectActivity(self,name): + """ + Selects an activity from the activityList by name. This allows + simpler access to the list of activities. + (For example, one can specify only 'Calculate' instead of + 'org.laptop.Calculate' or 'calculate.Calculate'). + @param name: The name of the activity (e.g. 'Calculate') + """ + # Get the list of activities if it does not already exist + activityList = self.__getActivityList() + + # Initialization of some variables... + self.__path = None + self.__importClass = None + self.__className = None + + # Iterate through the activity list + for activity in activityList: + if activity.get_name() == name: + self.__path = activity.get_path() + self.__importClass = activity.get_command().split()[-1] + self.__className = self.__importClass.split(".")[-1] + self.log.debug("Importing class %s" % name) + return + + # If we ever get here, that means we didn't find anything... + if (self.__path is None) and (self.__importClass is None) and \ + (self.__className is None): + raise NameError, "Could not find '%s' in activity list." % name + + def __initializeScript(self): + """ + Initialize the script on the XML-RPC server. + Returns the name of the Activity that should be instantiated + """ + rpc = self.__xmlRPC + + try: + self.sessID = os.environ['SUGARBOT_ID'] + except KeyError: + self.sessID = 0 + + # Start the script + if not rpc.startScript(self.sessID): + rpc.fail("Could not start the script!", self.sessID) + exit() + + # Get our activity name + activityName = rpc.getActivityName(self.sessID) + if activityName is None: + rpc.fail('Bad activity name provided', self.sessID) + exit() + + self.log.info("Activity is %s" % activityName) + return activityName + + + def __init__(self, handle): + """ + Performs setup, and runs the specified activity. + """ + self.log = logging.getLogger('sugarbot') + + # Set up threading... + gtk.gdk.threads_init() + + # Handle is set to 'None' for testing purposes via Nose. Obviously, + # if Sugar sets the handle to None, there are other problems... + if handle is None: + return + + try: + # Create the RPC connection object + self.__xmlRPC = ServerProxy(proxyString()) + + # Set up the sbGUI object for automation + self.__sbgui = sbGUI(self, self.__xmlRPC) + + # Get our activity name + activityName = self.__initializeScript() + + # Actually clone the activity + self.__selectActivity(activityName) + self.__dynamicImport(self.__importClass,self.__path) + self.__cloneActivity(handle) + except socket.error: + sys.log.error("====== COULD NOT CONNECT TO XML-RPC SERVER ======") + sys.exit(-1) diff --git a/Experior.Activity/sugarbot.pyc b/Experior.Activity/sugarbot.pyc new file mode 100644 index 0000000..86dddaa --- /dev/null +++ b/Experior.Activity/sugarbot.pyc Binary files differ diff --git a/Experior.Activity/sugarbotlauncher.py b/Experior.Activity/sugarbotlauncher.py new file mode 100755 index 0000000..29d8d7c --- /dev/null +++ b/Experior.Activity/sugarbotlauncher.py @@ -0,0 +1,298 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +sugarbot-launcher.py + +This file is part of sugarbot. + +sugarbot is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +sugarbot is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with sugarbot. If not, see . +""" +import logging +import os +import signal +import sys +import socket + +from subprocess import Popen +from time import time,sleep +from xmlrpclib import ServerProxy + +# Set the Sugarbot path env var if it has not already been set +if not os.environ.has_key('SUGARBOT_PATH'): + os.environ['SUGARBOT_PATH']=os.getcwd() + +# Add the sugarbot path to the include-path +sys.path.append(os.environ['SUGARBOT_PATH']) + +# Import sugar-specific stuff +from sbrpcserver import proxyString, rebuildMarshalledObject, sugarbotSession +try: + from sbconfig import clientName +except ImportError: + from sbconfig_sample import clientName + +class SugarProcessHandler(): + """ + Handles launching and monitoring of the Sugar process. + """ + def __init__(self): + # Set the environment variable to emulate, and the executed scripts. + os.environ['SUGARBOT_EMULATOR']='1' + + # Get the connection to the XML-RPC server + self.xml = self.connectToXMLRPC() + logging.info("Connected to XML-RPC server %s" % proxyString()) + + # Get our ID and reset the state + self.ID = self.getSugarbotClientID() + self.xml.resetClientState(self.ID) + logging.info("Using session ID %s" % self.ID) + + def getSugarbotClientID(self): + """ + Retrieves whatever ID we should be using. This is either set by the + environment variables, the configuration file, or generated by the + server. + """ + # If we have not specified a sugarbot ID, specify one now + if not os.environ.has_key('SUGARBOT_ID'): + if not clientName: + os.environ['SUGARBOT_ID']=str(xml.generateSessionID()) + else: + os.environ['SUGARBOT_ID']=clientName + + return os.environ['SUGARBOT_ID'] + + def connectToXMLRPC(self): + """ + Connects to the ML-RPC server, tests connectivity. + """ + xml = ServerProxy(proxyString()) + try: + xml.testConnectivity() + except: + logging.fatal('Could not connect to the XML-RPC server') + sys.exit(1) + + return xml + + def launchSugar(self): + """ + Launches the sugar-emulator process and waits for it to finish. + """ + # Launch the process + self.pid = Popen('sugar-emulator') + self.waitForSugarbotExecution() + + def waitForSugarbotExecution(self): + """ + Waits a certain amount of time for Sugar to quit. + + If Sugar does not die in the alloted time, it is killed. + """ + # Wait five minutes for the tests to execute? Why not. + start = time() + wait = 60*5 + done = start + wait + + # We've waited long enough. Kill it! + while self.pid.poll() is None and done > time(): + sleep(0.1) + if self.pid.poll() is None: + logging.fatal("Had to send SIGTERM to Sugar process") + os.kill(self.pid.pid, signal.SIGTERM) + + def getReturnValue(self): + """ + Determines whether all of the tests succeeded, or there was a failure. + + 0 = All tests succeeded + 1 = At least one failure + """ + # Get the completion status... + sessionDict = self.xml.completionStatus(self.ID) + if sessionDict is None: + logging.error("Could not get completion status from XMLRPC server") + return -1 + + session = sugarbotSession() + rebuildMarshalledObject(session, sessionDict) + + # Get whatever the return value should be. + retval = session.getSuccessValue() + + # Print out the completion statuses all pretty-like + status = "" + logging.info("Script completion statuses:") + for script in session.responses: + logging.info("\t%s: %s" % (session.responses[script], script)) + + # if session.failureText.has_key(script) and \ + # not session.responses[script]: + if not session.responses[script]: + logging.info("================ [%s] ================", script) + for line in session.failureText[script].split('\n'): + if len(line) > 0: + logging.info(line) + logging.info("================ [%s] ================", script) + + + # Check the number of scripts to make sure they all executed + numScripts = self.xml.numberOfScripts() + if( numScripts > len(session.responses) ): + retval = False + logging.warning("Only executed %i/%i scripts" % \ + (numScripts,len(session.responses)) ) + + return retval + +def main(): + s = SugarProcessHandler() + s.launchSugar() + + retval = s.getReturnValue() + logging.info("Returning %s" % retval) + + return retval + +if __name__ == "__main__": + main() + +else: + import pygtk + pygtk.require('2.0') + import gtk + import gobject + import time + + from view.Shell import Shell + from model.shellmodel import ShellModel + from shellservice import ShellService + + from view.frame.activitiestray import ActivitiesTray + from view.frame.activitybutton import ActivityButton + + class SugarbotLauncher: + """ + Autmates the launching and re-launching of Sugarbot from the main Sugar + GUI. This automation is handled by simulating a click on the Sugarbot + activity icon in the Sugar Pane. + """ + activityName = "sugarbot" + + def __init__(self, shell, shellModel): + gtk.gdk.event_handler_set(self.eventHandler) + self.model = shellModel + self.numberOfScripts = 0 + self.shell = shell + self.sugarbotIsRunning = False + self.timesLaunched = 0 + self.xml = ServerProxy(proxyString()) + + home = self.model.get_home() + home.connect('activity-started', self._activity_started_cb) + home.connect('activity-removed', self._activity_removed_cb) + home.connect('active-activity-changed', self._activity_active_cb) + home.connect('pending-activity-changed', self._activity_pending_cb) + + def isSugarbotActivity(self,activity): + """ + Checks to see if an 'Activity' object is the sugarbot activity. + """ + if activity._activity_info.name == self.activityName: + if self.numberOfScripts <= 0: + self.doSetup(activity) + return True + return False + + def doSetup(self, activity): + """ + Prepares for launching the activity, given its information. + """ + path = activity._activity_info.path + sys.path.append(path) + + try: + self.numberOfScripts = self.xml.numberOfScripts() + except: + logging.fatal("Could not connect to XMLRPC Server") + sys.exit(-1) + + def _activity_started_cb(self, model, activity): + if self.isSugarbotActivity(activity): + self.sugarbotIsRunning = True + self.timesLaunched += 1 + + def _activity_removed_cb(self, model, activity): + if self.isSugarbotActivity(activity): + self.sugarbotIsRunning = False + + if self.xml.getKillFlag() and \ + 1 <= self.numberOfScripts <= self.timesLaunched: + logging.info("Sugarbot execution completed successfully") + self.killSugar() + + def _activity_active_cb(self, model, activity): + pass + + def _activity_pending_cb(self, model, activity): + pass + + def killSugar(self): + """ + Sends SIGTERM to the Sugar process. + """ + try: + if os.environ.has_key('SUGAR_EMULATOR_PID'): + pid = int(os.environ['SUGAR_EMULATOR_PID']) + os.kill(pid, signal.SIGTERM) + except: + raise + + def tryToLaunchSugarbotActivity(self): + """ + Attempts to launch sugarbot. + """ + if self.sugarbotIsRunning or not self.xml.getRestartFlag(): + # time.sleep(0.1) + return + + frame = self.shell.get_frame() + frameBottomPanel = frame._bottom_panel + bottomPanelChildren = frameBottomPanel._bg.get_children() + tray = [k for k in bottomPanelChildren if isinstance(k,ActivitiesTray)] + + if tray: + trayIcons = tray[0]._tray.get_children() + else: + return + + activities = [k for k in trayIcons if isinstance(k, ActivityButton)] + sbList = [k for k in activities if \ + k._activity_info.name == self.activityName] + + if len(sbList) > 0: + sbList[0].emit('clicked') + self.sugarbotIsRunning = True + + def eventHandler(self, event): + """ + Intercepts GTK calls, to attempt to launch sugarbot. + + Attempts to launch sugarbot at every gdk.Event emitted. + """ + if event is not None: + gtk.main_do_event(event) + self.tryToLaunchSugarbotActivity() + diff --git a/Experior.Activity/svn-commit.tmp b/Experior.Activity/svn-commit.tmp new file mode 100644 index 0000000..b34bcea --- /dev/null +++ b/Experior.Activity/svn-commit.tmp @@ -0,0 +1,6 @@ +Hacky changes + +Committing changes to 0.1 release made to ge things to work. +--This line, and those below, will be ignored-- + +M sugarbot.py diff --git a/Experior.Activity/test_rpcserver.py b/Experior.Activity/test_rpcserver.py new file mode 100755 index 0000000..919e82c --- /dev/null +++ b/Experior.Activity/test_rpcserver.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +test_rpcserver.py + +This file is part of sugarbot. + +sugarbot is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +sugarbot is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with sugarbot. If not, see . +""" +import os +try: + import sbconfig +except ImportError: + import sbconfig_sample + +from random import randint +from sbrpcserver import * +from xmlrpclib import ServerProxy + +class test_rpcServer: + + files = ["fileOne.py", "fileTwo.py"] + activityNames = ["activityOne", "activityTwo"] + basicScript = \ +""" +def sugarbot_main(var): + assert var + +""" + scripts = [basicScript + "sugarActivityName = '%s'" % activityNames[0], + basicScript + "sugarActivityName = '%s'" % activityNames[1]] + + def __init__(self): + self.is_setup = False + + def setUp(self): + assert not self.is_setup + + # Generate a random ID + self.clientID = randint(1,1000) + + # Create the server object. + self.server = sbRpcServer([], None) + + # Create the script file and add it... + self.createScriptFiles() + for file in self.files: + self.server.addScript(file) + + self.is_setup = True + + + def tearDown(self): + assert self.is_setup + + self.server.server_close() + self.deleteScriptFiles() + + self.is_setup = False + + def createScriptFiles(self): + for i in range(0, len(self.scripts)): + f = open(self.files[i],"w+") + f.write(self.scripts[i]) + f.close() + + def deleteScriptFiles(self): + for f in self.files: + os.remove(f) + + def test_StartScript_Reset(self): + self.server.startScript(self.clientID) + + client = self.server.clients[self.clientID] + client.currentScript = 999999 + + self.server.startScript(self.clientID) + + assert client.currentScript == 0 + assert client.responses[client.scriptName] == False + + def test_StartScript_Bounds(self): + # Check to see if we go beyond the bounds + for count in range(0,1+len(self.server.listOfScripts)): + self.server.startScript() + assert self.server.clients[0].currentScript == 0 + + def test_RegisteredFunctions(self): + expectedMethods = ['addScript', + 'completionStatus', + 'fail', + 'generateSessionID', + 'getActivityName', + 'getRestartFlag', + 'getScript', + 'numberOfScripts', + 'startScript', + 'success'] + listedMethods = self.server.system_listMethods() + + for method in expectedMethods: + if not method in listedMethods: + print method + assert 0 + + assert not 'NOTREGISTERED' in listedMethods + + def test_getActivityName_IndexError(self): + try: + self.server.startScript(self.clientID) + self.server.getActivityName(self.clientID, 9999) + assert 0 + except IndexError: + pass + + def test_getActivityName_WithoutStartingScript(self): + try: + self.server.getActivityName(self.clientID) + assert 0 + except IndexError: + pass + + def test_getActivityName(self): + for activity in self.activityNames: + self.server.startScript() + assert self.server.getActivityName() == activity + + def test_multipleCallsToGetScript(self): + for script in self.scripts: + self.server.startScript() + assert self.server.getScript() == script + \ No newline at end of file diff --git a/Experior.Activity/test_rpcserver.pyc b/Experior.Activity/test_rpcserver.pyc new file mode 100644 index 0000000..650aed2 --- /dev/null +++ b/Experior.Activity/test_rpcserver.pyc Binary files differ diff --git a/Experior.Activity/test_sbexecutionengine.py b/Experior.Activity/test_sbexecutionengine.py new file mode 100755 index 0000000..29582c0 --- /dev/null +++ b/Experior.Activity/test_sbexecutionengine.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +test_sbexecutionengine.py + +This file is part of sugarbot. + +sugarbot is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +sugarbot is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with sugarbot. If not, see . +""" +from sbexecutionengine import * + +logging.raiseExceptions = 0 + +class failException(Exception): pass +class successException(Exception): pass + +class test_sbExecutionEngine: + + class fakeXmlRpc: + def __init__(self): + self.assertionValue = 1 + self.restart = False + self.kill = False + self.status = False + + def success(self, clientID=None): + # raise successException + # We can't raise exceptions on success, because of the way + # sbExecutionEngine works. Any exceptions call fail() + self.status = True + + def fail(self, reason=None, clientID=None): + raise failException + + def getRestartFlag(self): + return self.restart + + def getKillFlag(self): + return self.kill + + def getScript(self, dontCare): + return \ +""" +def sugarbot_main(param): + assert param +""" + + def __init__(self): + self.is_setup = False + + def setUp(self): + assert not self.is_setup + + self.xml = self.fakeXmlRpc() + self.ee = sbExecutionEngine(None,self.xml) + + self.is_setup = True + + def tearDown(self): + assert self.is_setup + + self.is_setup = False + + def test_setComplete(self): + assert not self.ee.isComplete() + self.ee.executionComplete = True + assert self.ee.isComplete() + + def test_killSugarbot(self): + try: + self.ee.killSugarbot() + assert 0 + except SystemExit: + pass + + def test_executePy_Fail(self): + try: + self.ee.widgets.__nonzero__ = lambda: False + self.ee.executePy() + assert 0 + except failException: + pass + + def test_executePy_Success(self): + # try: + self.ee.widgets.__nonzero__ = lambda: True + self.ee.executePy() + + assert self.xml.status == True + + def test_executePy_UnexpectedException(self): + try: + def raiseException(): raise ValueError + + self.ee.widgets.__nonzero__ = raiseException + self.ee.executePy() + assert 0 + except: + pass \ No newline at end of file diff --git a/Experior.Activity/test_sbexecutionengine.pyc b/Experior.Activity/test_sbexecutionengine.pyc new file mode 100644 index 0000000..363c69c --- /dev/null +++ b/Experior.Activity/test_sbexecutionengine.pyc Binary files differ diff --git a/Experior.Activity/test_sbgui.py b/Experior.Activity/test_sbgui.py new file mode 100755 index 0000000..f526d5f --- /dev/null +++ b/Experior.Activity/test_sbgui.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +test_sbgui.py + +This file is part of sugarbot. + +sugarbot is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +sugarbot is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with sugarbot. If not, see . +""" +import unittest +import sbexecutionengine + +from sbgui import * + +class test_sbgui: + class fakeExecutionEngine(): + def getCommand(self): + pass + def executeNextCommand(self): + pass + def isComplete(self): + return False + + def __init__(self): + self.is_setup = False + + def setUp(self): + assert not self.is_setup + + self.gui = sbGUI(None,None) + self.gui.engine = self.fakeExecutionEngine() + self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) + + self.is_setup = True + + def tearDown(self): + assert self.is_setup + + self.is_setup = False + + def startTimer(self, timer): + self.timer = time.time() + timer + + def timeout(self): + if time.time() > self.timer: + return True + return False + + def test_CatchWidgetInstantiation(self): + wlabel = "testButton" + widget = gtk.Button(label=wlabel) + + self.window.add(widget) + widget.show() + self.window.show() + + self.startTimer(3.0) + while not self.timeout(): + gtk.main_iteration(False) + + if self.gui.getWidgetByName(wlabel) is widget: + return + assert 0 + + def test_registerEventHandler(self): + try: + self.gui.registerEventHandler() + except NotImplementedError: + assert 0 + + +if __name__ == "__main__": + unittest.main() diff --git a/Experior.Activity/test_sbgui.pyc b/Experior.Activity/test_sbgui.pyc new file mode 100644 index 0000000..1722516 --- /dev/null +++ b/Experior.Activity/test_sbgui.pyc Binary files differ diff --git a/Experior.Activity/test_sugarbot.py b/Experior.Activity/test_sugarbot.py new file mode 100755 index 0000000..33b6126 --- /dev/null +++ b/Experior.Activity/test_sugarbot.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +test_sugarbot.py + +This file is part of sugarbot. + +sugarbot is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +sugarbot is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with sugarbot. If not, see . +""" +from sugarbot import * + +class rpcFailure(Exception): + pass + +class test_sugarbot: + + class cloneTest(activity.Activity): + def __init__(self,x): + self.clonedProperly = True + + class fakeXmlRpc: + activity = "exampleActivity" + def startScript(self, *args): + return True + def fail(self, reason, *args): + raise rpcFailure + + class fakeXmlRpcGood(fakeXmlRpc): + def getActivityName(self, *args): + return self.activity + + class fakeXmlRpcBad(fakeXmlRpc): + def getActivityName(self, *args): + return None + + def __init__(self): + self.is_setup = False + + def setUp(self): + assert not self.is_setup + + self.sb = sugarbot(None) + + self.is_setup = True + + def tearDown(self): + assert self.is_setup + + sugarbot.__bases__ = (activity.Activity, ) + + self.is_setup = False + + def test_dynamicImport1(self): + # Attempt to import a valid module + assert gobject.GObject == \ + self.sb._sugarbot__dynamicImport("gobject.GObject") + + def test_dynamicImport2(self): + # Attempt to import a module with a bad length or None + assert activity.Activity == self.sb._sugarbot__dynamicImport("") + assert activity.Activity == self.sb._sugarbot__dynamicImport(None) + + def test_dynamicImport3(self): + # Attempt to import a module that does not exist + try: + assert activity.Activity == \ + self.sb._sugarbot__dynamicImport("asdf.1231231.##fw") + assert 0 + except ImportError: + pass + + def test_cloneActivity1(self): + self.sb._sugarbot__parentClass = None + try: + self.sb._sugarbot__cloneActivity(None) + assert 0 + except AttributeError: + pass + + def test_cloneActivity2(self): + self.sb._sugarbot__parentClass = self.cloneTest + self.sb._sugarbot__cloneActivity(None) + + assert self.cloneTest in sugarbot.__bases__ + assert self.sb.clonedProperly + + def test_cloneActivity3(self): + assert not self.cloneTest in sugarbot.__bases__ + + def test_initializeScript1(self): + fake = self.fakeXmlRpcGood() + self.sb._sugarbot__xmlRPC = fake + + assert fake.activity == self.sb._sugarbot__initializeScript() + + def test_initializeScript2(self): + fake = self.fakeXmlRpcBad() + self.sb._sugarbot__xmlRPC = fake + + try: + self.sb._sugarbot__initializeScript() + assert 0 + except rpcFailure: + pass + + + diff --git a/Experior.Activity/test_sugarbot.pyc b/Experior.Activity/test_sugarbot.pyc new file mode 100644 index 0000000..464463b --- /dev/null +++ b/Experior.Activity/test_sugarbot.pyc Binary files differ diff --git a/Experior.Activity/test_widgetIdentifier.py b/Experior.Activity/test_widgetIdentifier.py new file mode 100755 index 0000000..83f6b3f --- /dev/null +++ b/Experior.Activity/test_widgetIdentifier.py @@ -0,0 +1,254 @@ +from widgetIdentifier import * + +class test_widgetIdentifier: + def __init__(self): + self.is_setup = False + + def setUp(self): + assert not self.is_setup + + self.widget = gtk.Button() + self.name = "nameExample" + + self.widget.set_name(self.name) + + self.identifier = widgetIdentifier(self.widget) + + self.is_setup = True + + def tearDown(self): + assert self.is_setup + self.is_setup = False + + def test_init(self): + assert self.identifier._widget == self.widget + + def test_getIdentifier(self): + assert self.identifier.getIdentifier() == self.name + + def test_checkStoredIdentifier(self): + assert not self.identifier.checkStoredIdentifier() + + def test_setIdentifier(self): + assert not self.identifier.setIdentifier("GtkAnything") + + name = "x" + assert self.identifier.setIdentifier(name) == name + + def test_setWidget(self): + self.identifier.setWidget(None) + assert not self.identifier.getIdentifier() + + widget = gtk.Label("") + name = "asdf" + widget.set_name(name) + assert not self.identifier.getIdentifier() == name + + self.identifier.setWidget(widget) + assert self.identifier.getIdentifier() == name + + def test_getStoredIdentifier(self): + assert not self.identifier.getStoredIdentifier() == self.name + + self.identifier.getIdentifier() + assert self.identifier.getStoredIdentifier() == self.name + + name = "x" + self.identifier.setIdentifier(name) + assert self.identifier.getStoredIdentifier() == name + + def test_validateIdentifier(self): + assert not self.identifier.validateIdentifier(None) + assert not self.identifier.validateIdentifier("") + assert not self.identifier.validateIdentifier(1) + assert not self.identifier.validateIdentifier("GtkAnything") + assert not self.identifier.validateIdentifier("SugarToggleToolButton") + assert self.identifier.validateIdentifier("ValidName") + +class test_buttonIdentifier: + def __init__(self): + self.is_setup = False + + def setUp(self): + assert not self.is_setup + + self.name = "nameExample" + self.widget = gtk.Button() + + self.identifier = buttonIdentifier(self.widget) + + self.is_setup = True + + def tearDown(self): + assert self.is_setup + self.is_setup = False + + def test_init(self): + assert not self.identifier.getIdentifier() + + def test_getIdentifier1(self): + self.widget.set_label(self.name) + assert self.identifier.getIdentifier() == self.name + + def test_getIdentifier2(self): + self.widget.set_name(self.name) + assert self.identifier.getIdentifier() == self.name + +class test_toolButtonIdentifier: + def __init__(self): + self.is_setup = False + + def setUp(self): + assert not self.is_setup + + self.name = "nameExample" + self.widget = gtk.ToolButton() + + self.identifier = toolButtonIdentifier(self.widget) + + self.is_setup = True + + def tearDown(self): + assert self.is_setup + self.is_setup = False + + def test_init(self): + assert not self.identifier.getIdentifier() + + def test_getIdentifier1(self): + self.widget.set_name(self.name) + assert self.identifier.getIdentifier() == self.name + + def test_getIdentifier2(self): + self.widget.set_label(self.name) + assert self.identifier.getIdentifier() == self.name + + def test_getIdentifier3(self): + self.widget.set_icon_name(self.name) + assert self.identifier.getIdentifier() == self.name + + def test_getIdentifier4(self): + label = gtk.Label(self.name) + self.widget.set_label_widget(label) + assert self.identifier.getIdentifier() == self.name + +class test_comboBoxIdentifier: + def __init__(self): + self.is_setup = False + + def setUp(self): + assert not self.is_setup + + self.name = "nameExample" + self.widget = gtk.ComboBox() + + self.identifier = comboBoxIdentifier(self.widget) + + self.is_setup = True + + def tearDown(self): + assert self.is_setup + self.is_setup = False + + def test_init(self): + assert not self.identifier.getIdentifier() + + def test_getIdentifier1(self): + self.widget.set_name(self.name) + assert self.identifier.getIdentifier() == self.name + + def test_getIdentifier2(self): + self.widget.set_title(self.name) + assert self.identifier.getIdentifier() == self.name + +class test_entryIdentifier: + def __init__(self): + self.is_setup = False + + def setUp(self): + assert not self.is_setup + + self.name = "nameExample" + self.widget = gtk.Entry() + + self.identifier = entryIdentifier(self.widget) + + self.is_setup = True + + def tearDown(self): + assert self.is_setup + self.is_setup = False + + def test_init(self): + assert not self.identifier.getIdentifier() + + def test_getIdentifier1(self): + self.widget.set_name(self.name) + assert self.identifier.getIdentifier() == self.name + + def test_getIdentifier2(self): + self.widget.set_text(self.name) + assert self.identifier.getIdentifier() == self.name + +# class test_paletteIdentifier: +# def __init__(self): +# self.is_setup = False +# +# def setUp(self): +# assert not self.is_setup +# +# self.name = "nameExample" +# # self.widget = gtk.Entry() +# self.widget = Palette(self.name) +# +# self.identifier = paletteIdentifier(self.widget) +# +# self.is_setup = True +# +# def tearDown(self): +# assert self.is_setup +# self.is_setup = False +# +# def test_init(self): +# assert self.identifier.getIdentifier() +# +# def test_getIdentifier1(self): +# otherName = "someOtherName" +# self.widget.set_name(otherName) +# assert self.identifier.getIdentifier() == otherName +# +# def test_getIdentifier2(self): +# assert self.identifier.getIdentifier() == self.name + +class test_toolComboBoxIdentifier: + def __init__(self): + self.is_setup = False + + def setUp(self): + assert not self.is_setup + + self.name = "nameExample" + self.widget = ToolComboBox() + self.widget.set_property('label-text', self.name) + + # Set the widget later... + self.identifier = toolComboBoxIdentifier(None) + + self.is_setup = True + + def tearDown(self): + assert self.is_setup + self.is_setup = False + + def test_getIdentifier1(self): + otherName = "someOtherName" + self.widget.set_name(otherName) + self.identifier.setWidget(self.widget) + assert self.identifier.getIdentifier() == otherName + + def test_getIdentifier2(self): + self.identifier.setWidget(self.widget) + assert self.identifier.getIdentifier() == self.name + +if __name__ == "__main__": + unittest.main() diff --git a/Experior.Activity/test_widgetIdentifier.pyc b/Experior.Activity/test_widgetIdentifier.pyc new file mode 100644 index 0000000..f1385de --- /dev/null +++ b/Experior.Activity/test_widgetIdentifier.pyc Binary files differ diff --git a/Experior.Activity/widgetIdentifier.py b/Experior.Activity/widgetIdentifier.py new file mode 100755 index 0000000..c9eb7dd --- /dev/null +++ b/Experior.Activity/widgetIdentifier.py @@ -0,0 +1,260 @@ +#!/usr/bin/env python +# encoding: utf-8 +""" +widgetIdentifier.py + +This file is part of sugarbot. + +sugarbot is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +sugarbot is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with sugarbot. If not, see . +""" +import gobject +import sys +import os +import time +import pygtk +pygtk.require('2.0') + +import gtk +from gtk import gdk + +import sugar +from sugar import graphics +from sugar.graphics.toolbutton import Palette +from sugar.graphics.icon import Icon +from sugar.graphics.toolcombobox import ToolComboBox + +class widgetIdentifier: + """ + The widgetIdentifier class is used as a basis for classes to identify + Widgets. Ideally, all developers would call Widget.set_name() on all + widgets created by their Activities. Since this is not always + practical or worthwhile, we must rely on other methods to identify + widgets. + + This class provides some of the functionality for identifying widgets, + such as a list of strings that are default Widget names (e.g. 'GtkButton'). + """ + identifiers = { } + + def __init__(self, widget): + self.widgetAttribute = "sugarbotWidgetIdentifier" + + self.dontWant = ["GtkToolbar", "GtkToggleButton","GtkButton", + "GtkEventBox", "GtkNotebook", "GtkViewport", + "HippoCanvas", "GtkTextView", "GtkInvisible", + "GtkEntry", "GtkLabel", "GtkVBox", "GtkHBox", + "SugarIcon","SugarToolButton","GtkAlignment", + "GtkSeparatorToolItem","GtkTable","SugarToolbox", + "GtkToggleToolButton","GtkScrolledWindow","GtkCellView", + "GtkVSeparator","GtkArrow","GtkToolItem","GtkAccelLabel", + "SugarComboBox","SugarToggleToolButton","GtkHSeparator", + "GtkHButtonBox","GtkImageMenuItem","GtkSeparatorMenuItem", + "GtkSpinButton","GtkDrawingArea","GtkFrame", + "GtkColorButton", ""] + + self.dontWantPrefixes = ["Gtk", "sugar+graphics"] + self.setWidget(widget) + + def setWidget(self, widget): + self._widget = widget + # self.getIdentifier() + + def getIdentifierSub(self): + """ + Overridden by inheriting classes. + """ + return None + + def getIdentifier(self): + """ + Returns the Identifier of the Widget set with __init__, or None + if we cannot find an identifier. Do not override this function! + """ + # If we have already set the attribute for this widget, just + # retrieve it quickly. + if self.checkStoredIdentifier(): + return self.getStoredIdentifier() + + ident = None + widget = self._widget + + if hasattr(widget, "get_name"): + ident = widget.get_name() + + if not self.validateIdentifier(ident): + ident = self.getIdentifierSub() + + return self.setIdentifier(ident) + + def checkStoredIdentifier(self): + """ + Checks to see if we have previously identified this same Widget. + If we have, the Widget will have an attribute as defined by + widgetIdentifier.widgetAttribute. + """ + if hasattr(self._widget, self.widgetAttribute) \ + and getattr(self._widget, self.widgetAttribute) is not None: + return True + return False + + def getStoredIdentifier(self): + """ + If the Widget has a stored identifier (see checkStoredIdentifier), + then retrieve the stored identifier's value. + + If the Widget does not have a stored identifier, return None. + """ + if self.checkStoredIdentifier(): + return getattr(self._widget, self.widgetAttribute) + else: + return None + + def validateIdentifier(self,ident): + """ + Checks a proposed identifier against a series of criterium. For + example, empty strings, and blacklisted strings, as well as + blacklisted prefixes, may rule out an identifier for use. + """ + if ident is None: + return False + elif not isinstance(ident, str): + return False + elif len(ident) < 1: + return False + elif ident in self.dontWant: + return False + else: + for prefix in self.dontWantPrefixes: + if ident.startswith(prefix): + return False + return True + + def setIdentifier(self, ident): + """ + Sets the stored identifier for the Widget assigned by __init__. + """ + if self.validateIdentifier(ident): + setattr(self._widget, self.widgetAttribute, ident) + return ident + return None +widgetIdentifier.identifiers[gtk.Widget] = widgetIdentifier + + +class buttonIdentifier(widgetIdentifier): + def getIdentifierSub(self): + ident = None + widget = self._widget + + if hasattr(widget, "get_label"): + ident = widget.get_label() + + return ident +widgetIdentifier.identifiers[gtk.Button] = buttonIdentifier + +# class toolButtonIdentifier(widgetIdentifier): +class toolButtonIdentifier(buttonIdentifier): + def getIdentifierSub(self): + ident = buttonIdentifier.getIdentifierSub(self) + widget = self._widget + + # Get the identifier using the icon + if not self.validateIdentifier(ident): + ico = None + ico = widget.get_icon_widget() + if isinstance(ico, Icon): + ident = ico.props.icon_name + + # Label did not give us a good ident, check the icon name + if not self.validateIdentifier(ident): + ident = widget.get_icon_name() + + # Icon did not give us a good ident, try the label + if not self.validateIdentifier(ident): + label = widget.get_label_widget() + if hasattr(label,"get_text"): + ident = label.get_text() + + return ident +widgetIdentifier.identifiers[gtk.ToolButton] = toolButtonIdentifier + +class comboBoxIdentifier(widgetIdentifier): + def getIdentifierSub(self): + ident = None + widget = self._widget + + if hasattr(widget, "get_title"): + ident = self._widget.get_title() + + return ident +widgetIdentifier.identifiers[gtk.ComboBox] = comboBoxIdentifier + + +class entryIdentifier(widgetIdentifier): + def getIdentifierSub(self): + ident = None + widget = self._widget + + if not self.validateIdentifier(ident): + if hasattr(widget, "get_text"): + ident = self._widget.get_text() + + return ident +widgetIdentifier.identifiers[gtk.Entry] = entryIdentifier + +class paletteIdentifier(widgetIdentifier): + def getIdentifierSub(self): + ident = None + widget = self._widget + + if hasattr(widget, "_primary_text"): + ident = getattr(widget, "_primary_text") + + elif not self.validateIdentifier(ident): + if hasattr(widget, "props.primary_text"): + ident = getattr(widget, "props.primary_text") + + return ident +widgetIdentifier.identifiers[Palette] = paletteIdentifier + + +class toolComboBoxIdentifier(widgetIdentifier): + def getIdentifierSub(self): + ident = None + widget = self._widget + + if hasattr(widget, "_label_text"): + ident = getattr(widget, "_label_text") + print ident + + # if not self.validateIdentifier(ident): + # try: + # print "@@@" + # ident = widget.get_property("label-text") + # print ident + # except TypeError: + # raise + + # + # if not self.validateIdentifier(ident): + # if hasattr(widget, "_label_text"): + # ident = getattr(widget, "_label_text") + # + # elif not self.validateIdentifier(ident): + # if hasattr(widget, "label"): + # label = widget.label + # ident = label.get_text() + # + + return ident +widgetIdentifier.identifiers[ToolComboBox] = toolComboBoxIdentifier \ No newline at end of file diff --git a/Experior.Activity/widgetIdentifier.pyc b/Experior.Activity/widgetIdentifier.pyc new file mode 100644 index 0000000..99312df --- /dev/null +++ b/Experior.Activity/widgetIdentifier.pyc Binary files differ -- cgit v0.9.1