Merge branch 'master' of git+ssh://dev.laptop.org/git/sugar

This commit is contained in:
Marco Pesenti Gritti 2007-04-14 10:00:00 +02:00
commit 845575c708
9 changed files with 194 additions and 15 deletions

View File

@ -18,6 +18,7 @@
import gobject import gobject
import dbus, dbus.service import dbus, dbus.service
from sugar import util from sugar import util
import logging
from telepathy.interfaces import (CHANNEL_INTERFACE) from telepathy.interfaces import (CHANNEL_INTERFACE)
@ -228,22 +229,27 @@ class Activity(DBusGObject):
# Not for us # Not for us
return return
(sigid, async_cb, async_err_cb) = userdata (sigid, owner, async_cb, async_err_cb) = userdata
self._tp.disconnect(sigid) self._tp.disconnect(sigid)
if exc: if exc:
logging.debug("Share of activity %s failed: %s" % (self._id, exc))
async_err_cb(exc) async_err_cb(exc)
else: else:
self._handle_share_join(tp, text_channel) self._handle_share_join(tp, text_channel)
self.send_properties() self.send_properties()
owner.add_activity(self)
async_cb(dbus.ObjectPath(self._object_path)) 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: if self._joined:
async_err_cb(RuntimeError("Already shared activity %s" % self.props.id)) async_err_cb(RuntimeError("Already shared activity %s" % self.props.id))
return return
sigid = self._tp.connect('activity-shared', self._shared_cb) 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): def _joined_cb(self, tp, activity_id, text_channel, exc, userdata):
if activity_id != self.props.id: if activity_id != self.props.id:

View File

@ -169,7 +169,10 @@ class PresenceService(dbus.service.Object):
del self._activities[activity.props.id] del self._activities[activity.props.id]
def _buddy_activities_changed(self, tp, contact_handle, activities): 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] buddies = self._handles_buddies[tp]
buddy = buddies.get(contact_handle) buddy = buddies.get(contact_handle)
@ -306,7 +309,7 @@ class PresenceService(dbus.service.Object):
id=actid, type=atype, name=name, color=color, local=True) id=actid, type=atype, name=name, color=color, local=True)
activity.connect("validity-changed", self._activity_validity_changed_cb) activity.connect("validity-changed", self._activity_validity_changed_cb)
self._activities[actid] = activity self._activities[actid] = activity
activity._share(callbacks) activity._share(callbacks, self._owner)
# local activities are valid at creation by definition, but we can't # local activities are valid at creation by definition, but we can't
# connect to the activity's validity-changed signal until its already # connect to the activity's validity-changed signal until its already

View File

@ -66,8 +66,7 @@ class ActivityHost:
return self._activity.execute(command, dbus.Array(args)) return self._activity.execute(command, dbus.Array(args))
def share(self): def share(self):
self._activity.share() self._activity.share(ignore_reply=True)
self._chat_widget.share()
def invite(self, buddy): def invite(self, buddy):
pass pass

View File

@ -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.
"""

View File

@ -62,6 +62,7 @@ class Activity(Window, gtk.Container):
self._activity_id = handle.activity_id self._activity_id = handle.activity_id
self._pservice = presenceservice.get_instance() self._pservice = presenceservice.get_instance()
self._service = None self._service = None
self._share_sigid = None
service = handle.get_presence_service() service = handle.get_presence_service()
if service: if service:
@ -100,11 +101,21 @@ class Activity(Window, gtk.Container):
self._service.join() self._service.join()
self.present() self.present()
def share(self): def _share_cb(self, ps, success, service, err):
"""Share the activity on the network.""" self._pservice.disconnect(self._share_sigid)
logging.debug('Share activity %s on the network.' % self.get_id()) self._share_sigid = None
self._service = self._pservice.share_activity(self) if success:
logging.debug('Share of activity %s successful.' % self.get_id())
self._service = service
self._shared = True self._shared = True
else:
logging.debug('Share of activity %s failed: %s.' % (self.get_id(), err))
def share(self):
"""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): def execute(self, command, args):
"""Execute the given command with args""" """Execute the given command with args"""

View File

@ -99,6 +99,8 @@ class ActivityFactoryService(dbus.service.Object):
handle -- sugar.activity.activityhandle.ActivityHandle handle -- sugar.activity.activityhandle.ActivityHandle
compatible dictionary providing the instance-specific compatible dictionary providing the instance-specific
values for the new instance values for the new instance
returns xid for the created instance' root window
""" """
activity_handle = activityhandle.create_from_dict(handle) activity_handle = activityhandle.create_from_dict(handle)
activity = self._constructor(activity_handle) activity = self._constructor(activity_handle)

View File

@ -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 * from sugar.browser._sugarbrowser import *

View File

@ -1,3 +1,4 @@
"""UI class to access system-level clipboard object"""
import logging import logging
import dbus import dbus
import gobject import gobject
@ -14,7 +15,16 @@ DBUS_INTERFACE = "org.laptop.Clipboard"
DBUS_PATH = "/org/laptop/Clipboard" DBUS_PATH = "/org/laptop/Clipboard"
class ClipboardService(gobject.GObject): 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__ = { __gsignals__ = {
'object-added': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, 'object-added': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([str, str])), ([str, str])),
@ -25,6 +35,11 @@ class ClipboardService(gobject.GObject):
} }
def __init__(self): 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) gobject.GObject.__init__(self)
self._dbus_service = None self._dbus_service = None
@ -44,6 +59,7 @@ class ClipboardService(gobject.GObject):
logging.debug(exception) logging.debug(exception)
def _connect_clipboard_signals(self): def _connect_clipboard_signals(self):
"""Connect dbus signals to our GObject signal generating callbacks"""
bus = dbus.SessionBus() bus = dbus.SessionBus()
if not self._connected: if not self._connected:
proxy_obj = bus.get_object(DBUS_SERVICE, DBUS_PATH) proxy_obj = bus.get_object(DBUS_SERVICE, DBUS_PATH)
@ -59,46 +75,124 @@ class ClipboardService(gobject.GObject):
bus.remove_signal_receiver(self._nameOwnerChangedHandler) bus.remove_signal_receiver(self._nameOwnerChangedHandler)
def _name_owner_changed_cb(self, name, old, new): def _name_owner_changed_cb(self, name, old, new):
"""On backend service creation, connect to the server"""
if not old and new: if not old and new:
# ClipboardService started up # ClipboardService started up
self._connect_clipboard_signals() self._connect_clipboard_signals()
def _object_added_cb(self, object_id, name): 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) self.emit('object-added', str(object_id), name)
def _object_deleted_cb(self, object_id): def _object_deleted_cb(self, object_id):
"""Emit an object-deleted GObject event when dbus event arrives"""
self.emit('object-deleted', str(object_id)) self.emit('object-deleted', str(object_id))
def _object_state_changed_cb(self, object_id, values): 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], self.emit('object-state-changed', str(object_id), values[NAME_KEY],
values[PERCENT_KEY], values[ICON_KEY], values[PREVIEW_KEY], values[PERCENT_KEY], values[ICON_KEY], values[PREVIEW_KEY],
values[ACTIVITY_KEY]) values[ACTIVITY_KEY])
def add_object(self, name): 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)) return str(self._dbus_service.add_object(name))
def add_object_format(self, object_id, formatType, data, on_disk): 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), self._dbus_service.add_object_format(dbus.ObjectPath(object_id),
formatType, formatType,
data, data,
on_disk) on_disk)
def delete_object(self, object_id): 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)) self._dbus_service.delete_object(dbus.ObjectPath(object_id))
def set_object_percent(self, object_id, percent): 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) self._dbus_service.set_object_percent(dbus.ObjectPath(object_id), percent)
def get_object(self, object_id): 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),) return self._dbus_service.get_object(dbus.ObjectPath(object_id),)
def get_object_data(self, object_id, formatType): 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), return self._dbus_service.get_object_data(dbus.ObjectPath(object_id),
formatType, formatType,
byte_arrays=True) byte_arrays=True)
_clipboard_service = None _clipboard_service = None
def get_instance(): def get_instance():
"""Retrieve this process's interface to the clipboard service"""
global _clipboard_service global _clipboard_service
if not _clipboard_service: if not _clipboard_service:
_clipboard_service = ClipboardService() _clipboard_service = ClipboardService()

View File

@ -16,6 +16,7 @@
# Boston, MA 02111-1307, USA. # Boston, MA 02111-1307, USA.
import dbus, dbus.glib, gobject import dbus, dbus.glib, gobject
import logging
import buddy, activity import buddy, activity
@ -59,7 +60,10 @@ class PresenceService(gobject.GObject):
'activity-appeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, 'activity-appeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT])), ([gobject.TYPE_PYOBJECT])),
'activity-disappeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, '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/" _PS_BUDDY_OP = DBUS_PATH + "/Buddies/"
@ -178,12 +182,20 @@ class PresenceService(gobject.GObject):
return None return None
return self._new_object(owner_op) 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={}): def share_activity(self, activity, properties={}):
actid = activity.get_id() actid = activity.get_id()
atype = activity.get_service_name() atype = activity.get_service_name()
name = activity.props.title name = activity.props.title
serv_op = self._ps.ShareActivity(actid, atype, name, properties) self._ps.ShareActivity(actid, atype, name, properties,
return self._new_object(serv_op) 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): class _MockPresenceService(gobject.GObject):