From 845575c70851f0dae2a35312ec90a0f3cf055746 Mon Sep 17 00:00:00 2001 From: Marco Pesenti Gritti Date: Sat, 14 Apr 2007 08:00:00 +0000 Subject: Merge branch 'master' of git+ssh://dev.laptop.org/git/sugar --- diff --git a/services/presence/activity.py b/services/presence/activity.py index 3638a96..7471b46 100644 --- a/services/presence/activity.py +++ b/services/presence/activity.py @@ -18,6 +18,7 @@ import gobject import dbus, dbus.service from sugar import util +import logging from telepathy.interfaces import (CHANNEL_INTERFACE) @@ -228,22 +229,27 @@ class Activity(DBusGObject): # Not for us return - (sigid, async_cb, async_err_cb) = userdata + (sigid, owner, async_cb, async_err_cb) = userdata self._tp.disconnect(sigid) if exc: + logging.debug("Share of activity %s failed: %s" % (self._id, exc)) async_err_cb(exc) else: self._handle_share_join(tp, text_channel) self.send_properties() + owner.add_activity(self) async_cb(dbus.ObjectPath(self._object_path)) + logging.debug("Share of activity %s succeeded." % self._id) - def _share(self, (async_cb, async_err_cb)): + def _share(self, (async_cb, async_err_cb), owner): + logging.debug("Starting share of activity %s" % self._id) if self._joined: async_err_cb(RuntimeError("Already shared activity %s" % self.props.id)) return sigid = self._tp.connect('activity-shared', self._shared_cb) - self._tp.share_activity(self.props.id, (sigid, async_cb, async_err_cb)) + self._tp.share_activity(self.props.id, (sigid, owner, async_cb, async_err_cb)) + logging.debug("done with share attempt %s" % self._id) def _joined_cb(self, tp, activity_id, text_channel, exc, userdata): if activity_id != self.props.id: diff --git a/services/presence/presenceservice.py b/services/presence/presenceservice.py index 9fa3483..fd4cbb6 100644 --- a/services/presence/presenceservice.py +++ b/services/presence/presenceservice.py @@ -169,7 +169,10 @@ class PresenceService(dbus.service.Object): del self._activities[activity.props.id] def _buddy_activities_changed(self, tp, contact_handle, activities): - logging.debug("------------activities changed-------------") + acts = [] + for act in activities: + acts.append(str(act)) + logging.debug("Handle %s activities changed: %s" % (contact_handle, acts)) buddies = self._handles_buddies[tp] buddy = buddies.get(contact_handle) @@ -306,7 +309,7 @@ class PresenceService(dbus.service.Object): id=actid, type=atype, name=name, color=color, local=True) activity.connect("validity-changed", self._activity_validity_changed_cb) self._activities[actid] = activity - activity._share(callbacks) + activity._share(callbacks, self._owner) # local activities are valid at creation by definition, but we can't # connect to the activity's validity-changed signal until its already diff --git a/shell/view/ActivityHost.py b/shell/view/ActivityHost.py index d0bb663..c7bbd2d 100644 --- a/shell/view/ActivityHost.py +++ b/shell/view/ActivityHost.py @@ -66,8 +66,7 @@ class ActivityHost: return self._activity.execute(command, dbus.Array(args)) def share(self): - self._activity.share() - self._chat_widget.share() + self._activity.share(ignore_reply=True) def invite(self, buddy): pass diff --git a/sugar/activity/__init__.py b/sugar/activity/__init__.py index e69de29..524cdfc 100644 --- a/sugar/activity/__init__.py +++ b/sugar/activity/__init__.py @@ -0,0 +1,47 @@ +"""Activity implementation code for Sugar-based activities + +Each activity within the OLPC environment must provide two +dbus services. The first, patterned after the + + sugar.activity.activityfactory.ActivityFactory + +class is responsible for providing a "create" method which +takes a small dictionary with values corresponding to a + + sugar.activity.activityhandle.ActivityHandle + +describing an individual instance of the activity. The +ActivityFactory service is registered with dbus using the +global + + sugar.activity.bundleregistry.BundleRegistry + +service, which creates dbus .service files in a well known +directory. Those files tell dbus what executable to run +in order to load the ActivityFactory which will provide +the creation service. + +Each activity so registered is described by a + + sugar.activity.bundle.Bundle + +instance, which parses a specially formatted activity.info +file (stored in the activity directory's ./activity +subdirectory). The + + sugar.activity.bundlebuilder + +module provides facilities for the standard setup.py module +which produces and registers bundles from activity source +directories. + +Once instantiated by the ActivityFactory's create method, +each activity must provide an introspection API patterned +after the + + sugar.activity.activityservice.ActivityService + +class. This class allows for querying the ID of the root +window, requesting sharing across the network, and basic +"what type of application are you" queries. +""" diff --git a/sugar/activity/activity.py b/sugar/activity/activity.py index 5360253..1fb77b8 100644 --- a/sugar/activity/activity.py +++ b/sugar/activity/activity.py @@ -62,6 +62,7 @@ class Activity(Window, gtk.Container): self._activity_id = handle.activity_id self._pservice = presenceservice.get_instance() self._service = None + self._share_sigid = None service = handle.get_presence_service() if service: @@ -100,11 +101,21 @@ class Activity(Window, gtk.Container): self._service.join() self.present() + def _share_cb(self, ps, success, service, err): + self._pservice.disconnect(self._share_sigid) + self._share_sigid = None + if success: + logging.debug('Share of activity %s successful.' % self.get_id()) + self._service = service + self._shared = True + else: + logging.debug('Share of activity %s failed: %s.' % (self.get_id(), err)) + def share(self): - """Share the activity on the network.""" - logging.debug('Share activity %s on the network.' % self.get_id()) - self._service = self._pservice.share_activity(self) - self._shared = True + """Request that the activity be shared on the network.""" + logging.debug('Requesting share of activity %s.' % self.get_id()) + self._share_sigid = self._pservice.connect("activity-shared", self._share_cb) + self._pservice.share_activity(self) def execute(self, command, args): """Execute the given command with args""" diff --git a/sugar/activity/activityfactoryservice.py b/sugar/activity/activityfactoryservice.py index cf51f9c..5dcedb3 100644 --- a/sugar/activity/activityfactoryservice.py +++ b/sugar/activity/activityfactoryservice.py @@ -99,6 +99,8 @@ class ActivityFactoryService(dbus.service.Object): handle -- sugar.activity.activityhandle.ActivityHandle compatible dictionary providing the instance-specific values for the new instance + + returns xid for the created instance' root window """ activity_handle = activityhandle.create_from_dict(handle) activity = self._constructor(activity_handle) diff --git a/sugar/browser/__init__.py b/sugar/browser/__init__.py index 0fa05f3..a79048b 100644 --- a/sugar/browser/__init__.py +++ b/sugar/browser/__init__.py @@ -1 +1,6 @@ +"""Sugar's web-browser activity + +XUL Runner and gtkmozembed and is produced by the PyGTK +.defs system. +""" from sugar.browser._sugarbrowser import * diff --git a/sugar/clipboard/clipboardservice.py b/sugar/clipboard/clipboardservice.py index c7d68e4..e144fae 100644 --- a/sugar/clipboard/clipboardservice.py +++ b/sugar/clipboard/clipboardservice.py @@ -1,3 +1,4 @@ +"""UI class to access system-level clipboard object""" import logging import dbus import gobject @@ -14,7 +15,16 @@ DBUS_INTERFACE = "org.laptop.Clipboard" DBUS_PATH = "/org/laptop/Clipboard" class ClipboardService(gobject.GObject): - + """GUI interfaces for the system clipboard dbus service + + This object is used to provide convenient access to the clipboard + service (see source/services/clipboard/clipboardservice.py). It + provides utility methods for adding/getting/removing objects from + the clipboard as well as generating events when such events occur. + + Meaning is source/services/clipboard/clipboardobject.py + objects when describing "objects" on the clipboard. + """ __gsignals__ = { 'object-added': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([str, str])), @@ -25,6 +35,11 @@ class ClipboardService(gobject.GObject): } def __init__(self): + """Initialise the ClipboardService instance + + If the service is not yet active in the background uses + a signal watcher to connect when the service appears. + """ gobject.GObject.__init__(self) self._dbus_service = None @@ -44,6 +59,7 @@ class ClipboardService(gobject.GObject): logging.debug(exception) def _connect_clipboard_signals(self): + """Connect dbus signals to our GObject signal generating callbacks""" bus = dbus.SessionBus() if not self._connected: proxy_obj = bus.get_object(DBUS_SERVICE, DBUS_PATH) @@ -59,46 +75,124 @@ class ClipboardService(gobject.GObject): bus.remove_signal_receiver(self._nameOwnerChangedHandler) def _name_owner_changed_cb(self, name, old, new): + """On backend service creation, connect to the server""" if not old and new: # ClipboardService started up self._connect_clipboard_signals() def _object_added_cb(self, object_id, name): + """Emit an object-added GObject event when dbus event arrives""" self.emit('object-added', str(object_id), name) def _object_deleted_cb(self, object_id): + """Emit an object-deleted GObject event when dbus event arrives""" self.emit('object-deleted', str(object_id)) def _object_state_changed_cb(self, object_id, values): + """Emit an object-state-changed GObject event when dbus event arrives + + GObject event has: + + object_id + name + percent + icon + preview + activity + + From the ClipboardObject instance which is being described. + """ self.emit('object-state-changed', str(object_id), values[NAME_KEY], values[PERCENT_KEY], values[ICON_KEY], values[PREVIEW_KEY], values[ACTIVITY_KEY]) def add_object(self, name): + """Add a new object to the path + + returns dbus path-name for the new object's cliboard service, + this is used for all future references to the cliboard object. + + Note: + That service is actually provided by the clipboard + service object, not the ClipboardObject + """ return str(self._dbus_service.add_object(name)) def add_object_format(self, object_id, formatType, data, on_disk): + """Annotate given object on the clipboard with new information + + object_id -- dbus path as returned from add_object + formatType -- XXX what should this be? mime type? + data -- storage format for the clipped object? + on_disk -- whether the data is on-disk (non-volatile) or in + memory (volatile) + + Last three arguments are just passed directly to the + clipboardobject.Format instance on the server side. + + returns None + """ self._dbus_service.add_object_format(dbus.ObjectPath(object_id), formatType, data, on_disk) def delete_object(self, object_id): + """Remove the given object from the clipboard + + object_id -- dbus path as returned from add_object + """ self._dbus_service.delete_object(dbus.ObjectPath(object_id)) def set_object_percent(self, object_id, percent): + """Set the "percentage" for the given clipboard object + + object_id -- dbus path as returned from add_object + percentage -- numeric value from 0 to 100 inclusive + + Object percentages which are set to 100% trigger "file-completed" + operations, see the backend ClipboardService's + _handle_file_completed method for details. + + returns None + """ self._dbus_service.set_object_percent(dbus.ObjectPath(object_id), percent) def get_object(self, object_id): + """Retrieve the clipboard object structure for given object + + object_id -- dbus path as returned from add_object + + Retrieves the metadata description of a given object, but + *not* the data for the object. Use get_object_data passing + one of the values in the FORMATS_KEY value in order to + retrieve the data. + + returns dictionary with + NAME_KEY: str, + PERCENT_KEY: number, + ICON_KEY: str, + PREVIEW_KEY: XXX what is it?, + ACTIVITY_KEY: source activity id, + FORMATS_KEY: list of XXX what is it? + """ return self._dbus_service.get_object(dbus.ObjectPath(object_id),) def get_object_data(self, object_id, formatType): + """Retrieve object's data in the given formatType + + object_id -- dbus path as returned from add_object + formatType -- format specifier XXX of what description + + returns data as a string + """ return self._dbus_service.get_object_data(dbus.ObjectPath(object_id), formatType, byte_arrays=True) _clipboard_service = None def get_instance(): + """Retrieve this process's interface to the clipboard service""" global _clipboard_service if not _clipboard_service: _clipboard_service = ClipboardService() diff --git a/sugar/presence/presenceservice.py b/sugar/presence/presenceservice.py index 604439b..9b7bd3c 100644 --- a/sugar/presence/presenceservice.py +++ b/sugar/presence/presenceservice.py @@ -16,6 +16,7 @@ # Boston, MA 02111-1307, USA. import dbus, dbus.glib, gobject +import logging import buddy, activity @@ -59,7 +60,10 @@ class PresenceService(gobject.GObject): 'activity-appeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([gobject.TYPE_PYOBJECT])), 'activity-disappeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, - ([gobject.TYPE_PYOBJECT])) + ([gobject.TYPE_PYOBJECT])), + 'activity-shared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT, + gobject.TYPE_PYOBJECT])) } _PS_BUDDY_OP = DBUS_PATH + "/Buddies/" @@ -178,12 +182,20 @@ class PresenceService(gobject.GObject): return None return self._new_object(owner_op) + def _share_activity_cb(self, activity, op): + self.emit("activity-shared", True, self._new_object(op), None) + + def _share_activity_error_cb(self, activity, err): + logging.debug("Error sharing activity %s: %s" % (activity.get_id(), err)) + self.emit("activity-shared", False, None, err) + def share_activity(self, activity, properties={}): actid = activity.get_id() atype = activity.get_service_name() name = activity.props.title - serv_op = self._ps.ShareActivity(actid, atype, name, properties) - return self._new_object(serv_op) + self._ps.ShareActivity(actid, atype, name, properties, + reply_handler=lambda *args: self._share_activity_cb(activity, *args), + error_handler=lambda *args: self._share_activity_error_cb(activity, *args)) class _MockPresenceService(gobject.GObject): -- cgit v0.9.1