Jingle: dialog for accepting voice calls

This commit is contained in:
Tomasz Melcer 2007-08-23 23:42:31 +00:00
parent c2c8efe2cc
commit 2a7f1a654a
5 changed files with 290 additions and 54 deletions

View File

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
<!--*- mode: xml -*-->
<glade-interface>
<widget class="GtkMessageDialog" id="voip_call_received_messagedialog">
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="border_width">5</property>
<property name="resizable">False</property>
<property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
<property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
<property name="skip_taskbar_hint">True</property>
<property name="has_separator">False</property>
<property name="message_type">GTK_MESSAGE_QUESTION</property>
<property name="buttons">GTK_BUTTONS_YES_NO</property>
<property name="text">&lt;b&gt;&lt;big&gt;Incoming call&lt;/big&gt;&lt;/b&gt;</property>
<property name="use_markup">True</property>
<property name="secondary_text">%(contact)s wants to start a voice chat with you. Do you want to answer the call?</property>
<signal name="close" handler="on_voip_call_received_messagedialog_close"/>
<signal name="response" handler="on_voip_call_received_messagedialog_response"/>
<child internal-child="vbox">
<widget class="GtkVBox" id="dialog-vbox2">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="spacing">2</property>
<child internal-child="action_area">
<widget class="GtkHButtonBox" id="dialog-action_area2">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="layout_style">GTK_BUTTONBOX_END</property>
</widget>
<packing>
<property name="expand">False</property>
<property name="pack_type">GTK_PACK_END</property>
</packing>
</child>
</widget>
</child>
</widget>
</glade-interface>

View File

@ -52,7 +52,6 @@ def state_changed(stream, state, dir):
print "state_changed: connectied" print "state_changed: connectied"
print "WW: stream.signal_native_candidates_prepared()" print "WW: stream.signal_native_candidates_prepared()"
print "WW: stream.start()" print "WW: stream.start()"
exit()
stream.signal_native_candidates_prepared() stream.signal_native_candidates_prepared()
stream.start() stream.start()

View File

@ -12,24 +12,35 @@
## ##
''' Handles the jingle signalling protocol. ''' ''' Handles the jingle signalling protocol. '''
# note: if there will be more types of sessions (possibly file transfer,
# video...), split this file
import gajim import gajim
import gobject
import xmpp import xmpp
# ugly hack # ugly hack, fixed in farsight 0.1.24
import sys, dl, gst import sys, dl, gst
sys.setdlopenflags(dl.RTLD_NOW | dl.RTLD_GLOBAL) sys.setdlopenflags(dl.RTLD_NOW | dl.RTLD_GLOBAL)
import farsight import farsight
sys.setdlopenflags(dl.RTLD_NOW | dl.RTLD_LOCAL) sys.setdlopenflags(dl.RTLD_NOW | dl.RTLD_LOCAL)
def timeout_add_and_call(timeout, callable, *args, **kwargs):
''' Call a callback once. If it returns True, add a timeout handler to call it more times.
Helper function. '''
if callable(*args, **kwargs):
return gobject.timeout_add(timeout, callable, *args, **kwargs)
return -1 # gobject.source_remove will not object
class JingleStates(object): class JingleStates(object):
''' States in which jingle session may exist. ''' ''' States in which jingle session may exist. '''
ended=0 ended=0
pending=1 pending=1
active=2 active=2
class Exception(object): pass class Error(Exception): pass
class WrongState(Exception): pass class WrongState(Error): pass
class NoCommonCodec(Exception): pass class NoSuchSession(Error): pass
class JingleSession(object): class JingleSession(object):
''' This represents one jingle session. ''' ''' This represents one jingle session. '''
@ -54,6 +65,8 @@ class JingleSession(object):
sid=con.connection.getAnID() sid=con.connection.getAnID()
self.sid=sid # sessionid self.sid=sid # sessionid
self.accepted=True # is this session accepted by user
# callbacks to call on proper contents # callbacks to call on proper contents
# use .prepend() to add new callbacks, especially when you're going # use .prepend() to add new callbacks, especially when you're going
# to send error instead of ack # to send error instead of ack
@ -75,6 +88,17 @@ class JingleSession(object):
self.p2psession = farsight.farsight_session_factory_make('rtp') self.p2psession = farsight.farsight_session_factory_make('rtp')
self.p2psession.connect('error', self.on_p2psession_error) self.p2psession.connect('error', self.on_p2psession_error)
''' Interaction with user '''
def approveSession(self):
''' Called when user accepts session in UI (when we aren't the initiator).'''
self.accepted=True
self.acceptSession()
def declineSession(self):
''' Called when user declines session in UI (when we aren't the initiator,
or when the user wants to stop session completly. '''
self.__sessionTerminate()
''' Middle-level functions to manage contents. Handle local content ''' Middle-level functions to manage contents. Handle local content
cache and send change notifications. ''' cache and send change notifications. '''
def addContent(self, name, content, creator='we'): def addContent(self, name, content, creator='we'):
@ -82,6 +106,8 @@ class JingleSession(object):
this will send proper stanza to update session. this will send proper stanza to update session.
The protocol prohibits changing that when pending. The protocol prohibits changing that when pending.
Creator must be one of ('we', 'peer', 'initiator', 'responder')''' Creator must be one of ('we', 'peer', 'initiator', 'responder')'''
assert creator in ('we', 'peer', 'initiator', 'responder')
if self.state==JingleStates.pending: if self.state==JingleStates.pending:
raise WrongState raise WrongState
@ -104,6 +130,13 @@ class JingleSession(object):
''' We do not need this now ''' ''' We do not need this now '''
pass pass
def acceptSession(self):
''' Check if all contents and user agreed to start session. '''
if not self.weinitiate and \
self.accepted and \
all(c.negotiated for c in self.contents.itervalues()):
self.__sessionAccept()
else:
''' Middle-level function to do stanza exchange. ''' ''' Middle-level function to do stanza exchange. '''
def startSession(self): def startSession(self):
''' Start session. ''' ''' Start session. '''
@ -111,7 +144,7 @@ class JingleSession(object):
def sendSessionInfo(self): pass def sendSessionInfo(self): pass
''' Callbacks. ''' ''' Session callbacks. '''
def stanzaCB(self, stanza): def stanzaCB(self, stanza):
''' A callback for ConnectionJingle. It gets stanza, then ''' A callback for ConnectionJingle. It gets stanza, then
tries to send it to all internally registered callbacks. tries to send it to all internally registered callbacks.
@ -152,12 +185,21 @@ class JingleSession(object):
def __sessionInitiateCB(self, stanza, jingle, error, action): def __sessionInitiateCB(self, stanza, jingle, error, action):
''' We got a jingle session request from other entity, ''' We got a jingle session request from other entity,
therefore we are the receiver... Unpack the data. ''' therefore we are the receiver... Unpack the data,
inform the user. '''
self.initiator = jingle['initiator'] self.initiator = jingle['initiator']
self.responder = self.ourjid self.responder = self.ourjid
self.peerjid = self.initiator self.peerjid = self.initiator
self.accepted = False # user did not accept this session yet
# TODO: If the initiator is unknown to the receiver (e.g., via presence
# subscription) and the receiver has a policy of not communicating via
# Jingle with unknown entities, it SHOULD return a <service-unavailable/>
# error.
# Lets check what kind of jingle session does the peer want
fail = True fail = True
contents = []
for element in jingle.iterTags('content'): for element in jingle.iterTags('content'):
# checking what kind of session this will be # checking what kind of session this will be
desc_ns = element.getTag('description').getNamespace() desc_ns = element.getTag('description').getNamespace()
@ -165,10 +207,13 @@ class JingleSession(object):
if desc_ns==xmpp.NS_JINGLE_AUDIO and tran_ns==xmpp.NS_JINGLE_ICE_UDP: if desc_ns==xmpp.NS_JINGLE_AUDIO and tran_ns==xmpp.NS_JINGLE_ICE_UDP:
# we've got voip content # we've got voip content
self.addContent(element['name'], JingleVoiP(self), 'peer') self.addContent(element['name'], JingleVoiP(self), 'peer')
contents.append(('VOIP',))
fail = False fail = False
# If there's no content we understand...
if fail: if fail:
# TODO: we should send <unsupported-content/> inside too # TODO: we should send <unsupported-content/> inside too
# TODO: delete this instance
self.connection.connection.send( self.connection.connection.send(
xmpp.Error(stanza, xmpp.NS_STANZAS + 'feature-not-implemented')) xmpp.Error(stanza, xmpp.NS_STANZAS + 'feature-not-implemented'))
self.connection.deleteJingle(self) self.connection.deleteJingle(self)
@ -176,6 +221,9 @@ class JingleSession(object):
self.state = JingleStates.pending self.state = JingleStates.pending
# Send event about starting a session
self.connection.dispatch('JINGLE_INCOMING', (self.initiator, self.sid, contents))
def __broadcastCB(self, stanza, jingle, error, action): def __broadcastCB(self, stanza, jingle, error, action):
''' Broadcast the stanza contents to proper content handlers. ''' ''' Broadcast the stanza contents to proper content handlers. '''
for content in jingle.iterTags('content'): for content in jingle.iterTags('content'):
@ -198,47 +246,47 @@ class JingleSession(object):
'sid': self.sid}) 'sid': self.sid})
return stanza, jingle return stanza, jingle
def __appendContent(self, jingle, content, full=True): def __appendContent(self, jingle, content):
''' Append <content/> element to <jingle/> element, ''' Append <content/> element to <jingle/> element,
with (full=True) or without (full=False) <content/> with (full=True) or without (full=False) <content/>
children. ''' children. '''
if full:
jingle.addChild(node=content.toXML())
else:
jingle.addChild('content', jingle.addChild('content',
attrs={'name': content.name, 'creator': content.creator}) attrs={'name': content.name, 'creator': content.creator})
def __appendContents(self, jingle, full=True): def __appendContents(self, jingle):
''' Append all <content/> elements to <jingle/>.''' ''' Append all <content/> elements to <jingle/>.'''
# TODO: integrate with __appendContent? # TODO: integrate with __appendContent?
# TODO: parameters 'name', 'content'? # TODO: parameters 'name', 'content'?
for content in self.contents.values(): for content in self.contents.values():
self.__appendContent(jingle, content, full=full) self.__appendContent(jingle, content)
def __sessionInitiate(self): def __sessionInitiate(self):
assert self.state==JingleStates.ended assert self.state==JingleStates.ended
stanza, jingle = self.__makeJingle('session-initiate') stanza, jingle = self.__makeJingle('session-initiate')
self.__appendContents(jingle) self.__appendContents(jingle)
self.__broadcastCB(stanza, jingle, None, 'session-initiate-sent')
self.connection.connection.send(stanza) self.connection.connection.send(stanza)
def __sessionAccept(self): def __sessionAccept(self):
assert self.state==JingleStates.pending assert self.state==JingleStates.pending
stanza, jingle = self.__jingle('session-accept') stanza, jingle = self.__makeJingle('session-accept')
self.__appendContents(jingle, False) self.__appendContents(jingle)
self.__broadcastCB(stanza, jingle, None, 'session-accept-sent')
self.connection.connection.send(stanza) self.connection.connection.send(stanza)
self.state=JingleStates.active self.state=JingleStates.active
def __sessionInfo(self, payload=None): def __sessionInfo(self, payload=None):
assert self.state!=JingleStates.ended assert self.state!=JingleStates.ended
stanza, jingle = self.__jingle('session-info') stanza, jingle = self.__makeJingle('session-info')
if payload: if payload:
jingle.addChild(node=payload) jingle.addChild(node=payload)
self.connection.connection.send(stanza) self.connection.connection.send(stanza)
def __sessionTerminate(self): def __sessionTerminate(self):
assert self.state!=JingleStates.ended assert self.state!=JingleStates.ended
stanza, jingle = self.__jingle('session-terminate') stanza, jingle = self.__makeJingle('session-terminate')
self.connection.connection.send(stanza) self.connection.connection.send(stanza)
self.__broadcastCB(stanza, jingle, None, 'session-terminate-sent')
def __contentAdd(self): def __contentAdd(self):
assert self.state==JingleStates.active assert self.state==JingleStates.active
@ -252,6 +300,12 @@ class JingleSession(object):
def __contentRemove(self): def __contentRemove(self):
assert self.state!=JingleStates.ended assert self.state!=JingleStates.ended
def sendContentAccept(self, content):
assert self.state!=JingleStates.ended
stanza, jingle = self.__makeJingle('content-accept')
jingle.addChild(node=content)
self.connection.connection.send(stanza)
def sendTransportInfo(self, content): def sendTransportInfo(self, content):
assert self.state!=JingleStates.ended assert self.state!=JingleStates.ended
stanza, jingle = self.__makeJingle('transport-info') stanza, jingle = self.__makeJingle('transport-info')
@ -270,6 +324,7 @@ class JingleContent(object):
# (a JingleContent not added to session shouldn't send anything) # (a JingleContent not added to session shouldn't send anything)
#self.creator = None #self.creator = None
#self.name = None #self.name = None
self.negotiated = False # is this content already negotiated?
class JingleVoiP(JingleContent): class JingleVoiP(JingleContent):
''' Jingle VoiP sessions consist of audio content transported ''' Jingle VoiP sessions consist of audio content transported
@ -288,22 +343,34 @@ class JingleVoiP(JingleContent):
def stanzaCB(self, stanza, content, error, action): def stanzaCB(self, stanza, content, error, action):
''' Called when something related to our content was sent by peer. ''' ''' Called when something related to our content was sent by peer. '''
callbacks = { callbacks = {
# these are called when *we* get stanzas
'content-accept': [self.__getRemoteCodecsCB], 'content-accept': [self.__getRemoteCodecsCB],
'content-add': [], 'content-add': [],
'content-modify': [], 'content-modify': [],
'content-remove': [], 'content-remove': [],
'session-accept': [self.__getRemoteCodecsCB], 'session-accept': [self.__getRemoteCodecsCB, self.__startMic],
'session-info': [], 'session-info': [],
'session-initiate': [self.__getRemoteCodecsCB], 'session-initiate': [self.__getRemoteCodecsCB],
'session-terminate': [], 'session-terminate': [self.__stop],
'transport-info': [self.__transportInfoCB], 'transport-info': [self.__transportInfoCB],
'iq-result': [], 'iq-result': [],
'iq-error': [], 'iq-error': [],
# these are called when *we* sent these stanzas
'session-initiate-sent': [self.__sessionInitiateSentCB],
'session-accept-sent': [self.__startMic],
'session-terminate-sent': [self.__stop],
}[action] }[action]
for callback in callbacks: for callback in callbacks:
callback(stanza, content, error, action) callback(stanza, content, error, action)
def __sessionInitiateSentCB(self, stanza, content, error, action):
''' Add our things to session-initiate stanza. '''
content.setAttr('profile', 'RTP/AVP')
content.addChild(xmpp.NS_JINGLE_AUDIO+' description', payload=self.iterCodecs())
content.addChild(xmpp.NS_JINGLE_ICE_UDP+' transport')
def __getRemoteCodecsCB(self, stanza, content, error, action): def __getRemoteCodecsCB(self, stanza, content, error, action):
''' Get peer codecs from what we get from peer. '''
if self.got_codecs: return if self.got_codecs: return
codecs = [] codecs = []
@ -362,51 +429,29 @@ class JingleVoiP(JingleContent):
attrs={'name': self.name, 'creator': self.creator, 'profile': 'RTP/AVP'}, attrs={'name': self.name, 'creator': self.creator, 'profile': 'RTP/AVP'},
payload=payload) payload=payload)
def setupStream(self):
self.p2pstream = self.session.p2psession.create_stream(
farsight.MEDIA_TYPE_AUDIO, farsight.STREAM_DIRECTION_BOTH)
self.p2pstream.set_property('transmitter', 'libjingle')
self.p2pstream.connect('error', self.on_p2pstream_error)
self.p2pstream.connect('new-active-candidate-pair', self.on_p2pstream_new_active_candidate_pair)
self.p2pstream.connect('codec-changed', self.on_p2pstream_codec_changed)
self.p2pstream.connect('native-candidates-prepared', self.on_p2pstream_native_candidates_prepared)
self.p2pstream.connect('state-changed', self.on_p2pstream_state_changed)
self.p2pstream.connect('new-native-candidate', self.on_p2pstream_new_native_candidate)
self.p2pstream.set_remote_codecs(self.p2pstream.get_local_codecs())
self.p2pstream.prepare_transports()
self.p2pstream.set_active_codec(8) #???
sink = gst.element_factory_make('alsasink')
sink.set_property('sync', False)
sink.set_property('latency-time', 20000)
sink.set_property('buffer-time', 80000)
src = gst.element_factory_make('audiotestsrc')
src.set_property('blocksize', 320)
#src.set_property('latency-time', 20000)
src.set_property('is-live', True)
self.p2pstream.set_sink(sink)
self.p2pstream.set_source(src)
def on_p2pstream_error(self, *whatever): pass def on_p2pstream_error(self, *whatever): pass
def on_p2pstream_new_active_candidate_pair(self, stream, native, remote): pass def on_p2pstream_new_active_candidate_pair(self, stream, native, remote): pass
def on_p2pstream_codec_changed(self, stream, codecid): pass def on_p2pstream_codec_changed(self, stream, codecid): pass
def on_p2pstream_native_candidates_prepared(self, *whatever): def on_p2pstream_native_candidates_prepared(self, *whatever):
for candidate in self.p2pstream.get_native_candidate_list(): pass
self.send_candidate(candidate)
def on_p2pstream_state_changed(self, stream, state, dir): def on_p2pstream_state_changed(self, stream, state, dir):
if state==farsight.STREAM_STATE_CONNECTED: if state==farsight.STREAM_STATE_CONNECTED:
stream.signal_native_candidates_prepared() stream.signal_native_candidates_prepared()
stream.start() stream.start()
self.pipeline.set_state(gst.STATE_PLAYING)
self.negotiated = True
if not self.session.weinitiate:
self.session.sendContentAccept(self.__content((xmpp.Node('description', payload=self.iterCodecs()),)))
self.session.acceptSession()
def on_p2pstream_new_native_candidate(self, p2pstream, candidate_id): def on_p2pstream_new_native_candidate(self, p2pstream, candidate_id):
candidates = p2pstream.get_native_candidate(candidate_id) candidates = p2pstream.get_native_candidate(candidate_id)
for candidate in candidates: for candidate in candidates:
self.send_candidate(candidate) self.send_candidate(candidate)
def send_candidate(self, candidate): def send_candidate(self, candidate):
attrs={ attrs={
'component': candidate['component'], 'component': candidate['component'],
@ -442,6 +487,85 @@ class JingleVoiP(JingleContent):
else: p = () else: p = ()
yield xmpp.Node('payload-type', a, p) yield xmpp.Node('payload-type', a, p)
''' Things to control the gstreamer's pipeline '''
def setupStream(self):
# the pipeline
self.pipeline = gst.Pipeline()
# the network part
self.p2pstream = self.session.p2psession.create_stream(
farsight.MEDIA_TYPE_AUDIO, farsight.STREAM_DIRECTION_BOTH)
self.p2pstream.set_pipeline(self.pipeline)
self.p2pstream.set_property('transmitter', 'libjingle')
self.p2pstream.connect('error', self.on_p2pstream_error)
self.p2pstream.connect('new-active-candidate-pair', self.on_p2pstream_new_active_candidate_pair)
self.p2pstream.connect('codec-changed', self.on_p2pstream_codec_changed)
self.p2pstream.connect('native-candidates-prepared', self.on_p2pstream_native_candidates_prepared)
self.p2pstream.connect('state-changed', self.on_p2pstream_state_changed)
self.p2pstream.connect('new-native-candidate', self.on_p2pstream_new_native_candidate)
self.p2pstream.set_remote_codecs(self.p2pstream.get_local_codecs())
self.p2pstream.prepare_transports()
self.p2pstream.set_active_codec(8) #???
# the local parts
# TODO: use gconfaudiosink?
sink = gst.element_factory_make('alsasink')
sink.set_property('sync', False)
sink.set_property('latency-time', 20000)
sink.set_property('buffer-time', 80000)
self.pipeline.add(sink)
self.src_signal = gst.element_factory_make('audiotestsrc')
self.src_signal.set_property('blocksize', 320)
self.src_signal.set_property('freq', 440)
self.pipeline.add(self.src_signal)
# TODO: use gconfaudiosrc?
self.src_mic = gst.element_factory_make('alsasrc')
self.src_mic.set_property('blocksize', 320)
self.pipeline.add(self.src_mic)
self.mic_volume = gst.element_factory_make('volume')
self.mic_volume.set_property('volume', 0)
self.pipeline.add(self.mic_volume)
self.adder = gst.element_factory_make('adder')
self.pipeline.add(self.adder)
# link gst elements
self.src_signal.link(self.adder)
self.src_mic.link(self.mic_volume)
self.mic_volume.link(self.adder)
# this will actually start before the pipeline will be started.
# no worries, though; it's only a ringing sound
def signal():
while True:
self.src_signal.set_property('volume', 0.5)
yield True # wait 750 ms
yield True # wait 750 ms
self.src_signal.set_property('volume', 0)
yield True # wait 750 ms
self.signal_cb_id = timeout_add_and_call(750, signal().__iter__().next)
self.p2pstream.set_sink(sink)
self.p2pstream.set_source(self.adder)
def __startMic(self, *things):
gobject.source_remove(self.signal_cb_id)
self.src_signal.set_property('volume', 0)
self.mic_volume.set_property('volume', 1)
def __stop(self, *things):
self.pipeline.set_state(gst.STATE_NULL)
gobject.source_remove(self.signal_cb_id)
def __del__(self):
self.__stop()
class ConnectionJingle(object): class ConnectionJingle(object):
''' This object depends on that it is a part of Connection class. ''' ''' This object depends on that it is a part of Connection class. '''
def __init__(self): def __init__(self):
@ -484,7 +608,6 @@ class ConnectionJingle(object):
# do we need to create a new jingle object # do we need to create a new jingle object
if (jid, sid) not in self.__sessions: if (jid, sid) not in self.__sessions:
# TODO: we should check its type here...
newjingle = JingleSession(con=self, weinitiate=False, jid=jid, sid=sid) newjingle = JingleSession(con=self, weinitiate=False, jid=jid, sid=sid)
self.addJingle(newjingle) self.addJingle(newjingle)
@ -501,3 +624,8 @@ class ConnectionJingle(object):
self.addJingle(jingle) self.addJingle(jingle)
jingle.addContent('voice', JingleVoiP(jingle)) jingle.addContent('voice', JingleVoiP(jingle))
jingle.startSession() jingle.startSession()
def getJingleSession(self, jid, sid):
try:
return self.__sessions[(jid, sid)]
except KeyError:
raise NoSuchSession

View File

@ -3298,3 +3298,37 @@ class AdvancedNotificationsWindow:
def on_close_window(self, widget): def on_close_window(self, widget):
self.window.destroy() self.window.destroy()
class VoIPCallReceivedDialog(object):
def __init__(self, account, contact_jid, sid):
self.account = account
self.jid = contact_jid
self.sid = sid
xml = gtkgui_helpers.get_glade('voip_call_received_dialog.glade')
xml.signal_autoconnect(self)
contact = gajim.contacts.get_first_contact_from_jid(account, contact_jid)
if contact and contact.name:
contact_text = '%s (%s)' % (contact.name, contact_jid)
else:
contact_text = contact_jid
# do the substitution
dialog = xml.get_widget('voip_call_received_messagedialog')
dialog.set_property('secondary-text',
dialog.get_property('secondary-text') % {'contact': contact_text})
dialog.show_all()
def on_voip_call_received_messagedialog_close(self, dialog):
return self.on_voip_call_received_messagedialog_response(dialog, gtk.RESPONSE_NO)
def on_voip_call_received_messagedialog_response(self, dialog, response):
# we've got response from user, either stop connecting or accept the call
session = gajim.connections[self.account].getJingleSession(self.jid, self.sid)
if response==gtk.RESPONSE_YES:
session.approveSession()
else: # response==gtk.RESPONSE_NO
session.declineSession()
dialog.destroy()

View File

@ -1864,6 +1864,41 @@ class Interface:
_('You are already connected to this account with the same resource. Please type a new one'), input_str = gajim.connections[account].server_resource, _('You are already connected to this account with the same resource. Please type a new one'), input_str = gajim.connections[account].server_resource,
is_modal = False, ok_handler = on_ok) is_modal = False, ok_handler = on_ok)
def handle_event_jingle_incoming(self, account, data):
# ('JINGLE_INCOMING', account, peer jid, sid, tuple-of-contents==(type, data...))
# TODO: conditional blocking if peer is not in roster
# unpack data
peerjid, sid, contents = data
content_types = set(c[0] for c in contents)
# check type of jingle session
if 'VOIP' in content_types:
# a voip session...
# we now handle only voip, so the only thing we will do here is
# not to return from function
pass
else:
# unknown session type... it should be declined in common/jingle.py
return
if helpers.allow_popup_window(account):
dialogs.VoIPCallReceivedDialog(account, peerjid, sid)
# TODO: not checked
self.add_event(account, peerjid, 'jingle-session', (sid, contents))
if helpers.allow_showing_notification(account):
# TODO: we should use another pixmap ;-)
img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events',
'ft_request.png')
txt = _('%s wants to start a jingle session.') % gajim.get_name_from_jid(
account, jid)
path = gtkgui_helpers.get_path_to_generic_or_avatar(img)
event_type = _('Jingle Session Request')
notify.popup(event_type, jid, account, 'jingle-request',
path_to_image = path, title = event_type, text = txt)
def read_sleepy(self): def read_sleepy(self):
'''Check idle status and change that status if needed''' '''Check idle status and change that status if needed'''
if not self.sleeper.poll(): if not self.sleeper.poll():
@ -2192,6 +2227,7 @@ class Interface:
'SEARCH_FORM': self.handle_event_search_form, 'SEARCH_FORM': self.handle_event_search_form,
'SEARCH_RESULT': self.handle_event_search_result, 'SEARCH_RESULT': self.handle_event_search_result,
'RESOURCE_CONFLICT': self.handle_event_resource_conflict, 'RESOURCE_CONFLICT': self.handle_event_resource_conflict,
'JINGLE_INCOMING': self.handle_event_jingle_incoming,
} }
gajim.handlers = self.handlers gajim.handlers = self.handlers