diff --git a/Conversation.py b/Conversation.py index 0291505..ac799a0 100644 --- a/Conversation.py +++ b/Conversation.py @@ -93,9 +93,11 @@ class Conversation(gobject.GObject): ft.accept() def view_webcam(self): + '''This send a request to received the user's webcam''' p2p = self.controller.msn.p2p[self.switchboard.firstUser] - handler = emesenelib.p2p.transfers.WebcamHandler(we_invited=True, p2p=p2p) + handler = emesenelib.p2p.transfers.WebcamHandler(we_initiate=True, request=False, p2p=p2p) self.webcam_other = Webcam.Webcam(p2p, self, handler.session_id, handler) + self.appendOutputText(None, _("You requested to receive the user's webcam"), 'information') def on_webcam_invite(self, p2p, session, sender): self.webcam_other = Webcam.Webcam(p2p, self, session, sender) @@ -105,6 +107,14 @@ class Conversation(gobject.GObject): if response == stock.YES: self.webcam_other.accept() + def sendWebcam(self): + '''This send a request to send your webcam to the user''' + + p2p = self.controller.msn.p2p[self.switchboard.firstUser] + handler = emesenelib.p2p.transfers.WebcamHandler(we_initiate=True, request=True, p2p=p2p) + self.webcam = Webcam.Webcam(p2p, self, handler.session_id, handler) + self.appendOutputText(None, _("You requested to send your webcam"), 'information') + def sendFile(self, path): '''This sends a file''' # TODO: handle multichat diff --git a/ConversationUI.py b/ConversationUI.py index 6fe5bd1..6a90a4d 100644 --- a/ConversationUI.py +++ b/ConversationUI.py @@ -1138,6 +1138,10 @@ class ToolbarWidget(gtk.Toolbar): self.sendfileButton.set_stock_id(gtk.STOCK_GOTO_TOP) self.sendfileButton.connect('clicked', self.sendFileClicked) + self.sendWebcamButton = gtk.ToolButton() + self.sendWebcamButton.set_label(_('Send Webcam')) + self.sendWebcamButton.connect('clicked', self.sendWebcamClicked) + # Add buttons to the toolbar self.insert(self.fontFace, -1) self.insert(self.fontColor, -1) @@ -1152,6 +1156,7 @@ class ToolbarWidget(gtk.Toolbar): self.insert(self.inviteButton, -1) self.insert(self.sendfileButton, -1) + self.insert(self.sendWebcamButton, -1) self.insert(gtk.SeparatorToolItem(), -1) @@ -1170,6 +1175,7 @@ class ToolbarWidget(gtk.Toolbar): _('Invite a friend to the conversation')) self.clearButton.set_tooltip(self.tooltips, _('Clear conversation')) self.sendfileButton.set_tooltip(self.tooltips, _('Send a file')) + self.sendWebcamButton.set_tooltip(self.tooltips, _('Send your Webcam')) self.show_all() @@ -1263,6 +1269,9 @@ class ToolbarWidget(gtk.Toolbar): if win is not None: win.send_file_dialog() + def sendWebcamClicked(self, *args): + self.parentUI.parentConversation.sendWebcam() + def update(self, *args): '''this method disables some buttons on switchboard error''' conversation = self.parentUI.parentConversation diff --git a/Webcam.py b/Webcam.py index 8acf005..a410cf5 100644 --- a/Webcam.py +++ b/Webcam.py @@ -31,6 +31,11 @@ try: except: print "Libmimic not found, webcam not available" +try: + import WebcamDevice +except: + print "Can't initialize webcam device, sending disabled" + class Webcam: ''' this class represents a webcam session''' @@ -40,7 +45,7 @@ class Webcam: self.p2p = p2p self.conversation = conversation self.session = int(session) - self.sender = sender + self.sender = sender; self.timeAccepted = None @@ -48,12 +53,14 @@ class Webcam: sap = self.signals.append sap(self.p2p.connect('webcam-frame-received', self.on_webcam_frame)) sap(self.p2p.connect('webcam-failed', self.on_webcam_failed)) + sap(self.p2p.connect('webcam-ack', self.on_accept)) - self.decoder = libmimic.new_decoder() self.init = False self.gc = None + self.webcam = None self.frames = 0 + self.errors = 0 self.win = gtk.Window() self.win.set_double_buffered(False) @@ -65,6 +72,8 @@ class Webcam: self.pixmap = None def on_close(self, *args): + if self.webcam is not None: + self.webcam.stop() self.conversation.appendOutputText(None, _('You have canceled the webcam session'), 'information') self.p2p.emit('webcam-canceled', self.session) @@ -75,9 +84,16 @@ class Webcam: def on_webcam_frame(self, p2p, session, sender, frame): try: width, height, data = libmimic.decode(self.decoder, frame) + if self.errors > 0: + self.errors -= 1 + print "self.errors: %d\n" % self.errors except: - print "Decode error, stopping webcam" - self.on_close() + print "Decode error" + self.errors += 1 + print "self.errors: %d\n" % self.errors + if self.errors > 10: + print "Too many errors, closing webcam" + self.on_close() return if not self.init: @@ -92,9 +108,23 @@ class Webcam: self.win.window.draw_rgb_image(self.gc, 0, 0, width, height, \ gtk.gdk.RGB_DITHER_NONE, data, width*3) self.win.queue_draw() + + def on_accept(self, p2p): + if not self.init: + print "Yes we received that!" + self.win.set_size_request(320, 240) + self.win.show_all() + self.gc = gtk.gdk.GC(self.win.window) + self.pixmap = gtk.gdk.Pixmap(self.win.window, 320, 240, -1) + self.device = WebcamDevice.WebcamDevice(self.win.window.xid) + self.init = True def accept(self): + self.decoder = libmimic.new_decoder() self.p2p.emit('webcam-accepted', self.session, self.sender) - - + + def invite(self, receiver): + self.receiver = receiver + self.encoder = libmimic.new_encoder() + self.p2p.emit('webcam-invite', self.session, self.receiver) diff --git a/WebcamDevice.py b/WebcamDevice.py new file mode 100644 index 0000000..4f03994 --- /dev/null +++ b/WebcamDevice.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- + +# This file is part of emesene. +# +# Emesene 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. +# +# emesene 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 emesene; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import pygst +pygst.require("0.10") +import gst + +class WebcamDevice: + def __init__(self, xid): + self.xid = xid + self.p = gst.Pipeline("my") + self.source = gst.element_factory_make("v4l2src") + self.p.add(self.source) + scale = gst.element_factory_make("videoscale") + self.p.add(scale) + self.source.link(scale) + caps = gst.Caps("video/x-raw-yuv, width=320, height=240") + filter = gst.element_factory_make("capsfilter", "filter") + filter.set_property("caps", caps) + self.p.add(filter) + scale.link(filter) + self.sink = gst.element_factory_make("autovideosink") + self.p.add(self.sink) + filter.link(self.sink) + self.bus = self.p.get_bus() + self.bus.add_signal_watch() + self.bus.enable_sync_message_emission() + self.bus.connect("sync-message::element", self.onmessage) + self.p.set_state(gst.STATE_PLAYING) + + def onmessage(self, bus, message): + if message.structure is None: + return + if message.structure.get_name() == "prepare-xwindow-id": + message.src.set_xwindow_id(self.xid) + def stop(self): + self.p.set_state(gst.STATE_READY) + diff --git a/emesenelib/p2p/tlp.py b/emesenelib/p2p/tlp.py index 4f5ec74..f980938 100644 --- a/emesenelib/p2p/tlp.py +++ b/emesenelib/p2p/tlp.py @@ -96,6 +96,9 @@ class P2PUser(gobject.GObject): # session, sender 'webcam-invite': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT)), + + # This signal is sent when the other user accepts webcam + 'webcam-ack': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), # session, sender 'webcam-accepted': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, @@ -304,7 +307,7 @@ class P2PUser(gobject.GObject): elif slp.body['EUF-GUID'] == msn_slp.SLPMessage.EUFGUID_WEBCAM: # webcam invite rawcontext = slp.body['Context'] - msn_p2p.WebcamHandler(we_invited=False, p2p=self, slp=slp) + msn_p2p.WebcamHandler(False, True, self, slp) return else: data = self.get_data_by_context(slp.body['Context']) diff --git a/emesenelib/p2p/transfers.py b/emesenelib/p2p/transfers.py index 5c3ac83..d4a0dd2 100644 --- a/emesenelib/p2p/transfers.py +++ b/emesenelib/p2p/transfers.py @@ -663,7 +663,7 @@ class WebcamHandler(Base): 'webcam-canceled'] signals_self = ['msnp2p-message-ready'] - def __init__(self, we_invited, p2p, slp=None): + def __init__(self, we_initiate, request, p2p, slp=None): '''context, slp and bin_header are from the INVITE message''' Base.__init__(self, p2p) @@ -673,7 +673,8 @@ class WebcamHandler(Base): self.session_id = int(slp.body['SessionID']) self.p2p = p2p - self.we_invited = we_invited + self.we_initiate = we_initiate + self.producer = request self.connect_handler(p2p) @@ -688,11 +689,16 @@ class WebcamHandler(Base): msn = self.p2p.manager.msn self.external_ip = msn.demographics['ClientIP'] - if we_invited: - # ask to receive webcam - self.debug('Sending invite to receive webcam') - message = msn_slp.invite(self, self.context, msn_slp.SLPMessage.EUFGUID_WEBCAM_ASK) - self.emit('msnp2p-message-ready', message, 0) + if we_initiate: + if request: + self.debug('Sending invite to send webcam') + message = msn_slp.invite(self, self.context, msn_slp.SLPMessage.EUFGUID_WEBCAM) + self.emit('msnp2p-message-ready', message, 0) + else: + # ask to receive webcam + self.debug('Sending invite to receive webcam') + message = msn_slp.invite(self, self.context, msn_slp.SLPMessage.EUFGUID_WEBCAM_ASK) + self.emit('msnp2p-message-ready', message, 0) else: self.debug('Received webcam invite') p2p.emit('webcam-invite', int(self.session_id), self.from_) @@ -793,8 +799,12 @@ class WebcamHandler(Base): self.emit('msnp2p-message-ready', reply, self.session_id) elif msg.startswith('ack'): - # TODO: if we are producer send data now self.debug('Received ACK', 'webcam') + p2p.emit('webcam-ack') + #FIXME: Find out what is rid and what should it be set to + data = self.get_producer_xml(self.session_id, 342) + '\x00' + reply = format_message(data) + self.emit('msnp2p-message-ready', reply, self.session_id) elif msg.startswith(''): if self.received_producer_xml: @@ -862,6 +872,24 @@ class WebcamHandler(Base): self.debug('Viewer xml: ' + xml, 'webcam') return xml + def get_producer_xml(self, session, rid): + ip = '%s' % self.external_ip + port = '6892' + + xml = '' + xml += '2.0' + xml += '' + str(rid) + "" + xml += ""+str(int(session))+"02931" + xml += "" + xml += "" + port + "" + port + "" + xml += "" + port + "" + ip + xml += "" + xml += "" + xml += "1" + xml += "\r\n\r\n" + + self.debug('Producer xml: ' + xml, 'webcam') + return xml class WebcamListenSocket(asyncore.dispatcher): '''Webcam server socket''' @@ -870,7 +898,16 @@ class WebcamListenSocket(asyncore.dispatcher): self.create_socket(socket.AF_INET, socket.SOCK_STREAM) self.set_reuse_addr() - self.bind((host, port)) + retry = 1 + while retry: + try: + self.bind((host, port)) + retry = 0 + except: + port += 1 + retry += 1 + if retry > 5: + raise Error("Could bind to port") self.listen(1) self.receiver = receiver