Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--shell/model/homeactivity.py59
-rw-r--r--shell/model/homemodel.py24
-rw-r--r--sugar/__init__.py1
-rw-r--r--sugar/activity/activity.py28
-rw-r--r--sugar/activity/activityfactory.py47
-rw-r--r--sugar/activity/activityfactoryservice.py52
-rw-r--r--sugar/activity/activityhandle.py58
-rw-r--r--sugar/activity/activityservice.py15
-rw-r--r--sugar/activity/bundle.py13
-rw-r--r--sugar/activity/bundleregistry.py19
-rw-r--r--sugar/date.py15
-rw-r--r--sugar/env.py1
-rw-r--r--sugar/logger.py1
-rw-r--r--sugar/profile.py25
-rw-r--r--sugar/util.py33
15 files changed, 373 insertions, 18 deletions
diff --git a/shell/model/homeactivity.py b/shell/model/homeactivity.py
index 9570c1b..cb71d8e 100644
--- a/shell/model/homeactivity.py
+++ b/shell/model/homeactivity.py
@@ -25,6 +25,13 @@ from sugar.presence import presenceservice
from sugar import profile
class HomeActivity(gobject.GObject):
+ """Activity which appears in the "Home View" of the Sugar shell
+
+ This class stores the Sugar Shell's metadata regarding a
+ given activity/application in the system. It interacts with
+ the sugar.activity.* modules extensively in order to
+ accomplish its tasks.
+ """
__gsignals__ = {
'launch-timeout': (gobject.SIGNAL_RUN_FIRST,
gobject.TYPE_NONE,
@@ -32,6 +39,15 @@ class HomeActivity(gobject.GObject):
}
def __init__(self, bundle, activity_id):
+ """Initialise the HomeActivity
+
+ bundle -- sugar.activity.bundle.Bundle instance,
+ provides the information required to actually
+ create the new instance. This is, in effect,
+ the "type" of activity being created.
+ activity_id -- unique identifier for this instance
+ of the activity type
+ """
gobject.GObject.__init__(self)
self._window = None
self._xid = None
@@ -52,6 +68,8 @@ class HomeActivity(gobject.GObject):
self._launch_timeout_id = 0
def _launch_timeout_cb(self, user_data=None):
+ """Callback for launch operation timeouts
+ """
logging.debug("Activity %s (%s) launch timed out" %
(self._activity_id, self.get_type))
self._launch_timeout_id = 0
@@ -78,17 +96,33 @@ class HomeActivity(gobject.GObject):
self._service = service
def get_service(self):
+ """Retrieve the application's sugar introspection service
+
+ Note that non-native Sugar applications will not have
+ such a service, so the return value will be None in
+ those cases.
+ """
return self._service
def get_title(self):
+ """Retrieve the application's root window's suggested title"""
if not self._launched:
raise RuntimeError("Activity is still launching.")
return self._window.get_name()
def get_icon_name(self):
+ """Retrieve the bundle's icon (file) name"""
return self._bundle.get_icon()
def get_icon_color(self):
+ """Retrieve the appropriate icon colour for this activity
+
+ Uses activity_id to index into the PresenceService's
+ set of activity colours, if the PresenceService does not
+ have an entry (implying that this is not a Sugar-shared application)
+ uses the local user's profile.get_color() to determine the
+ colour for the icon.
+ """
pservice = presenceservice.get_instance()
activity = pservice.get_activity(self._activity_id)
if activity != None:
@@ -97,28 +131,53 @@ class HomeActivity(gobject.GObject):
return profile.get_color()
def get_activity_id(self):
+ """Retrieve the "activity_id" passed in to our constructor
+
+ This is a "globally likely unique" identifier generated by
+ sugar.util.unique_id
+ """
return self._activity_id
def get_xid(self):
+ """Retrieve the X-windows ID of our root window"""
if not self._launched:
raise RuntimeError("Activity is still launching.")
return self._xid
def get_window(self):
+ """Retrieve the X-windows root window of this application
+
+ This was stored by the set_window method, which was
+ called by HomeModel._add_activity, which was called
+ via a callback that looks for all 'window-opened'
+ events.
+
+ HomeModel currently uses a dbus service query on the
+ activity to determine to which HomeActivity the newly
+ launched window belongs.
+ """
if not self._launched:
raise RuntimeError("Activity is still launching.")
return self._window
def get_type(self):
+ """Retrieve bundle's "service_name" for future reference"""
return self._bundle.get_service_name()
def get_shared(self):
+ """Return whether this activity is using Presence service sharing"""
if not self._launched:
raise RuntimeError("Activity is still launching.")
return self._service.get_shared()
def get_launch_time(self):
+ """Return the time at which the activity was first launched
+
+ Format is floating-point time.time() value
+ (seconds since the epoch)
+ """
return self._launch_time
def get_launched(self):
+ """Return whether we have bound our top-level window yet"""
return self._launched
diff --git a/shell/model/homemodel.py b/shell/model/homemodel.py
index 9aa47e2..0b1eeb5 100644
--- a/shell/model/homemodel.py
+++ b/shell/model/homemodel.py
@@ -28,7 +28,19 @@ _SERVICE_NAME = "org.laptop.Activity"
_SERVICE_PATH = "/org/laptop/Activity"
class HomeModel(gobject.GObject):
-
+ """Model of the "Home" view (activity management)
+
+ The HomeModel is basically the point of registration
+ for all running activities within Sugar. It traps
+ events that tell the system there is a new activity
+ being created (generated by the activity factories),
+ or removed, as well as those which tell us that the
+ currently focussed activity has changed.
+
+ The HomeModel tracks a set of HomeActivity instances,
+ which are tracking the window to activity mappings
+ the activity factories have set up.
+ """
__gsignals__ = {
'activity-launched': (gobject.SIGNAL_RUN_FIRST,
gobject.TYPE_NONE,
@@ -124,6 +136,16 @@ class HomeModel(gobject.GObject):
self.emit('activity-added', home_window)
def _add_activity(self, window):
+ """Add the window to the set of windows we track
+
+ At the moment this requires that something somewhere
+ have registered a dbus service with the XID of the
+ new window that is used to bind the requested activity
+ to the window.
+
+ window -- gtk.Window instance representing a new
+ normal, top-level window
+ """
bus = dbus.SessionBus()
xid = window.get_xid()
try:
diff --git a/sugar/__init__.py b/sugar/__init__.py
index 238ae96..05d58f0 100644
--- a/sugar/__init__.py
+++ b/sugar/__init__.py
@@ -1,3 +1,4 @@
+"""OLPC Sugar User Interface"""
ZOOM_MESH = 0
ZOOM_FRIENDS = 1
ZOOM_HOME = 2
diff --git a/sugar/activity/activity.py b/sugar/activity/activity.py
index d6e363b..fe0780c 100644
--- a/sugar/activity/activity.py
+++ b/sugar/activity/activity.py
@@ -1,3 +1,8 @@
+"""Base class for Python-coded activities
+
+This is currently the only reference for what an
+activity must do to participate in the Sugar desktop.
+"""
# Copyright (C) 2006, Red Hat, Inc.
#
# This library is free software; you can redistribute it and/or
@@ -29,6 +34,26 @@ class Activity(Window, gtk.Container):
"""Base Activity class that all other Activities derive from."""
__gtype_name__ = 'SugarActivity'
def __init__(self, handle):
+ """Initialise the Activity
+
+ handle -- sugar.activity.activityhandle.ActivityHandle
+ instance providing the activity id and access to the
+ presence service which *may* provide sharing for this
+ application
+
+ Side effects:
+
+ Sets the gdk screen DPI setting (resolution) to the
+ Sugar screen resolution.
+
+ Connects our "destroy" message to our _destroy_cb
+ method.
+
+ Creates a base gtk.Window within this window.
+
+ Creates an ActivityService (self._bus) servicing
+ this application.
+ """
Window.__init__(self)
self.connect('destroy', self._destroy_cb)
@@ -105,6 +130,7 @@ class Activity(Window, gtk.Container):
return False
def _destroy_cb(self, window):
+ """Destroys our ActivityService and sharing service"""
if self._bus:
del self._bus
self._bus = None
@@ -112,4 +138,6 @@ class Activity(Window, gtk.Container):
self._pservice.unregister_service(self._service)
def get_bundle_path():
+ """Return the bundle path for the current process' bundle
+ """
return os.environ['SUGAR_BUNDLE_PATH']
diff --git a/sugar/activity/activityfactory.py b/sugar/activity/activityfactory.py
index 222333e..06ab280 100644
--- a/sugar/activity/activityfactory.py
+++ b/sugar/activity/activityfactory.py
@@ -1,3 +1,4 @@
+"""Shell side object which manages request to start activity"""
# Copyright (C) 2006, Red Hat, Inc.
#
# This library is free software; you can redistribute it and/or
@@ -31,6 +32,7 @@ _ACTIVITY_SERVICE_PATH = "/org/laptop/Activity"
_ACTIVITY_INTERFACE = "org.laptop.Activity"
def create_activity_id():
+ """Generate a new, unique ID for this activity"""
pservice = presenceservice.get_instance()
# create a new unique activity ID
@@ -52,6 +54,14 @@ def create_activity_id():
raise RuntimeError("Cannot generate unique activity id.")
class ActivityCreationHandler(gobject.GObject):
+ """Sugar-side activity creation interface
+
+ This object uses a dbus method on the ActivityFactory
+ service to create the new activity. It generates
+ GObject events in response to the success/failure of
+ activity startup using callbacks to the service's
+ create call.
+ """
__gsignals__ = {
'success': (gobject.SIGNAL_RUN_FIRST,
gobject.TYPE_NONE, ([])),
@@ -61,6 +71,29 @@ class ActivityCreationHandler(gobject.GObject):
}
def __init__(self, service_name, activity_handle):
+ """Initialise the handler
+
+ service_name -- used to retrieve the activity bundle
+ from the global BundleRegistry. This is what
+ determines what activity will be run.
+ activity_handle -- stores the values which are to
+ be passed to the service to uniquely identify
+ the activity to be created and the sharing
+ service that may or may not be connected with it
+
+ sugar.activity.activityhandle.ActivityHandle instance
+
+ calls the "create" method on the service for this
+ particular activity type and registers the
+ _reply_handler and _error_handler methods on that
+ call's results.
+
+ The specific service which creates new instances of this
+ particular type of activity is created during the activity
+ registration process in
+ sugar.activity.bundleregistry.BundleRegistry which creates
+ service definition files for each registered bundle type.
+ """
gobject.GObject.__init__(self)
self._service_name = service_name
self._activity_handle = activity_handle
@@ -75,16 +108,28 @@ class ActivityCreationHandler(gobject.GObject):
factory.create(self._activity_handle.get_dict(),
reply_handler=self._reply_handler,
error_handler=self._error_handler)
-
def get_activity_id(self):
+ """Retrieve the unique identity for this activity"""
return self._activity_handle.activity_id
def _reply_handler(self, xid):
+ """Reply from service regarding what window was created
+
+ xid -- X windows ID for the window that was just created
+
+ emits the "success" message (on ourselves)
+ """
logging.debug("Activity created %s (%s)." %
(self._activity_handle.activity_id, self._service_name))
self.emit('success')
def _error_handler(self, err):
+ """Reply from service with an error message (exception)
+
+ err -- exception object describing the error
+
+ emits the "error" message (on ourselves)
+ """
logging.debug("Couldn't create activity %s (%s): %s" %
(self._activity_handle.activity_id, self._service_name, err))
self.emit('error', err)
diff --git a/sugar/activity/activityfactoryservice.py b/sugar/activity/activityfactoryservice.py
index 3142da0..08b5eec 100644
--- a/sugar/activity/activityfactoryservice.py
+++ b/sugar/activity/activityfactoryservice.py
@@ -35,9 +35,43 @@ gobject.threads_init()
dbus.glib.threads_init()
class ActivityFactoryService(dbus.service.Object):
- """D-Bus service that creates new instances of an activity"""
+ """D-Bus service that creates instances of Python activities
+
+ The ActivityFactoryService is a dbus service created for
+ each Python based activity type (that is, each activity
+ bundle which declares a "class" in its activity.info file,
+ rather than an "exec").
+
+ The ActivityFactoryService is the actual process which
+ instantiates the Python classes for Sugar interfaces. That
+ is, your Python code runs in the same process as the
+ ActivityFactoryService itself.
+
+ The "service" process is created at the moment Sugar first
+ attempts to create an instance of the activity type. It
+ then remains in memory until the last instance of the
+ activity type is terminated.
+ """
def __init__(self, service_name, activity_class):
+ """Initialize the service to create activities of this type
+
+ service_name -- bundle's service name, this is used
+ to construct the dbus service name used to access
+ the created service.
+ activity_class -- dotted Python class name for the
+ Activity class which is to be instantiated by
+ the service. Assumed to be composed of a module
+ followed by a class.
+
+ if the module specified has a "start" attribute this object
+ will be called on service initialisation (before first
+ instance is created).
+
+ if the module specified has a "stop" attribute this object
+ will be called after every instance exits (note: may be
+ called multiple times for each time start is called!)
+ """
self._activities = []
splitted_module = activity_class.rsplit('.', 1)
@@ -60,6 +94,12 @@ class ActivityFactoryService(dbus.service.Object):
@dbus.service.method("com.redhat.Sugar.ActivityFactory", in_signature="a{ss}")
def create(self, handle):
+ """Create a new instance of this activity
+
+ handle -- sugar.activity.activityhandle.ActivityHandle
+ compatible dictionary providing the instance-specific
+ values for the new instance
+ """
activity_handle = activityhandle.create_from_dict(handle)
activity = self._constructor(activity_handle)
activity.present()
@@ -70,6 +110,16 @@ class ActivityFactoryService(dbus.service.Object):
return activity.window.xid
def _activity_destroy_cb(self, activity):
+ """On close of an instance's root window
+
+ Removes the activity from the tracked activities.
+
+ If our implementation module has a stop, calls
+ that.
+
+ If there are no more tracked activities, closes
+ the activity.
+ """
self._activities.remove(activity)
if hasattr(self._module, 'stop'):
diff --git a/sugar/activity/activityhandle.py b/sugar/activity/activityhandle.py
index fee6327..c87fb6b 100644
--- a/sugar/activity/activityhandle.py
+++ b/sugar/activity/activityhandle.py
@@ -18,13 +18,47 @@
from sugar.presence import presenceservice
class ActivityHandle(object):
- def __init__(self, activity_id):
+ """Data structure storing simple activity metadata"""
+ def __init__(
+ self, activity_id, pservice_id=None,
+ object_id=None,uri=None
+ ):
+ """Initialise the handle from activity_id
+
+ activity_id -- unique id for the activity to be
+ created
+ pservice_id -- identity of the sharing service
+ for this activity in the PresenceService
+ object_id -- identity of the journal object
+ associated with the activity. It was used by
+ the journal prototype implementation, might
+ change when we do the real one.
+
+ When you resume an activity from the journal
+ the object_id will be passed in. It's optional
+ since new activities does not have an
+ associated object (yet).
+
+ XXX Not clear how this relates to the activity
+ id yet, i.e. not sure we really need both. TBF
+ uri -- URI associated with the activity. Used when
+ opening an external file or resource in the
+ activity, rather than a journal object
+ (downloads stored on the file system for
+ example or web pages)
+ """
self.activity_id = activity_id
- self.pservice_id = None
- self.object_id = None
- self.uri = None
+ self.pservice_id = pservice_id
+ self.object_id = object_id
+ self.uri = uri
def get_presence_service(self):
+ """Retrieve the "sharing service" for this activity
+
+ Uses the PresenceService to find any existing dbus
+ service which provides sharing mechanisms for this
+ activity.
+ """
if self.pservice_id:
pservice = presenceservice.get_instance()
return pservice.get_activity(self.pservice_id)
@@ -32,6 +66,7 @@ class ActivityHandle(object):
return None
def get_dict(self):
+ """Retrieve our settings as a dictionary"""
result = { 'activity_id' : self.activity_id }
if self.pservice_id:
result['pservice_id'] = self.pservice_id
@@ -43,12 +78,11 @@ class ActivityHandle(object):
return result
def create_from_dict(handle_dict):
- result = ActivityHandle(handle_dict['activity_id'])
- if handle_dict.has_key('pservice_id'):
- result.pservice_id = handle_dict['pservice_id']
- if handle_dict.has_key('object_id'):
- result.object_id = handle_dict['object_id']
- if handle_dict.has_key('uri'):
- result.uri = handle_dict['uri']
-
+ """Create a handle from a dictionary of parameters"""
+ result = ActivityHandle(
+ handle_dict['activity_id'],
+ pservice_id = handle_dict.get( 'pservice_id' ),
+ object_id = handle_dict.get('object_id'),
+ uri = handle_dict.get('uri'),
+ )
return result
diff --git a/sugar/activity/activityservice.py b/sugar/activity/activityservice.py
index bff42fa..e5b8956 100644
--- a/sugar/activity/activityservice.py
+++ b/sugar/activity/activityservice.py
@@ -29,6 +29,21 @@ class ActivityService(dbus.service.Object):
tightly control what stuff passes through the dbus python bindings."""
def __init__(self, activity):
+ """Initialise the service for the given activity
+
+ activity -- sugar.activity.activity.Activity instance,
+ must have already bound it's window (i.e. it must
+ have already initialised to the point of having
+ the X window available).
+
+ Creates dbus services that use the xid of the activity's
+ root window as discriminants among all active services
+ of this type. That is, the services are all available
+ as names/paths derived from the xid for the window.
+
+ The various methods exposed on dbus are just forwarded
+ to the client Activity object's equally-named methods.
+ """
xid = activity.window.xid
service_name = _ACTIVITY_SERVICE_NAME + '%d' % xid
object_path = _ACTIVITY_SERVICE_PATH + "/%s" % xid
diff --git a/sugar/activity/bundle.py b/sugar/activity/bundle.py
index 8cb1945..2ea5a54 100644
--- a/sugar/activity/bundle.py
+++ b/sugar/activity/bundle.py
@@ -1,3 +1,4 @@
+"""Metadata description of a given application/activity"""
import logging
import locale
import os
@@ -8,7 +9,17 @@ from sugar import env
_PYTHON_FACTORY='sugar-activity-factory'
class Bundle:
- """Info about an activity bundle. Wraps the activity.info file."""
+ """Metadata description of a given application/activity
+
+ The metadata is normally read from an activity.info file,
+ which is an INI-style configuration file read using the
+ standard Python ConfigParser module.
+
+ The format reference for the Bundle definition files is
+ available for further reference:
+
+ http://wiki.laptop.org/go/Activity_bundles
+ """
def __init__(self, path):
self._name = None
self._icon = None
diff --git a/sugar/activity/bundleregistry.py b/sugar/activity/bundleregistry.py
index 7b12492..58b1700 100644
--- a/sugar/activity/bundleregistry.py
+++ b/sugar/activity/bundleregistry.py
@@ -15,8 +15,25 @@ def _get_data_dirs():
return [ '/usr/local/share/', '/usr/share/' ]
class _ServiceManager(object):
+ """Internal class responsible for creating dbus service files
+
+ DBUS services are defined in files which bind a service name
+ to the name of an executable which provides the service name.
+
+ In Sugar, the service files are automatically generated from
+ the activity registry (by this class). When an activity's
+ dbus launch service is requested, dbus will launch the
+ specified executable in order to allow it to provide the
+ requested activity-launching service.
+
+ In the case of activities which provide a "class", instead of
+ an "exec" attribute in their activity.info, the
+ sugar-activity-factory script is used with an appropriate
+ argument to service that bundle.
+ """
+ SERVICE_DIRECTORY = '~/.local/share/dbus-1/services'
def __init__(self):
- service_dir = os.path.expanduser('~/.local/share/dbus-1/services')
+ service_dir = os.path.expanduser(self.SERVICE_DIRECTORY)
if not os.path.isdir(service_dir):
os.makedirs(service_dir)
diff --git a/sugar/date.py b/sugar/date.py
index 331124b..2894a87 100644
--- a/sugar/date.py
+++ b/sugar/date.py
@@ -1,10 +1,25 @@
+"""Simple date-representation model"""
import datetime
class Date(object):
+ """Date-object storing a simple time.time() float
+
+ XXX not sure about the rationale for this class,
+ possibly it makes transfer over dbus easier?
+ """
def __init__(self, timestamp):
+ """Initialise via a timestamp (floating point value)"""
self._timestamp = timestamp
def __str__(self):
+ """Produce a formatted date representation
+
+ Eventually this should produce a localised version
+ of the date. At the moment it always produces English
+ dates in long form with Today and Yesterday
+ special-cased and dates from this year not presenting
+ the year in the date.
+ """
date = datetime.date.fromtimestamp(self._timestamp)
today = datetime.date.today()
diff --git a/sugar/env.py b/sugar/env.py
index 00539b5..f25f460 100644
--- a/sugar/env.py
+++ b/sugar/env.py
@@ -1,3 +1,4 @@
+"""Calculates file-paths for the Sugar working environment"""
# Copyright (C) 2006, Red Hat, Inc.
#
# This library is free software; you can redistribute it and/or
diff --git a/sugar/logger.py b/sugar/logger.py
index bd01f2e..87a9c96 100644
--- a/sugar/logger.py
+++ b/sugar/logger.py
@@ -1,3 +1,4 @@
+"""Logging module configuration for Sugar"""
# Copyright (C) 2006, Red Hat, Inc.
#
# This library is free software; you can redistribute it and/or
diff --git a/sugar/profile.py b/sugar/profile.py
index 4df0f52..840e101 100644
--- a/sugar/profile.py
+++ b/sugar/profile.py
@@ -1,3 +1,4 @@
+"""User settings/configuration loading"""
# Copyright (C) 2006, Red Hat, Inc.
#
# This program is free software; you can redistribute it and/or modify
@@ -23,6 +24,30 @@ from sugar import util
from sugar.graphics.xocolor import XoColor
class _Profile(object):
+ """Local user's current options/profile information
+
+ User settings are stored in an INI-style configuration
+ file. This object uses the ConfigParser module to load
+ the settings. At the moment the only storage mechanism
+ is in the set_server_registered method, which loads the
+ file directly from disk, then dumps it back out again
+ immediately, rather than using the class.
+
+ The profile is also responsible for loading the user's
+ public and private ssh keys from disk.
+
+ Attributes:
+
+ name -- child's name
+ color -- XoColor for the child's icon
+ server -- school server with which the child is
+ associated
+ server_registered -- whether the child has registered
+ with the school server or not
+
+ pubkey -- public ssh key
+ privkey_hash -- SHA has of the child's public key
+ """
def __init__(self):
self.name = None
self.color = None
diff --git a/sugar/util.py b/sugar/util.py
index 8ad840d..58fcc7b 100644
--- a/sugar/util.py
+++ b/sugar/util.py
@@ -1,3 +1,4 @@
+"""Various utility functions"""
# Copyright (C) 2006, Red Hat, Inc.
#
# This library is free software; you can redistribute it and/or
@@ -38,6 +39,19 @@ def _sha_data(data):
return sha_hash.digest()
def unique_id(data = ''):
+ """Generate a likely-unique ID for whatever purpose
+
+ data -- suffix appended to working data before hashing
+
+ Returns a 40-character string with hexidecimal digits
+ representing an SHA hash of the time, a random digit
+ within a constrained range and the data passed.
+
+ Note: these are *not* crypotographically secure or
+ globally unique identifiers. While they are likely
+ to be unique-enough, no attempt is made to make
+ perfectly unique values.
+ """
data_string = "%s%s%s" % (time.time(), random.randint(10000, 100000), data)
return printable_hash(_sha_data(data_string))
@@ -49,7 +63,7 @@ def is_hex(s):
def validate_activity_id(actid):
"""Validate an activity ID."""
- if not isinstance(actid, str) and not isinstance(actid, unicode):
+ if not isinstance(actid, (str,unicode)):
return False
if len(actid) != ACTIVITY_ID_LEN:
return False
@@ -62,6 +76,23 @@ class _ServiceParser(ConfigParser):
return option
def write_service(name, bin, path):
+ """Write a D-BUS service definition file
+
+ These are written by the bundleregistry when
+ a new activity is registered. They bind a
+ D-BUS bus-name with an executable which is
+ to provide the named service.
+
+ name -- D-BUS service name, must be a valid
+ filename/D-BUS name
+ bin -- executable providing named service
+ path -- directory into which to write the
+ name.service file
+
+ The service files themselves are written using
+ the _ServiceParser class, which is a subclass
+ of the standard ConfigParser class.
+ """
service_cp = _ServiceParser()
section = 'D-BUS Service'
service_cp.add_section(section)