diff options
author | Tomeu Vizoso <tomeu@sugarlabs.org> | 2010-03-23 11:34:25 (GMT) |
---|---|---|
committer | Tomeu Vizoso <tomeu.vizoso@collabora.co.uk> | 2010-08-20 13:02:26 (GMT) |
commit | 40e5531bc210efca8dff0af388b8cbbbc78446ea (patch) | |
tree | 40f641efb774585f8634188d977f61df68fc253a | |
parent | bd685374977856d9a78b6cfe8fcb190fcadc5ef5 (diff) |
First implementation of invitations from non-Sugar clients
-rw-r--r-- | src/jarabe/frame/activitiestray.py | 227 | ||||
-rw-r--r-- | src/jarabe/model/invites.py | 158 | ||||
-rw-r--r-- | src/jarabe/model/owner.py | 39 | ||||
-rw-r--r-- | src/jarabe/model/telepathyclient.py | 97 |
4 files changed, 259 insertions, 262 deletions
diff --git a/src/jarabe/frame/activitiestray.py b/src/jarabe/frame/activitiestray.py index f594342..473036c 100644 --- a/src/jarabe/frame/activitiestray.py +++ b/src/jarabe/frame/activitiestray.py @@ -41,7 +41,7 @@ from sugar import env from jarabe.model import shell from jarabe.model import neighborhood -from jarabe.model import owner +from jarabe.model import invites from jarabe.model import bundleregistry from jarabe.model import filetransfer from jarabe.view.palettes import JournalPalette, CurrentActivityPalette @@ -49,7 +49,6 @@ from jarabe.view.pulsingicon import PulsingIcon from jarabe.view import launcher from jarabe.frame.frameinvoker import FrameWidgetInvoker from jarabe.frame.notification import NotificationIcon -from jarabe.journal import misc import jarabe.frame @@ -102,21 +101,54 @@ class ActivityButton(RadioToolButton): self._icon.props.pulsing = False -class BaseInviteButton(ToolButton): + +class InviteButton(ToolButton): + """Invite to shared activity""" def __init__(self, invite): ToolButton.__init__(self) + self._invite = invite + self.connect('clicked', self.__clicked_cb) + self.connect('destroy', self.__destroy_cb) + + bundle_registry = bundleregistry.get_registry() + bundle = bundle_registry.get_bundle(invite.get_bundle_id()) + self._icon = Icon() + logging.info('KILL_PS get the inviter''s colors') + #self._icon.props.xo_color = activity_model.get_color() + if bundle is not None: + self._icon.props.file = bundle.get_icon() + else: + self._icon.props.icon_name = 'image-missing' self.set_icon_widget(self._icon) self._icon.show() - self.connect('clicked', self.__clicked_cb) - self.connect('destroy', self.__destroy_cb) + palette = InvitePalette(invite) + palette.props.invoker = FrameWidgetInvoker(self) + palette.set_group_id('frame') + self.set_palette(palette) + self._notif_icon = NotificationIcon() self._notif_icon.connect('button-release-event', self.__button_release_event_cb) + logging.info('KILL_PS get the inviter''s colors') + #self._notif_icon.props.xo_color = activity_model.get_color() + if bundle is not None: + self._notif_icon.props.icon_filename = bundle.get_icon() + else: + self._notif_icon.props.icon_name = 'image-missing' + + palette = InvitePalette(invite) + palette.props.invoker = WidgetInvoker(self._notif_icon) + palette.set_group_id('frame') + self._notif_icon.palette = palette + + frame = jarabe.frame.get_view() + frame.add_notification(self._notif_icon, gtk.CORNER_TOP_LEFT) + def __button_release_event_cb(self, icon, event): self.emit('clicked') @@ -127,106 +159,38 @@ class BaseInviteButton(ToolButton): self._notif_icon = None self._launch() - def _launch(self): - """Launch the target of the invite""" - raise NotImplementedError - def __destroy_cb(self, button): frame = jarabe.frame.get_view() frame.remove_notification(self._notif_icon) -class ActivityInviteButton(BaseInviteButton): - """Invite to shared activity""" - def __init__(self, invite): - BaseInviteButton.__init__(self, invite) - mesh = neighborhood.get_model() - activity_model = mesh.get_activity(invite.get_activity_id()) - self._activity_model = activity_model - self._bundle_id = activity_model.get_bundle_id() - - self._icon.props.xo_color = activity_model.get_color() - if activity_model.get_icon_name(): - self._icon.props.file = activity_model.get_icon_name() - else: - self._icon.props.icon_name = 'image-missing' - - palette = ActivityInvitePalette(invite) - palette.props.invoker = FrameWidgetInvoker(self) - palette.set_group_id('frame') - self.set_palette(palette) - - self._notif_icon.props.xo_color = activity_model.get_color() - if activity_model.get_icon_name(): - icon_name = activity_model.get_icon_name() - self._notif_icon.props.icon_filename = icon_name - else: - self._notif_icon.props.icon_name = 'image-missing' - - palette = ActivityInvitePalette(invite) - palette.props.invoker = WidgetInvoker(self._notif_icon) - palette.set_group_id('frame') - self._notif_icon.palette = palette - - frame = jarabe.frame.get_view() - frame.add_notification(self._notif_icon, - gtk.CORNER_TOP_LEFT) - def _launch(self): """Join the activity in the invite.""" - registry = bundleregistry.get_registry() - bundle = registry.get_bundle(self._bundle_id) - - misc.launch(bundle, color=self._activity_model.get_color()) - -class PrivateInviteButton(BaseInviteButton): - """Invite to a private one to one channel""" - def __init__(self, invite): - BaseInviteButton.__init__(self, invite) - self._private_channel = invite.get_private_channel() - self._bundle_id = invite.get_bundle_id() - client = gconf.client_get_default() - color = XoColor(client.get_string('/desktop/sugar/user/color')) + shell_model = shell.get_model() + activity = shell_model.get_activity_by_id(self._activity_model.get_id()) + if activity: + activity.get_window().activate(gtk.get_current_event_time()) + return - self._icon.props.xo_color = color registry = bundleregistry.get_registry() - self._bundle = registry.get_bundle(self._bundle_id) - - if self._bundle: - self._icon.props.file = self._bundle.get_icon() - else: - self._icon.props.icon_name = 'image-missing' - - palette = PrivateInvitePalette(invite) - palette.props.invoker = FrameWidgetInvoker(self) - palette.set_group_id('frame') - self.set_palette(palette) - - self._notif_icon.props.xo_color = color - - if self._bundle: - self._notif_icon.props.icon_filename = self._bundle.get_icon() - else: - self._notif_icon.props.icon_name = 'image-missing' + bundle = registry.get_bundle(self._bundle_id) - palette = PrivateInvitePalette(invite) - palette.props.invoker = WidgetInvoker(self._notif_icon) - palette.set_group_id('frame') - self._notif_icon.palette = palette + launcher.add_launcher(self._activity_model.get_id(), + bundle.get_icon(), + self._activity_model.get_color()) - frame = jarabe.frame.get_view() - frame.add_notification(self._notif_icon, - gtk.CORNER_TOP_LEFT) + handle = ActivityHandle(self._activity_model.get_id()) + activityfactory.create(bundle, handle) - def _launch(self): - """Start the activity with private channel.""" - misc.launch(self._bundle, uri=self._private_channel) -class BaseInvitePalette(Palette): +class InvitePalette(Palette): """Palette for frame or notification icon for invites.""" - def __init__(self): + + def __init__(self, invite): Palette.__init__(self, '') + self._invite = invite + menu_item = MenuItem(_('Join'), icon_name='dialog-ok') menu_item.connect('activate', self.__join_activate_cb) self.menu.append(menu_item) @@ -237,73 +201,24 @@ class BaseInvitePalette(Palette): self.menu.append(menu_item) menu_item.show() - def __join_activate_cb(self, menu_item): - self._join() - - def __decline_activate_cb(self, menu_item): - self._decline() - - def _join(self): - raise NotImplementedError - - def _decline(self): - raise NotImplementedError - - -class ActivityInvitePalette(BaseInvitePalette): - """Palette for shared activity invites.""" - - def __init__(self, invite): - BaseInvitePalette.__init__(self) - - mesh = neighborhood.get_model() - activity_model = mesh.get_activity(invite.get_activity_id()) - self._activity_model = activity_model - self._bundle_id = activity_model.get_bundle_id() + bundle_id = invite.get_bundle_id() registry = bundleregistry.get_registry() - self._bundle = registry.get_bundle(self._bundle_id) + self._bundle = registry.get_bundle(bundle_id) if self._bundle: self.set_primary_text(self._bundle.get_name()) else: - self.set_primary_text(self._bundle_id) + self.set_primary_text(bundle_id) - def _join(self): - misc.launch(self._bundle, activity_id=self._activity_model.get_id()) + def __join_activate_cb(self, menu_item): + self._invite.join() - def _decline(self): - invites = owner.get_model().get_invites() + def __decline_activate_cb(self, menu_item): + invites = invites.get_instance() activity_id = self._activity_model.get_id() invites.remove_activity(activity_id) -class PrivateInvitePalette(BaseInvitePalette): - """Palette for private channel invites.""" - - def __init__(self, invite): - BaseInvitePalette.__init__(self) - - self._private_channel = invite.get_private_channel() - self._bundle_id = invite.get_bundle_id() - - registry = bundleregistry.get_registry() - self._bundle = registry.get_bundle(self._bundle_id) - if self._bundle: - self.set_primary_text(self._bundle.get_name()) - else: - self.set_primary_text(self._bundle_id) - - def _join(self): - misc.launch(self._bundle, uri=self._private_channel) - - invites = owner.get_model().get_invites() - invites.remove_private_channel(self._private_channel) - - def _decline(self): - invites = owner.get_model().get_invites() - invites.remove_private_channel(self._private_channel) - - class ActivitiesTray(HTray): def __init__(self): HTray.__init__(self) @@ -320,7 +235,7 @@ class ActivitiesTray(HTray): self._home_model.connect('tabbing-activity-changed', self.__tabbing_activity_changed_cb) - self._invites = owner.get_model().get_invites() + self._invites = invites.get_instance() for invite in self._invites: self._add_invite(invite) self._invites.connect('invite-added', self.__invite_added_cb) @@ -396,20 +311,12 @@ class ActivitiesTray(HTray): self._remove_invite(invite) def _add_invite(self, invite): - """Add an invite (SugarInvite or PrivateInvite)""" - item = None - if hasattr(invite, 'get_activity_id'): - mesh = neighborhood.get_model() - activity_model = mesh.get_activity(invite.get_activity_id()) - if activity_model is not None: - item = ActivityInviteButton(invite) - else: - item = PrivateInviteButton(invite) - if item is not None: - item.connect('clicked', self.__invite_clicked_cb, invite) - self.add_item(item) - item.show() - self._invite_to_item[invite] = item + """Add an invite""" + item = InviteButton(invite) + item.connect('clicked', self.__invite_clicked_cb, invite) + self.add_item(item) + item.show() + self._invite_to_item[invite] = item def _remove_invite(self, invite): self.remove_item(self._invite_to_item[invite]) diff --git a/src/jarabe/model/invites.py b/src/jarabe/model/invites.py index f4c7d71..abe6d39 100644 --- a/src/jarabe/model/invites.py +++ b/src/jarabe/model/invites.py @@ -17,50 +17,43 @@ import logging import gobject -from sugar.presence import presenceservice +import dbus +from telepathy.interfaces import CHANNEL, \ + CHANNEL_DISPATCHER, \ + CHANNEL_DISPATCH_OPERATION, \ + CHANNEL_TYPE_CONTACT_LIST, \ + CHANNEL_TYPE_DBUS_TUBE, \ + CHANNEL_TYPE_STREAMED_MEDIA, \ + CHANNEL_TYPE_STREAM_TUBE, \ + CHANNEL_TYPE_TEXT, \ + CLIENT +from jarabe.model import telepathyclient -class BaseInvite: - """Invitation to shared activity or private 1-1 Telepathy channel""" - def __init__(self, bundle_id): - """init for BaseInvite. - bundle_id: string, e.g. 'org.laptop.Chat' - """ - self._bundle_id = bundle_id - - def get_bundle_id(self): - return self._bundle_id - - -class ActivityInvite(BaseInvite): +class ActivityInvite(object): """Invitation to a shared activity.""" - def __init__(self, bundle_id, activity_id): - BaseInvite.__init__(self, bundle_id) - self._activity_id = activity_id - - def get_activity_id(self): - return self._activity_id + def __init__(self, dispatch_operation_path, channel, handler): + self._dispatch_operation_path = dispatch_operation_path + self._channel = channel + self._handler = handler + def get_bundle_id(self): + return self._handler[len(CLIENT + '.'):] -class PrivateInvite(BaseInvite): - """Invitation to a private 1-1 Telepathy channel. - - This includes text chat or streaming media. - """ - def __init__(self, bundle_id, private_channel): - """init for PrivateInvite. - - bundle_id: string, e.g. 'org.laptop.Chat' - private_channel: string containing simplejson dump of Telepathy - bus, connection and channel - """ - BaseInvite.__init__(self, bundle_id) - self._private_channel = private_channel + def join(self): + bus = dbus.Bus() + obj = bus.get_object(CHANNEL_DISPATCHER, self._dispatch_operation_path) + dispatch_operation = dbus.Interface(obj, CHANNEL_DISPATCH_OPERATION) + dispatch_operation.HandleWith(self._handler, + reply_handler=self.__handle_with_reply_cb, + error_handler=self.__handle_with_reply_cb) - def get_private_channel(self): - """Telepathy channel info from private invitation""" - return self._private_channel + def __handle_with_reply_cb(self, error=None): + if error is not None: + logging.error('__handle_with_reply_cb %r', error) + else: + logging.debug('__handle_with_reply_cb') class Invites(gobject.GObject): @@ -74,48 +67,78 @@ class Invites(gobject.GObject): def __init__(self): gobject.GObject.__init__(self) - self._dict = {} + self._dispatch_operations = {} logging.info('KILL_PS listen for when the owner joins an activity') #ps = presenceservice.get_instance() #owner = ps.get_owner() #owner.connect('joined-activity', self._owner_joined_cb) - def add_invite(self, bundle_id, activity_id): - if activity_id in self._dict: - # there is no point to add more than one time - # an invite for the same activity + client_handler = telepathyclient.get_instance() + client_handler.got_dispatch_operation.connect( + self.__got_dispatch_operation_cb) + + def __got_dispatch_operation_cb(self, **kwargs): + logging.debug('__got_dispatch_operation_cb') + dispatch_operation_path = kwargs['dispatch_operation_path'] + channel, channel_properties = kwargs['channels'][0] + + channel_type = channel_properties[CHANNEL + '.ChannelType'] + if channel_type == CHANNEL_TYPE_CONTACT_LIST: + handler = None + self._handle_with(dispatch_operation_path, CLIENT + '.Sugar') + elif channel_type == CHANNEL_TYPE_TEXT: + handler = CLIENT + '.org.laptop.Chat' + elif channel_type == CHANNEL_TYPE_STREAMED_MEDIA: + handler = CLIENT + '.org.laptop.VideoChat' + elif channel_type == CHANNEL_TYPE_DBUS_TUBE: + handler = channel_properties[CHANNEL_TYPE_DBUS_TUBE + '.ServiceName'] + elif channel_type == CHANNEL_TYPE_STREAM_TUBE: + handler = channel_properties[CHANNEL_TYPE_STREAM_TUBE + '.Service'] + else: + handler = None + self._handle_with(dispatch_operation_path, '') + + if handler is not None: + self._add_invite(dispatch_operation_path, channel, handler) + + def _handle_with(self, dispatch_operation_path, handler): + logging.debug('_handle_with %r %r', dispatch_operation_path, handler) + bus = dbus.Bus() + obj = bus.get_object(CHANNEL_DISPATCHER, dispatch_operation_path) + dispatch_operation = dbus.Interface(obj, CHANNEL_DISPATCH_OPERATION) + dispatch_operation.HandleWith(handler, + reply_handler=self.__handle_with_reply_cb, + error_handler=self.__handle_with_reply_cb) + + def __handle_with_reply_cb(self, error=None): + if error is not None: + logging.error('__handle_with_reply_cb %r', error) + else: + logging.debug('__handle_with_reply_cb') + + def _add_invite(self, dispatch_operation_path, channel, handler): + logging.debug('_add_invite %r %r %r', dispatch_operation_path, channel, handler) + if dispatch_operation_path in self._dispatch_operations: + # there is no point to have more than one invite for the same + # dispatch operation return - invite = ActivityInvite(bundle_id, activity_id) - self._dict[activity_id] = invite + invite = ActivityInvite(dispatch_operation_path, channel, handler) + self._dispatch_operations[dispatch_operation_path] = invite self.emit('invite-added', invite) - def add_private_invite(self, private_channel, bundle_id): - if private_channel in self._dict: - # there is no point to add more than one invite for the - # same incoming connection - return - - invite = PrivateInvite(bundle_id, private_channel) - self._dict[private_channel] = invite - self.emit('invite-added', invite) - - def remove_invite(self, invite): - del self._dict[invite.get_activity_id()] - self.emit('invite-removed', invite) - - def remove_private_invite(self, invite): - del self._dict[invite.get_private_channel()] + def _remove_invite(self, invite): + del self._dispatch_operations[invite.get_activity_id()] self.emit('invite-removed', invite) def remove_activity(self, activity_id): - invite = self._dict.get(activity_id) + invite = self._dispatch_operations.get(activity_id) if invite is not None: self.remove_invite(invite) def remove_private_channel(self, private_channel): - invite = self._dict.get(private_channel) + invite = self._dispatch_operations.get(private_channel) if invite is not None: self.remove_private_invite(invite) @@ -123,4 +146,13 @@ class Invites(gobject.GObject): self.remove_activity(activity.props.id) def __iter__(self): - return self._dict.values().__iter__() + return self._dispatch_operations.values().__iter__() + + +_instance = None + +def get_instance(): + global _instance + if not _instance: + _instance = Invites() + return _instance diff --git a/src/jarabe/model/owner.py b/src/jarabe/model/owner.py index 17996e6..7d7e1f5 100644 --- a/src/jarabe/model/owner.py +++ b/src/jarabe/model/owner.py @@ -20,12 +20,8 @@ import os import gconf import simplejson -from telepathy.interfaces import CHANNEL_TYPE_TEXT - from sugar import env -from sugar.presence import presenceservice from sugar import util -from jarabe.model.invites import Invites class Owner(gobject.GObject): """Class representing the owner of this machine/instance. This class @@ -66,44 +62,9 @@ class Owner(gobject.GObject): digest = hashlib.md5(self._icon).digest() self._icon_hash = util.printable_hash(digest) - self._pservice = presenceservice.get_instance() - self._pservice.connect('activity-invitation', - self._activity_invitation_cb) - self._pservice.connect('private-invitation', - self._private_invitation_cb) - self._pservice.connect('activity-disappeared', - self._activity_disappeared_cb) - - self._invites = Invites() - - def get_invites(self): - return self._invites - def get_nick(self): return self._nick - def _activity_invitation_cb(self, pservice, activity, buddy, message): - self._invites.add_invite(activity.props.type, - activity.props.id) - - def _private_invitation_cb(self, pservice, bus_name, connection, - channel, channel_type): - """Handle a private-invitation from Presence Service. - - This is a connection by a non-Sugar XMPP client, so - launch Chat or VideoChat with the Telepathy connection and - channel. - """ - if channel_type == CHANNEL_TYPE_TEXT: - bundle_id = 'org.laptop.Chat' - else: - bundle_id = 'org.laptop.VideoChat' - tp_channel = simplejson.dumps([bus_name, connection, channel]) - self._invites.add_private_invite(tp_channel, bundle_id) - - def _activity_disappeared_cb(self, pservice, activity): - self._invites.remove_activity(activity.props.id) - _model = None def get_model(): diff --git a/src/jarabe/model/telepathyclient.py b/src/jarabe/model/telepathyclient.py new file mode 100644 index 0000000..5a483d8 --- /dev/null +++ b/src/jarabe/model/telepathyclient.py @@ -0,0 +1,97 @@ +# Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> +# +# 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 logging + +import dbus +from dbus import PROPERTIES_IFACE +from telepathy.interfaces import CLIENT, \ + CLIENT_APPROVER, \ + CLIENT_HANDLER, \ + CLIENT_INTERFACE_REQUESTS +from telepathy.server import DBusProperties + +from sugar import dispatch + +SUGAR_CLIENT_SERVICE = 'org.freedesktop.Telepathy.Client.Sugar' +SUGAR_CLIENT_PATH = '/org/freedesktop/Telepathy/Client/Sugar' + +class TelepathyClient(dbus.service.Object, DBusProperties): + def __init__(self): + self._interfaces = set([CLIENT, CLIENT_HANDLER, + CLIENT_INTERFACE_REQUESTS, PROPERTIES_IFACE, + CLIENT_APPROVER]) + + bus = dbus.Bus() + bus_name = dbus.service.BusName(SUGAR_CLIENT_SERVICE, bus=bus) + + dbus.service.Object.__init__(self, bus_name, SUGAR_CLIENT_PATH) + DBusProperties.__init__(self) + + self._implement_property_get(CLIENT, { + 'Interfaces': lambda: list(self._interfaces), + }) + self._implement_property_get(CLIENT_HANDLER, { + 'HandlerChannelFilter': self.__get_filters_cb, + }) + self._implement_property_get(CLIENT_APPROVER, { + 'ApproverChannelFilter': self.__get_filters_cb, + }) + + self.got_channel = dispatch.Signal() + self.got_dispatch_operation = dispatch.Signal() + + def __get_filters_cb(self): + logging.debug('__get_filters_cb') + filter_dict = dbus.Dictionary({}, signature='sv') + return dbus.Array([filter_dict], signature='a{sv}') + + @dbus.service.method(dbus_interface=CLIENT_HANDLER, + in_signature='ooa(oa{sv})aota{sv}', out_signature='') + def HandleChannels(self, account, connection, channels, requests_satisfied, + user_action_time, handler_info): + logging.debug('HandleChannels\n%r\n%r\n%r\n%r\n%r\n%r\n', account, connection, + channels, requests_satisfied, user_action_time, handler_info) + for channel in channels: + self.got_channel.send(self, account=account, + connection=connection, channel=channel) + + @dbus.service.method(dbus_interface=CLIENT_INTERFACE_REQUESTS, + in_signature='oa{sv}', out_signature='') + def AddRequest(self, request, properties): + logging.debug('AddRequest\n%r\n%r', request, properties) + + @dbus.service.method(dbus_interface=CLIENT_APPROVER, + in_signature='a(oa{sv})oa{sv}', out_signature='', + async_callbacks=('success_cb', 'error_cb_')) + def AddDispatchOperation(self, channels, dispatch_operation_path, + properties, success_cb, error_cb_): + success_cb() + + logging.debug('AddDispatchOperation\n%r\n%r\n%r', channels, dispatch_operation_path, properties) + + self.got_dispatch_operation.send(self, channels=channels, + dispatch_operation_path=dispatch_operation_path, + properties=properties) + + +_instance = None + +def get_instance(): + global _instance + if not _instance: + _instance = TelepathyClient() + return _instance |