# Copyright 2007-2008 One Laptop Per Child # # 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 """HelloMesh Activity: A case study for collaboration using Tubes.""" import hippo import gtk import logging import telepathy from dbus.service import method, signal from dbus.gobject_service import ExportedGObject from sugar.activity.activity import Activity, ActivityToolbox from sugar.graphics.alert import NotifyAlert from sugar.presence import presenceservice from sugar.presence.tubeconn import TubeConnection SERVICE = "org.laptop.HelloMesh" IFACE = SERVICE PATH = "/org/laptop/HelloMesh" class HelloMeshActivity(Activity): """HelloMesh Activity as specified in activity.info""" def __init__(self, handle): """Set up the HelloMesh activity.""" Activity.__init__(self, handle) self.set_title('HelloMesh Activity') self._logger = logging.getLogger('hellomesh-activity') # top toolbar with share and close buttons: toolbox = ActivityToolbox(self) self.set_toolbox(toolbox) toolbox.show() # Hippo Canvas: vbox = hippo.CanvasBox(spacing=4, orientation=hippo.ORIENTATION_VERTICAL) self.entry = gtk.Entry() self.entry.set_sensitive(False) self.entry.connect('activate', self.entry_activate_cb) vbox.append(hippo.CanvasWidget(widget=self.entry)) canvas = hippo.Canvas() canvas.set_root(vbox) self.set_canvas(canvas) self.show_all() self.hellotube = None # Shared session self.initiating = False # get the Presence Service self.pservice = presenceservice.get_instance() # Buddy object for you owner = self.pservice.get_owner() self.owner = owner self.connect('shared', self._shared_cb) self.connect('joined', self._joined_cb) def entry_activate_cb(self, entry): """Handle the event when Enter is pressed in the Entry.""" text = entry.props.text if self.hellotube is not None: self.hellotube.SendText(text) def entry_text_update_cb(self, text): """Update Entry text when text received from others.""" self.entry.props.text = text def _alert(self, title, text=None): alert = NotifyAlert(timeout=5) alert.props.title = title alert.props.msg = text self.add_alert(alert) alert.connect('response', self._alert_cancel_cb) alert.show() def _alert_cancel_cb(self, alert, response_id): self.remove_alert(alert) def _shared_cb(self, activity): self._logger.debug('My activity was shared') self._alert('Shared', 'The activity is shared') self.initiating = True self._sharing_setup() self._logger.debug('This is my activity: making a tube...') id = self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].OfferDBusTube( SERVICE, {}) def _sharing_setup(self): if self.shared_activity is None: self._logger.error('Failed to share or join activity') return self.conn = self.shared_activity.telepathy_conn self.tubes_chan = self.shared_activity.telepathy_tubes_chan self.text_chan = self.shared_activity.telepathy_text_chan self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].connect_to_signal( 'NewTube', self._new_tube_cb) self.shared_activity.connect('buddy-joined', self._buddy_joined_cb) self.shared_activity.connect('buddy-left', self._buddy_left_cb) self.entry.set_sensitive(True) self.entry.grab_focus() # Optional - included for example: # Find out who's already in the shared activity: for buddy in self.shared_activity.get_joined_buddies(): self._logger.debug('Buddy %s is already in the activity', buddy.props.nick) def _list_tubes_reply_cb(self, tubes): for tube_info in tubes: self._new_tube_cb(*tube_info) def _list_tubes_error_cb(self, e): self._logger.error('ListTubes() failed: %s', e) def _joined_cb(self, activity): if not self.shared_activity: return self._logger.debug('Joined an existing shared activity') self._alert('Joined', 'Joined a shared activity') self.initiating = False self._sharing_setup() self._logger.debug('This is not my activity: waiting for a tube...') self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].ListTubes( reply_handler=self._list_tubes_reply_cb, error_handler=self._list_tubes_error_cb) def _new_tube_cb(self, id, initiator, type, service, params, state): self._logger.debug('New tube: ID=%d initator=%d type=%d service=%s ' 'params=%r state=%d', id, initiator, type, service, params, state) if (type == telepathy.TUBE_TYPE_DBUS and service == SERVICE): if state == telepathy.TUBE_STATE_LOCAL_PENDING: self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].AcceptDBusTube(id) tube_conn = TubeConnection(self.conn, self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES], id, group_iface=self.text_chan[telepathy.CHANNEL_INTERFACE_GROUP]) self.hellotube = TextSync(tube_conn, self.initiating, self.entry_text_update_cb, self._alert, self._get_buddy) def _buddy_joined_cb (self, activity, buddy): """Called when a buddy joins the shared activity. This doesn't do much here as HelloMesh doesn't have much functionality. It's up to you do do interesting things with the Buddy... """ self._logger.debug('Buddy %s joined', buddy.props.nick) self._alert('Buddy joined', '%s joined' % buddy.props.nick) def _buddy_left_cb (self, activity, buddy): """Called when a buddy leaves the shared activity. This doesn't do much here as HelloMesh doesn't have much functionality. It's up to you do do interesting things with the Buddy... """ self._logger.debug('Buddy %s left', buddy.props.nick) self._alert('Buddy left', '%s left' % buddy.props.nick) def _get_buddy(self, cs_handle): """Get a Buddy from a channel specific handle.""" self._logger.debug('Trying to find owner of handle %u...', cs_handle) group = self.text_chan[telepathy.CHANNEL_INTERFACE_GROUP] my_csh = group.GetSelfHandle() self._logger.debug('My handle in that group is %u', my_csh) if my_csh == cs_handle: handle = self.conn.GetSelfHandle() self._logger.debug('CS handle %u belongs to me, %u', cs_handle, handle) elif group.GetGroupFlags() & telepathy.CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES: handle = group.GetHandleOwners([cs_handle])[0] self._logger.debug('CS handle %u belongs to %u', cs_handle, handle) else: handle = cs_handle self._logger.debug('non-CS handle %u belongs to itself', handle) # XXX: deal with failure to get the handle owner assert handle != 0 return self.pservice.get_buddy_by_telepathy_handle( self.conn.service_name, self.conn.object_path, handle) class TextSync(ExportedGObject): """The bit that talks over the TUBES!!!""" def __init__(self, tube, is_initiator, text_received_cb, alert, get_buddy): super(TextSync, self).__init__(tube, PATH) self._logger = logging.getLogger('hellomesh-activity.TextSync') self.tube = tube self.is_initiator = is_initiator self.text_received_cb = text_received_cb self._alert = alert self.entered = False # Have we set up the tube? self.text = '' # State that gets sent or received self._get_buddy = get_buddy # Converts handle to Buddy object self.tube.watch_participants(self.participant_change_cb) def participant_change_cb(self, added, removed): self._logger.debug('Tube: Added participants: %r', added) self._logger.debug('Tube: Removed participants: %r', removed) for handle, bus_name in added: buddy = self._get_buddy(handle) if buddy is not None: self._logger.debug('Tube: Handle %u (Buddy %s) was added', handle, buddy.props.nick) for handle in removed: buddy = self._get_buddy(handle) if buddy is not None: self._logger.debug('Buddy %s was removed' % buddy.props.nick) if not self.entered: if self.is_initiator: self._logger.debug("I'm initiating the tube, will " "watch for hellos.") self.add_hello_handler() else: self._logger.debug('Hello, everyone! What did I miss?') self.Hello() self.entered = True @signal(dbus_interface=IFACE, signature='') def Hello(self): """Say Hello to whoever else is in the tube.""" self._logger.debug('I said Hello.') @method(dbus_interface=IFACE, in_signature='s', out_signature='') def World(self, text): """To be called on the incoming XO after they Hello.""" if not self.text: self._logger.debug('Somebody called World and sent me %s', text) self._alert('World', 'Received %s' % text) self.text = text self.text_received_cb(text) # now I can World others self.add_hello_handler() else: self._logger.debug("I've already been welcomed, doing nothing") def add_hello_handler(self): self._logger.debug('Adding hello handler.') self.tube.add_signal_receiver(self.hello_cb, 'Hello', IFACE, path=PATH, sender_keyword='sender') self.tube.add_signal_receiver(self.sendtext_cb, 'SendText', IFACE, path=PATH, sender_keyword='sender') def hello_cb(self, sender=None): """Somebody Helloed me. World them.""" if sender == self.tube.get_unique_name(): # sender is my bus name, so ignore my own signal return self._logger.debug('Newcomer %s has joined', sender) self._logger.debug('Welcoming newcomer and sending them the game state') self.tube.get_object(sender, PATH).World(self.text, dbus_interface=IFACE) def sendtext_cb(self, text, sender=None): """Handler for somebody sending SendText""" if sender == self.tube.get_unique_name(): # sender is my bus name, so ignore my own signal return self._logger.debug('%s sent text %s', sender, text) self._alert('sendtext_cb', 'Received %s' % text) self.text = text self.text_received_cb(text) @signal(dbus_interface=IFACE, signature='s') def SendText(self, text): """Send some text to all participants.""" self.text = text self._logger.debug('Sent text: %s', text) self._alert('SendText', 'Sent %s' % text)