From 58b2d8fb939e3cab9eb8b587fec7c6cce6fecf1f Mon Sep 17 00:00:00 2001 From: Aleksey Lim Date: Wed, 04 Feb 2009 15:12:22 +0000 Subject: Add collab code --- (limited to 'Speak.activity') diff --git a/Speak.activity/activity.py b/Speak.activity/activity.py index ddfcfd5..ca933e4 100755 --- a/Speak.activity/activity.py +++ b/Speak.activity/activity.py @@ -59,13 +59,15 @@ import voice import face from toolbars import ChatToolbar from chat import Chat +from collab import CollabActivity +from messenger import Messenger, SERVICE CHAT_TOOLBAR = 3 -class SpeakActivity(activity.Activity): +class SpeakActivity(CollabActivity): def __init__(self, handle): - activity.Activity.__init__(self, handle) + CollabActivity.__init__(self, SERVICE, handle) bounds = self.get_allocation() # pick a voice that espeak supports @@ -450,11 +452,14 @@ class SpeakActivity(activity.Activity): def _toolbar_changed_cb(self, widget, index): if index == CHAT_TOOLBAR: - self.chat.update(self.face.status) + self.chat.me.update(self.face.status) self.notebook.set_current_page(1) else: self.notebook.set_current_page(0) + def on_tube(self, tube_conn, initiating): + self.chat.messenger = Messenger(tube_conn, initiating, self.chat) + #def on_quit(self, data=None): # self.audio.on_quit() diff --git a/Speak.activity/chat.py b/Speak.activity/chat.py index 9dae48b..fa78c91 100644 --- a/Speak.activity/chat.py +++ b/Speak.activity/chat.py @@ -24,8 +24,11 @@ import eye import glasses import mouth import face +import messenger from chatbox import ChatBox +logger = logging.getLogger('speak') + BUDDY_SIZE = min(100, min(gtk.gdk.screen_width(), gtk.gdk.screen_height() - style.LARGE_ICON_SIZE) / 5) BUDDY_XPAD = 0 @@ -42,8 +45,11 @@ ENTRY_YPAD = 7 class Chat(hippo.Canvas): def __init__(self): hippo.Canvas.__init__(self) - self._buddies = {} + self.messenger = None + self.me = None + + self._buddies = {} self.connect('motion_notify_event', self._motion_notify_cb) # buddies box @@ -62,10 +68,9 @@ class Chat(hippo.Canvas): # chat entry - self.chat = ChatBox() - self.me = self.chat.owner - - self.my_face, self_face = self._new_face(self.chat.owner, ENTRY_COLOR) + self._chat = ChatBox() + self.me, my_face_widget = self._new_face(self._chat.owner, + ENTRY_COLOR) chat_post = gtk.TextView() chat_post.modify_bg(gtk.STATE_INSENSITIVE, @@ -95,14 +100,14 @@ class Chat(hippo.Canvas): ) chat_entry.props.orientation = hippo.ORIENTATION_HORIZONTAL chat_entry.props.border_color = style.COLOR_WHITE.get_int() - chat_entry.append(self_face) + chat_entry.append(my_face_widget) chat_entry.append(chat_post_box, hippo.PACK_EXPAND) chat_box = hippo.CanvasBox( orientation = hippo.ORIENTATION_VERTICAL, background_color = style.COLOR_WHITE.get_int(), ) - chat_box.append(self.chat, hippo.PACK_EXPAND) + chat_box.append(self._chat, hippo.PACK_EXPAND) chat_box.append(chat_entry) # desk @@ -113,28 +118,24 @@ class Chat(hippo.Canvas): self.set_root(self._desk) - self._add_buddy(self.chat.owner) - - def update(self, status, buddy = None, text = None): - if not buddy or buddy == self.me: - face = self.my_face + def post(self, buddy, status, text): + i = self._buddies.get(buddy) + if i: + face = i['face'] else: - i = self._buddies.get(buddy) - if i: - face = i['face'] - else: - self._add_buddy(boddy) - face = self._buddies[buddy]['face'] + self._add_buddy(buddy) + face = self._buddies[buddy]['face'] if status: face.update(status) if text: + self._chat.add_text(buddy, text) face.say(text) def farewell(self, buddy): i = self._buddies.get(buddy) if not i: - logging.debug('farewell: cannot find boddy %s' % buddy.props.nick) + logger.debug('farewell: cannot find buddy %s' % buddy.props.nick) return self._buddies_list.remove(i['box']) @@ -173,15 +174,14 @@ class Chat(hippo.Canvas): if event.keyval == gtk.keysyms.Return: if not (event.state & gtk.gdk.CONTROL_MASK): text = widget.get_buffer().props.text - self.chat.add_text(None, text) - widget.get_buffer().props.text = '' - #if len(self._buddies) == 1: - # self._del_buddy(self.chat.owner) - #else: - # self._add_buddy(self.chat.owner) + if text: + self._chat.add_text(None, text) + widget.get_buffer().props.text = '' + self.me.say(text) + if self.messenger: + self.messenger.post(text) - self.update(None, None, text) return True return False @@ -218,7 +218,7 @@ class Chat(hippo.Canvas): return (buddy_face, outer) def _look_at(self, x, y): - self.my_face.look_at(x, y) + self.me.look_at(x, y) for i in self._buddies.values(): i['face'].look_at(x, y) diff --git a/Speak.activity/chatbox.py b/Speak.activity/chatbox.py index d098ca5..f9fb2fd 100644 --- a/Speak.activity/chatbox.py +++ b/Speak.activity/chatbox.py @@ -31,6 +31,8 @@ from sugar.graphics.style import (Color, COLOR_BLACK, COLOR_WHITE) from sugar.graphics.menuitem import MenuItem from sugar.activity.activity import get_activity_root +logger = logging.getLogger('speak') + URL_REGEXP = re.compile('((http|ftp)s?://)?' '(([-a-zA-Z0-9]+[.])+[-a-zA-Z0-9]{2,}|([0-9]{1,3}[.]){3}[0-9]{1,3})' '(:[1-9][0-9]{0,4})?(/[-a-zA-Z0-9/%~@&_+=;:,.?#]*[a-zA-Z0-9/])?') @@ -238,7 +240,7 @@ class ChatBox(hippo.CanvasScrollbars): from sugar import profile from sugar.activity.activity import show_object_in_journal from sugar.datastore import datastore - logging.debug('Create journal entry for URL: %s', url) + logger.debug('Create journal entry for URL: %s', url) jobject = datastore.create() metadata = { 'title': "%s: %s" % (_('URL from Chat'), url), @@ -300,7 +302,7 @@ class URLMenu(Palette): pass def _copy_to_clipboard_cb(self, menuitem): - logging.debug('Copy %s to clipboard', self.url) + logger.debug('Copy %s to clipboard', self.url) clipboard = gtk.clipboard_get() targets = [("text/uri-list", 0, 0), ("UTF8_STRING", 0, 1)] @@ -309,23 +311,23 @@ class URLMenu(Palette): self._clipboard_data_get_cb, self._clipboard_clear_cb, (self.url)): - logging.error('GtkClipboard.set_with_data failed!') + logger.error('GtkClipboard.set_with_data failed!') else: self.owns_clipboard = True def _clipboard_data_get_cb(self, clipboard, selection, info, data): - logging.debug('_clipboard_data_get_cb data=%s target=%s', data, + logger.debug('_clipboard_data_get_cb data=%s target=%s', data, selection.target) if selection.target in ['text/uri-list']: if not selection.set_uris([data]): - logging.debug('failed to set_uris') + logger.debug('failed to set_uris') else: - logging.debug('not uri') + logger.debug('not uri') if not selection.set_text(data): - logging.debug('failed to set_text') + logger.debug('failed to set_text') def _clipboard_clear_cb(self, clipboard, data): - logging.debug('clipboard_clear_cb') + logger.debug('clipboard_clear_cb') self.owns_clipboard = False def url_check_protocol(url): diff --git a/Speak.activity/collab.py b/Speak.activity/collab.py new file mode 100644 index 0000000..4638d25 --- /dev/null +++ b/Speak.activity/collab.py @@ -0,0 +1,95 @@ +# 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 telepathy + +from sugar.activity.activity import Activity +from sugar.presence.sugartubeconn import SugarTubeConnection + +logger = logging.getLogger('speak') + +class CollabActivity(Activity): + def __init__(self, service, *args): + Activity.__init__(self, *args) + self.service = service + + self.connect('shared', self._shared_cb) + + # Owner.props.key + if self._shared_activity: + # We are joining the activity + self.connect('joined', self._joined_cb) + if self.get_shared(): + # We've already joined + self._joined_cb() + + def on_tube(self, tube_conn, initiating): + pass + + def _shared_cb(self, activity): + logger.debug('My activity was shared') + self._initiating = True + self._sharing_setup() + + logger.debug('This is my activity: making a tube...') + id = self._tubes_chan[telepathy.CHANNEL_TYPE_TUBES].OfferDBusTube( + self.service, {}) + + def _joined_cb(self, activity): + if not self._shared_activity: + return + + logger.debug('Joined an existing shared activity') + + self._initiating = False + self._sharing_setup() + + 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 _sharing_setup(self): + if self._shared_activity is None: + 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) + + 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): + logger.error('ListTubes() failed: %s', e) + + def _new_tube_cb(self, id, initiator, type, service, params, state): + 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 == self.service): + if state == telepathy.TUBE_STATE_LOCAL_PENDING: + self._tubes_chan[telepathy.CHANNEL_TYPE_TUBES].AcceptDBusTube(id) + + tube_conn = SugarTubeConnection(self._conn, + self._tubes_chan[telepathy.CHANNEL_TYPE_TUBES], + id, group_iface=self._text_chan[telepathy.CHANNEL_INTERFACE_GROUP]) + + self.on_tube(tube_conn, self._initiating) diff --git a/Speak.activity/face.py b/Speak.activity/face.py index 7dbaa77..e77fe8f 100644 --- a/Speak.activity/face.py +++ b/Speak.activity/face.py @@ -34,6 +34,7 @@ import logging import gtk import gobject import pango +import cjson from gettext import gettext as _ # try: @@ -59,6 +60,8 @@ import voice import fft_mouth import waveform_mouth +logger = logging.getLogger('speak') + PITCH_MAX = 100 RATE_MAX = 100 FACE_PAD = 2 @@ -71,6 +74,38 @@ class Status: self.eyes = [eye.Eye] * 2 self.mouth = mouth.Mouth + def serialize(self): + eyes = { eye.Eye : 1, + glasses.Glasses : 2 } + mouths = { mouth.Mouth : 1, + fft_mouth.FFTMouth : 2, + waveform_mouth.WaveformMouth : 3 } + + return cjson.encode({ + 'voice' : { 'language' : self.voice.language, + 'gender' : self.voice.gender, + 'name' : self.voice.name }, + 'pitch' : self.pitch, + 'rate' : self.rate, + 'eyes' : [eyes[i] for i in self.eyes], + 'mouth' : mouths[self.mouth] }) + + def deserialize(self, buf): + eyes = { 1: eye.Eye, + 2: glasses.Glasses } + mouths = { 1: mouth.Mouth, + 2: fft_mouth.FFTMouth, + 3: waveform_mouth.WaveformMouth } + + data = cjson.decode(buf) + self.voice.language = data['voice']['language'] + self.voice.gender = data['voice']['gender'] + self.voice.name = data['voice']['name'] + self.pitch = data['pitch'] + self.rate = data['rate'] + self.eyes = [eyes[i] for i in data['eyes']] + self.mouth = mouths[data['mouth']] + class View(gtk.EventBox): def __init__(self, fill_color=style.COLOR_BUTTON_GREY): gtk.EventBox.__init__(self) @@ -154,7 +189,7 @@ class View(gtk.EventBox): if self._audio is None: return - logging.debug('%s: %s' % (self.status.voice.name, something)) + logger.debug('%s: %s' % (self.status.voice.name, something)) pitch = int(self.status.pitch) rate = int(self.status.rate) diff --git a/Speak.activity/messenger.py b/Speak.activity/messenger.py new file mode 100644 index 0000000..cf19701 --- /dev/null +++ b/Speak.activity/messenger.py @@ -0,0 +1,110 @@ +# 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 +from dbus.gobject_service import ExportedGObject +from dbus.service import method, signal + +from sugar.presence import presenceservice + +import face + +logger = logging.getLogger('speak') + +SERVICE = 'org.sugarlabs.Speak' +IFACE = SERVICE +PATH = '/org/sugarlabs/Speak' + +class Messenger(ExportedGObject): + def __init__(self, tube, is_initiator, chat): + ExportedGObject.__init__(self, tube, PATH) + + self.is_initiator = is_initiator + self.chat = chat + + self._tube = tube + self._entered = False + self._buddies = {} + + self._tube.add_signal_receiver(self._ping_cb, '_ping', IFACE, path=PATH, + sender_keyword='sender') + self._tube.add_signal_receiver(self._post_cb, '_post', IFACE, path=PATH, + sender_keyword='sender') + self._tube.watch_participants(self._participant_change_cb) + + def post(self, text): + self._post(self.chat.me.status.serialize(), text) + + def _participant_change_cb(self, added, removed): + if removed: + for i in removed: + buddy = self._buddies.get(i) + if buddy: + logger.debug('buddy %s left chat' + % self._buddies[i].props.nick) + self.chat.farewell(self._buddies[i]) + del self._buddies[i] + else: + logger.warning('_participant_change_cb: cannot find buddy') + else: + if not self._entered: + self.me = self._tube.get_unique_name() + if not self.is_initiator: + self._ping(self.chat.me.status.serialize()) + self._entered = True + + @signal(IFACE, signature='s') + def _ping(self, status): + logger.debug('send ping') + pass + + @signal(IFACE, signature='ss') + def _post(self, status, text): + logger.debug('send message: %s' % text) + pass + + @method(dbus_interface=IFACE, in_signature='s', out_signature='', + sender_keyword='sender') + def _pong(self, sender_status, sender=None): + tp_handle = self._tube.bus_name_to_handle[sender] + buddy = self._buddies[tp_handle] = self._tube.get_buddy(tp_handle) + + logger.debug('pong received from %s(%s)' % (sender, buddy.props.nick)) + + self.chat.post(buddy, face.Status().deserialize(sender_status), None) + + def _ping_cb(self, sender_status, sender=None): + if sender == self.me: + return + + tp_handle = self._tube.bus_name_to_handle[sender] + buddy = self._buddies[tp_handle] = self._tube.get_buddy(tp_handle) + + logger.debug('ping received from %s(%s)' % (sender, buddy.props.nick)) + + self.chat.post(buddy, face.Status().deserialize(sender_status), None) + remote_object = self._tube.get_object(sender, PATH) + remote_object._pong(self.chat.me.status.serialize()) + + def _post_cb(self, sender_status, text, sender=None): + if sender == self.me: + return + + tp_handle = self._tube.bus_name_to_handle[sender] + buddy = self._buddies[tp_handle] + + logger.debug('message received from %s(%s): %s' + % (sender, buddy.props.nick, text)) + + self.chat.post(buddy, face.Status().deserialize(sender_status), text) -- cgit v0.9.1