Jingle: dialog for accepting voice calls
This commit is contained in:
parent
c2c8efe2cc
commit
2a7f1a654a
|
@ -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"><b><big>Incoming call</big></b></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>
|
|
@ -52,7 +52,6 @@ def state_changed(stream, state, dir):
|
|||
print "state_changed: connectied"
|
||||
print "WW: stream.signal_native_candidates_prepared()"
|
||||
print "WW: stream.start()"
|
||||
exit()
|
||||
stream.signal_native_candidates_prepared()
|
||||
stream.start()
|
||||
|
||||
|
|
|
@ -12,24 +12,35 @@
|
|||
##
|
||||
''' Handles the jingle signalling protocol. '''
|
||||
|
||||
# note: if there will be more types of sessions (possibly file transfer,
|
||||
# video...), split this file
|
||||
|
||||
import gajim
|
||||
import gobject
|
||||
import xmpp
|
||||
|
||||
# ugly hack
|
||||
# ugly hack, fixed in farsight 0.1.24
|
||||
import sys, dl, gst
|
||||
sys.setdlopenflags(dl.RTLD_NOW | dl.RTLD_GLOBAL)
|
||||
import farsight
|
||||
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):
|
||||
''' States in which jingle session may exist. '''
|
||||
ended=0
|
||||
pending=1
|
||||
active=2
|
||||
|
||||
class Exception(object): pass
|
||||
class WrongState(Exception): pass
|
||||
class NoCommonCodec(Exception): pass
|
||||
class Error(Exception): pass
|
||||
class WrongState(Error): pass
|
||||
class NoSuchSession(Error): pass
|
||||
|
||||
class JingleSession(object):
|
||||
''' This represents one jingle session. '''
|
||||
|
@ -54,6 +65,8 @@ class JingleSession(object):
|
|||
sid=con.connection.getAnID()
|
||||
self.sid=sid # sessionid
|
||||
|
||||
self.accepted=True # is this session accepted by user
|
||||
|
||||
# callbacks to call on proper contents
|
||||
# use .prepend() to add new callbacks, especially when you're going
|
||||
# to send error instead of ack
|
||||
|
@ -75,6 +88,17 @@ class JingleSession(object):
|
|||
self.p2psession = farsight.farsight_session_factory_make('rtp')
|
||||
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
|
||||
cache and send change notifications. '''
|
||||
def addContent(self, name, content, creator='we'):
|
||||
|
@ -82,6 +106,8 @@ class JingleSession(object):
|
|||
this will send proper stanza to update session.
|
||||
The protocol prohibits changing that when pending.
|
||||
Creator must be one of ('we', 'peer', 'initiator', 'responder')'''
|
||||
assert creator in ('we', 'peer', 'initiator', 'responder')
|
||||
|
||||
if self.state==JingleStates.pending:
|
||||
raise WrongState
|
||||
|
||||
|
@ -104,6 +130,13 @@ class JingleSession(object):
|
|||
''' We do not need this now '''
|
||||
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. '''
|
||||
def startSession(self):
|
||||
''' Start session. '''
|
||||
|
@ -111,7 +144,7 @@ class JingleSession(object):
|
|||
|
||||
def sendSessionInfo(self): pass
|
||||
|
||||
''' Callbacks. '''
|
||||
''' Session callbacks. '''
|
||||
def stanzaCB(self, stanza):
|
||||
''' A callback for ConnectionJingle. It gets stanza, then
|
||||
tries to send it to all internally registered callbacks.
|
||||
|
@ -152,12 +185,21 @@ class JingleSession(object):
|
|||
|
||||
def __sessionInitiateCB(self, stanza, jingle, error, action):
|
||||
''' 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.responder = self.ourjid
|
||||
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
|
||||
contents = []
|
||||
for element in jingle.iterTags('content'):
|
||||
# checking what kind of session this will be
|
||||
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:
|
||||
# we've got voip content
|
||||
self.addContent(element['name'], JingleVoiP(self), 'peer')
|
||||
contents.append(('VOIP',))
|
||||
fail = False
|
||||
|
||||
# If there's no content we understand...
|
||||
if fail:
|
||||
# TODO: we should send <unsupported-content/> inside too
|
||||
# TODO: delete this instance
|
||||
self.connection.connection.send(
|
||||
xmpp.Error(stanza, xmpp.NS_STANZAS + 'feature-not-implemented'))
|
||||
self.connection.deleteJingle(self)
|
||||
|
@ -176,6 +221,9 @@ class JingleSession(object):
|
|||
|
||||
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):
|
||||
''' Broadcast the stanza contents to proper content handlers. '''
|
||||
for content in jingle.iterTags('content'):
|
||||
|
@ -198,47 +246,47 @@ class JingleSession(object):
|
|||
'sid': self.sid})
|
||||
return stanza, jingle
|
||||
|
||||
def __appendContent(self, jingle, content, full=True):
|
||||
def __appendContent(self, jingle, content):
|
||||
''' Append <content/> element to <jingle/> element,
|
||||
with (full=True) or without (full=False) <content/>
|
||||
children. '''
|
||||
if full:
|
||||
jingle.addChild(node=content.toXML())
|
||||
else:
|
||||
jingle.addChild('content',
|
||||
attrs={'name': content.name, 'creator': content.creator})
|
||||
jingle.addChild('content',
|
||||
attrs={'name': content.name, 'creator': content.creator})
|
||||
|
||||
def __appendContents(self, jingle, full=True):
|
||||
def __appendContents(self, jingle):
|
||||
''' Append all <content/> elements to <jingle/>.'''
|
||||
# TODO: integrate with __appendContent?
|
||||
# TODO: parameters 'name', 'content'?
|
||||
for content in self.contents.values():
|
||||
self.__appendContent(jingle, content, full=full)
|
||||
self.__appendContent(jingle, content)
|
||||
|
||||
def __sessionInitiate(self):
|
||||
assert self.state==JingleStates.ended
|
||||
stanza, jingle = self.__makeJingle('session-initiate')
|
||||
self.__appendContents(jingle)
|
||||
self.__broadcastCB(stanza, jingle, None, 'session-initiate-sent')
|
||||
self.connection.connection.send(stanza)
|
||||
|
||||
def __sessionAccept(self):
|
||||
assert self.state==JingleStates.pending
|
||||
stanza, jingle = self.__jingle('session-accept')
|
||||
self.__appendContents(jingle, False)
|
||||
stanza, jingle = self.__makeJingle('session-accept')
|
||||
self.__appendContents(jingle)
|
||||
self.__broadcastCB(stanza, jingle, None, 'session-accept-sent')
|
||||
self.connection.connection.send(stanza)
|
||||
self.state=JingleStates.active
|
||||
|
||||
def __sessionInfo(self, payload=None):
|
||||
assert self.state!=JingleStates.ended
|
||||
stanza, jingle = self.__jingle('session-info')
|
||||
stanza, jingle = self.__makeJingle('session-info')
|
||||
if payload:
|
||||
jingle.addChild(node=payload)
|
||||
self.connection.connection.send(stanza)
|
||||
|
||||
def __sessionTerminate(self):
|
||||
assert self.state!=JingleStates.ended
|
||||
stanza, jingle = self.__jingle('session-terminate')
|
||||
stanza, jingle = self.__makeJingle('session-terminate')
|
||||
self.connection.connection.send(stanza)
|
||||
self.__broadcastCB(stanza, jingle, None, 'session-terminate-sent')
|
||||
|
||||
def __contentAdd(self):
|
||||
assert self.state==JingleStates.active
|
||||
|
@ -252,6 +300,12 @@ class JingleSession(object):
|
|||
def __contentRemove(self):
|
||||
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):
|
||||
assert self.state!=JingleStates.ended
|
||||
stanza, jingle = self.__makeJingle('transport-info')
|
||||
|
@ -270,6 +324,7 @@ class JingleContent(object):
|
|||
# (a JingleContent not added to session shouldn't send anything)
|
||||
#self.creator = None
|
||||
#self.name = None
|
||||
self.negotiated = False # is this content already negotiated?
|
||||
|
||||
class JingleVoiP(JingleContent):
|
||||
''' Jingle VoiP sessions consist of audio content transported
|
||||
|
@ -288,22 +343,34 @@ class JingleVoiP(JingleContent):
|
|||
def stanzaCB(self, stanza, content, error, action):
|
||||
''' Called when something related to our content was sent by peer. '''
|
||||
callbacks = {
|
||||
# these are called when *we* get stanzas
|
||||
'content-accept': [self.__getRemoteCodecsCB],
|
||||
'content-add': [],
|
||||
'content-modify': [],
|
||||
'content-remove': [],
|
||||
'session-accept': [self.__getRemoteCodecsCB],
|
||||
'session-accept': [self.__getRemoteCodecsCB, self.__startMic],
|
||||
'session-info': [],
|
||||
'session-initiate': [self.__getRemoteCodecsCB],
|
||||
'session-terminate': [],
|
||||
'session-terminate': [self.__stop],
|
||||
'transport-info': [self.__transportInfoCB],
|
||||
'iq-result': [],
|
||||
'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]
|
||||
for callback in callbacks:
|
||||
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):
|
||||
''' Get peer codecs from what we get from peer. '''
|
||||
if self.got_codecs: return
|
||||
|
||||
codecs = []
|
||||
|
@ -362,51 +429,29 @@ class JingleVoiP(JingleContent):
|
|||
attrs={'name': self.name, 'creator': self.creator, 'profile': 'RTP/AVP'},
|
||||
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_new_active_candidate_pair(self, stream, native, remote): pass
|
||||
def on_p2pstream_codec_changed(self, stream, codecid): pass
|
||||
def on_p2pstream_native_candidates_prepared(self, *whatever):
|
||||
for candidate in self.p2pstream.get_native_candidate_list():
|
||||
self.send_candidate(candidate)
|
||||
pass
|
||||
|
||||
def on_p2pstream_state_changed(self, stream, state, dir):
|
||||
if state==farsight.STREAM_STATE_CONNECTED:
|
||||
stream.signal_native_candidates_prepared()
|
||||
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):
|
||||
candidates = p2pstream.get_native_candidate(candidate_id)
|
||||
|
||||
for candidate in candidates:
|
||||
self.send_candidate(candidate)
|
||||
|
||||
def send_candidate(self, candidate):
|
||||
attrs={
|
||||
'component': candidate['component'],
|
||||
|
@ -442,6 +487,85 @@ class JingleVoiP(JingleContent):
|
|||
else: 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):
|
||||
''' This object depends on that it is a part of Connection class. '''
|
||||
def __init__(self):
|
||||
|
@ -484,7 +608,6 @@ class ConnectionJingle(object):
|
|||
|
||||
# do we need to create a new jingle object
|
||||
if (jid, sid) not in self.__sessions:
|
||||
# TODO: we should check its type here...
|
||||
newjingle = JingleSession(con=self, weinitiate=False, jid=jid, sid=sid)
|
||||
self.addJingle(newjingle)
|
||||
|
||||
|
@ -501,3 +624,8 @@ class ConnectionJingle(object):
|
|||
self.addJingle(jingle)
|
||||
jingle.addContent('voice', JingleVoiP(jingle))
|
||||
jingle.startSession()
|
||||
def getJingleSession(self, jid, sid):
|
||||
try:
|
||||
return self.__sessions[(jid, sid)]
|
||||
except KeyError:
|
||||
raise NoSuchSession
|
||||
|
|
|
@ -3298,3 +3298,37 @@ class AdvancedNotificationsWindow:
|
|||
|
||||
def on_close_window(self, widget):
|
||||
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()
|
||||
|
|
36
src/gajim.py
36
src/gajim.py
|
@ -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,
|
||||
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):
|
||||
'''Check idle status and change that status if needed'''
|
||||
if not self.sleeper.poll():
|
||||
|
@ -2192,6 +2227,7 @@ class Interface:
|
|||
'SEARCH_FORM': self.handle_event_search_form,
|
||||
'SEARCH_RESULT': self.handle_event_search_result,
|
||||
'RESOURCE_CONFLICT': self.handle_event_resource_conflict,
|
||||
'JINGLE_INCOMING': self.handle_event_jingle_incoming,
|
||||
}
|
||||
gajim.handlers = self.handlers
|
||||
|
||||
|
|
Loading…
Reference in New Issue