Jingle: UI entry point and lots of small changes.
This commit is contained in:
parent
9b378d625a
commit
459c73f961
|
@ -65,6 +65,15 @@
|
|||
</widget>
|
||||
</child>
|
||||
|
||||
<child>
|
||||
<widget class="GtkMenuItem" id="start_voip_menuitem">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">Start _Voice chat</property>
|
||||
<property name="use_underline">True</property>
|
||||
<signal name="activate" handler="_on_start_voip_menuitem_activate" last_modification_time="Tue, 03 Jan 2006 04:26:46 GMT"/>
|
||||
</widget>
|
||||
</child>
|
||||
|
||||
<child>
|
||||
<widget class="GtkImageMenuItem" id="add_to_roster_menuitem">
|
||||
<property name="visible">True</property>
|
||||
|
|
|
@ -1191,6 +1191,10 @@ class ChatControl(ChatControlBase):
|
|||
gajim.config.set_per('contacts', self.contact.jid, 'gpg_enabled',
|
||||
widget.get_active())
|
||||
|
||||
def _on_start_voip_menuitem_activate(self, *things):
|
||||
print 'Start VoiP'
|
||||
gajim.connections[self.account].startVoiP(self.contact.jid)
|
||||
|
||||
def _update_gpg(self):
|
||||
tb = self.xml.get_widget('gpg_togglebutton')
|
||||
# we can do gpg
|
||||
|
@ -1533,6 +1537,7 @@ class ChatControl(ChatControlBase):
|
|||
|
||||
history_menuitem = xml.get_widget('history_menuitem')
|
||||
toggle_gpg_menuitem = xml.get_widget('toggle_gpg_menuitem')
|
||||
start_voip_menuitem = xml.get_widget('start_voip_menuitem')
|
||||
add_to_roster_menuitem = xml.get_widget('add_to_roster_menuitem')
|
||||
send_file_menuitem = xml.get_widget('send_file_menuitem')
|
||||
information_menuitem = xml.get_widget('information_menuitem')
|
||||
|
@ -1583,6 +1588,9 @@ class ChatControl(ChatControlBase):
|
|||
id = toggle_gpg_menuitem.connect('activate',
|
||||
self._on_toggle_gpg_menuitem_activate)
|
||||
self.handlers[id] = toggle_gpg_menuitem
|
||||
id = start_voip_menuitem.connect('activate',
|
||||
self._on_start_voip_menuitem_activate)
|
||||
self.handlers[id] = start_voip_menuitem
|
||||
id = information_menuitem.connect('activate',
|
||||
self._on_contact_information_menuitem_activate)
|
||||
self.handlers[id] = information_menuitem
|
||||
|
|
|
@ -38,6 +38,7 @@ from common import exceptions
|
|||
from common.commands import ConnectionCommands
|
||||
from common.pubsub import ConnectionPubSub
|
||||
from common.caps import ConnectionCaps
|
||||
from common.jingle import ConnectionJingle
|
||||
|
||||
STATUS_LIST = ['offline', 'connecting', 'online', 'chat', 'away', 'xa', 'dnd',
|
||||
'invisible', 'error']
|
||||
|
@ -1174,12 +1175,13 @@ class ConnectionVcard:
|
|||
#('VCARD', {entry1: data, entry2: {entry21: data, ...}, ...})
|
||||
self.dispatch('VCARD', vcard)
|
||||
|
||||
class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco, ConnectionCommands, ConnectionPubSub, ConnectionCaps):
|
||||
class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco, ConnectionCommands, ConnectionPubSub, ConnectionCaps, ConnectionJingle):
|
||||
def __init__(self):
|
||||
ConnectionVcard.__init__(self)
|
||||
ConnectionBytestream.__init__(self)
|
||||
ConnectionCommands.__init__(self)
|
||||
ConnectionPubSub.__init__(self)
|
||||
ConnectionJingle.__init__(self)
|
||||
self.gmail_url=None
|
||||
# List of IDs we are waiting answers for {id: (type_of_request, data), }
|
||||
self.awaiting_answers = {}
|
||||
|
@ -2099,6 +2101,10 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
|
|||
con.RegisterHandler('iq', self._search_fields_received, 'result',
|
||||
common.xmpp.NS_SEARCH)
|
||||
con.RegisterHandler('iq', self._PubSubCB, 'result')
|
||||
con.RegisterHandler('iq', self._JingleCB, 'result')
|
||||
con.RegisterHandler('iq', self._JingleCB, 'error')
|
||||
con.RegisterHandler('iq', self._JingleCB, 'set',
|
||||
common.xmpp.NS_JINGLE)
|
||||
con.RegisterHandler('iq', self._ErrorCB, 'error')
|
||||
con.RegisterHandler('iq', self._IqCB)
|
||||
con.RegisterHandler('iq', self._StanzaArrivedCB)
|
||||
|
|
|
@ -12,19 +12,24 @@
|
|||
##
|
||||
''' Handles the jingle signalling protocol. '''
|
||||
|
||||
import gajim
|
||||
import xmpp
|
||||
|
||||
import meta
|
||||
|
||||
class JingleStates(object):
|
||||
''' States in which jingle session may exist. '''
|
||||
ended=0
|
||||
pending=1
|
||||
active=2
|
||||
|
||||
class WrongState(exception): pass
|
||||
class NoCommonCodec(exception): pass
|
||||
class Exception(object): pass
|
||||
class WrongState(Exception): pass
|
||||
class NoCommonCodec(Exception): pass
|
||||
|
||||
class JingleSession(object):
|
||||
''' This represents one jingle session. '''
|
||||
__metaclass__=meta.VerboseClassType
|
||||
def __init__(self, con, weinitiate, jid):
|
||||
''' con -- connection object,
|
||||
weinitiate -- boolean, are we the initiator?
|
||||
|
@ -32,48 +37,59 @@ class JingleSession(object):
|
|||
self.contents={} # negotiated contents
|
||||
self.connection=con # connection to use
|
||||
# our full jid
|
||||
self.ourjid=gajim.get_full_jid_from_account(self.connection.name)
|
||||
self.jid=jid # jid we connect to
|
||||
self.ourjid=gajim.get_jid_from_account(self.connection.name)+'/'+con.server_resource
|
||||
self.peerjid=jid # jid we connect to
|
||||
# jid we use as the initiator
|
||||
self.initiator=weinitiate and self.ourjid or self.jid
|
||||
self.initiator=weinitiate and self.ourjid or self.peerjid
|
||||
# jid we use as the responder
|
||||
self.responder=weinitiate and self.jid or self.ourjid
|
||||
self.responder=weinitiate and self.peerjid or self.ourjid
|
||||
# are we an initiator?
|
||||
self.weinitiate=weinitiate
|
||||
# what state is session in? (one from JingleStates)
|
||||
self.state=JingleStates.ended
|
||||
self.sid=con.getAnID() # sessionid
|
||||
self.sid=con.connection.getAnID() # sessionid
|
||||
|
||||
# callbacks to call on proper contents
|
||||
# use .prepend() to add new callbacks
|
||||
self.callbacks=dict((key, [self.__defaultCB]) for key in
|
||||
('content-accept', 'content-add', 'content-modify',
|
||||
('content-add', 'content-modify',
|
||||
'content-remove', 'session-accept', 'session-info',
|
||||
'session-initiate', 'session-terminate',
|
||||
'transport-info'))
|
||||
self.callbacks['iq-result']=[]
|
||||
self.callbacks['iq-error']=[]
|
||||
|
||||
self.callbacks['content-accept']=[self.__contentAcceptCB, self.__defaultCB]
|
||||
|
||||
''' Middle-level functions to manage contents. Handle local content
|
||||
cache and send change notifications. '''
|
||||
def addContent(self, name, description, transport, profile=None):
|
||||
def addContent(self, name, content, initiator='we'):
|
||||
''' Add new content to session. If the session is active,
|
||||
this will send proper stanza to update session.
|
||||
The protocol prohibits changing that when pending.'''
|
||||
The protocol prohibits changing that when pending.
|
||||
Initiator must be one of ('we', 'peer', 'initiator', 'responder')'''
|
||||
if self.state==JingleStates.pending:
|
||||
raise WrongState
|
||||
|
||||
content={'creator': 'initiator',
|
||||
'name': name,
|
||||
'description': description,
|
||||
'transport': transport}
|
||||
if profile is not None:
|
||||
content['profile']=profile
|
||||
self.contents[('initiator', name)]=content
|
||||
if (initiator=='we' and self.weinitiate) or (initiator=='peer' and not self.weinitiate):
|
||||
initiator='initiator'
|
||||
elif (initiator=='peer' and self.weinitiate) or (initiator=='we' and not self.weinitiate):
|
||||
initiator='responder'
|
||||
content.creator = initiator
|
||||
content.name = name
|
||||
self.contents[(initiator,name)]=content
|
||||
|
||||
if self.state==JingleStates.active:
|
||||
pass # TODO: send proper stanza, shouldn't be needed now
|
||||
|
||||
def removeContent(self, creator, name):
|
||||
''' We do not need this now '''
|
||||
pass
|
||||
|
||||
def modifyContent(self, creator, name, *someother):
|
||||
''' We do not need this now '''
|
||||
pass
|
||||
|
||||
''' Middle-level function to do stanza exchange. '''
|
||||
def startSession(self):
|
||||
''' Start session. '''
|
||||
|
@ -92,7 +108,7 @@ class JingleSession(object):
|
|||
if error:
|
||||
# it's an iq-error stanza
|
||||
callables = 'iq-error'
|
||||
else if jingle:
|
||||
elif jingle:
|
||||
# it's a jingle action
|
||||
action = jingle.getAttr('action')
|
||||
callables = action
|
||||
|
@ -115,11 +131,20 @@ class JingleSession(object):
|
|||
self.connection.send(response)
|
||||
raise xmpp.NodeProcessed
|
||||
|
||||
def __contentAcceptCB(self, stanza, jingle, error):
|
||||
''' Called when we get content-accept stanza or equivalent one
|
||||
(like session-accept).'''
|
||||
# check which contents are accepted, call their callbacks
|
||||
for content in jingle.iterTags('content'):
|
||||
creator = content['creator']
|
||||
name = content['name']
|
||||
|
||||
|
||||
''' Methods that make/send proper pieces of XML. They check if the session
|
||||
is in appropriate state. '''
|
||||
def makeJingle(self, action):
|
||||
def __makeJingle(self, action):
|
||||
stanza = xmpp.Iq(typ='set', to=xmpp.JID(self.jid))
|
||||
jingle = stanza.addChild('jingle', attrs=
|
||||
jingle = stanza.addChild('jingle', attrs={
|
||||
'xmlns': 'http://www.xmpp.org/extensions/xep-0166.html#ns',
|
||||
'action': action,
|
||||
'initiator': self.initiator,
|
||||
|
@ -127,21 +152,18 @@ class JingleSession(object):
|
|||
'sid': self.sid})
|
||||
return stanza, jingle
|
||||
|
||||
def appendContent(self, jingle, content, full=True):
|
||||
def __appendContent(self, jingle, content, full=True):
|
||||
''' Append <content/> element to <jingle/> element,
|
||||
with (full=True) or without (full=False) <content/>
|
||||
children. '''
|
||||
c=jingle.addChild('content', attrs={
|
||||
'creator': content['creator'],
|
||||
'name': content['name']})
|
||||
if 'profile' in content:
|
||||
c['profile']=content['profile']
|
||||
if full:
|
||||
c.addChild(node=content['description'])
|
||||
c.addChild(node=content['transport'])
|
||||
jingle.addChild(node=content.toXML())
|
||||
else:
|
||||
jingle.addChild('content',
|
||||
attrs={'name': content.name, 'creator': content.creator})
|
||||
return c
|
||||
|
||||
def appendContents(self, jingle, full=True):
|
||||
def __appendContents(self, jingle, full=True):
|
||||
''' Append all <content/> elements to <jingle/>.'''
|
||||
# TODO: integrate with __appendContent?
|
||||
# TODO: parameters 'name', 'content'?
|
||||
|
@ -150,6 +172,9 @@ class JingleSession(object):
|
|||
|
||||
def __sessionInitiate(self):
|
||||
assert self.state==JingleStates.ended
|
||||
stanza, jingle = self.__makeJingle('session-initiate')
|
||||
self.__appendContents(jingle)
|
||||
self.connection.send(jingle)
|
||||
|
||||
def __sessionAccept(self):
|
||||
assert self.state==JingleStates.pending
|
||||
|
@ -197,7 +222,7 @@ class JingleSession(object):
|
|||
self.sid = jingle['sid']
|
||||
for element in jingle.iterTags('content'):
|
||||
content={'creator': 'initiator',
|
||||
'name': element['name']
|
||||
'name': element['name'],
|
||||
'description': element.getTag('description'),
|
||||
'transport': element.getTag('transport')}
|
||||
if element.has_attr('profile'):
|
||||
|
@ -207,6 +232,7 @@ class JingleSession(object):
|
|||
def sessionTerminateCB(self, stanza): pass
|
||||
|
||||
class JingleAudioSession(object):
|
||||
__metaclass__=meta.VerboseClassType
|
||||
class Codec(object):
|
||||
''' This class keeps description of a single codec. '''
|
||||
def __init__(self, name, id=None, **params):
|
||||
|
@ -232,15 +258,15 @@ class JingleAudioSession(object):
|
|||
attrs=self.attrs,
|
||||
payload=(xmpp.Node('parameter', {'name': k, 'value': v}) for k,v in self.params))
|
||||
|
||||
def __init__(self, con, weinitiate, jid):
|
||||
JingleSession.__init__(self, con, weinitiate, jid)
|
||||
if weinitiate:
|
||||
pass #add voice content
|
||||
self.callbacks['session-initiate'].prepend(
|
||||
def __init__(self, content):
|
||||
self.content = content
|
||||
|
||||
self.initiator_codecs=[]
|
||||
self.responder_codecs=[]
|
||||
|
||||
def sessionInitiateCB(self, stanza, ourcontent):
|
||||
pass
|
||||
|
||||
''' "Negotiation" of codecs... simply presenting what *we* can do, nothing more... '''
|
||||
def getOurCodecs(self, other=None):
|
||||
''' Get a list of codecs we support. Try to get them in the same
|
||||
|
@ -282,16 +308,46 @@ class JingleAudioSession(object):
|
|||
xmlns=xmpp.NS_JINGLE_AUDIO,
|
||||
payload=(codec.toXML() for codec in codecs))
|
||||
|
||||
def toXML(self):
|
||||
if not self.initiator_codecs:
|
||||
# we are the initiator, so just send our codecs
|
||||
self.initiator_codecs = self.getOurCodecs()
|
||||
return self.__codecsList(self.initiator_codecs)
|
||||
else:
|
||||
# we are the responder, we SHOULD adjust our codec list
|
||||
self.responder_codecs = self.getOurCodecs(self.initiator_codecs)
|
||||
return self.__codecsList(self.responder_codecs)
|
||||
|
||||
class JingleICEUDPSession(object):
|
||||
def __init__(self, con, weinitiate, jid):
|
||||
__metaclass__=meta.VerboseClassType
|
||||
def __init__(self, content):
|
||||
self.content = content
|
||||
|
||||
def _sessionInitiateCB(self):
|
||||
''' Called when we initiate the session. '''
|
||||
pass
|
||||
|
||||
class JingleVoiP(JingleSession):
|
||||
def toXML(self):
|
||||
''' ICE-UDP doesn't send much in its transport stanza... '''
|
||||
return xmpp.Node('transport', xmlns=xmpp.JINGLE_ICE_UDP)
|
||||
|
||||
class JingleVoiP(object):
|
||||
''' Jingle VoiP sessions consist of audio content transported
|
||||
over an ICE UDP protocol. '''
|
||||
def __init__(*data):
|
||||
JingleAudioSession.__init__(*data)
|
||||
JingleICEUDPSession.__init__(*data)
|
||||
__metaclass__=meta.VerboseClassType
|
||||
def __init__(self):
|
||||
self.audio = JingleAudioSession(self)
|
||||
self.transport = JingleICEUDPSession(self)
|
||||
|
||||
def toXML(self):
|
||||
''' Return proper XML for <content/> element. '''
|
||||
return xmpp.Node('content',
|
||||
attrs={'name': self.name, 'creator': self.creator, 'profile': 'RTP/AVP'},
|
||||
childs=[self.audio.toXML(), self.transport.toXML()])
|
||||
|
||||
def _sessionInitiateCB(self):
|
||||
''' Called when we initiate the session. '''
|
||||
self.transport._sessionInitiateCB()
|
||||
|
||||
class ConnectionJingle(object):
|
||||
''' This object depends on that it is a part of Connection class. '''
|
||||
|
@ -307,13 +363,13 @@ class ConnectionJingle(object):
|
|||
''' Add a jingle session to a jingle stanza dispatcher
|
||||
jingle - a JingleSession object.
|
||||
'''
|
||||
self.__sessions[(jingle.jid, jingle.sid)]=jingle
|
||||
self.__sessions[(jingle.peerjid, jingle.sid)]=jingle
|
||||
|
||||
def deleteJingle(self, jingle):
|
||||
''' Remove a jingle session from a jingle stanza dispatcher '''
|
||||
del self.__session[(jingle.jid, jingle.sid)]
|
||||
del self.__session[(jingle.peerjid, jingle.sid)]
|
||||
|
||||
def _jingleCB(self, con, stanza):
|
||||
def _JingleCB(self, con, stanza):
|
||||
''' The jingle stanza dispatcher.
|
||||
Route jingle stanza to proper JingleSession object,
|
||||
or create one if it is a new session.
|
||||
|
@ -330,6 +386,7 @@ class ConnectionJingle(object):
|
|||
raise xmpp.NodeProcessed
|
||||
|
||||
jingle = stanza.getTag('jingle')
|
||||
if not jingle: return
|
||||
sid = jingle.getAttr('sid')
|
||||
|
||||
# do we need to create a new jingle object
|
||||
|
@ -341,5 +398,11 @@ class ConnectionJingle(object):
|
|||
# we already have such session in dispatcher...
|
||||
return self.__sessions[(jid, sid)].stanzaCB(stanza)
|
||||
|
||||
def addJingleIqCallback(jid, id, jingle):
|
||||
def addJingleIqCallback(self, jid, id, jingle):
|
||||
self.__iq_responses[(jid, id)]=jingle
|
||||
|
||||
def startVoiP(self, jid):
|
||||
jingle = JingleSession(self, weinitiate=True, jid=jid)
|
||||
self.addJingle(jingle)
|
||||
jingle.addContent('voice', JingleVoiP())
|
||||
jingle.startSession()
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
import types
|
||||
|
||||
class VerboseClassType(type):
|
||||
indent = ''
|
||||
|
||||
def __init__(cls, name, bases, dict):
|
||||
super(VerboseClassType, cls).__init__(cls, name, bases, dict)
|
||||
new = {}
|
||||
print 'Initializing new class %s:' % cls
|
||||
for fname, fun in dict.iteritems():
|
||||
wrap = hasattr(fun, '__call__')
|
||||
print '%s%s is %s, we %s wrap it.' % \
|
||||
(cls.__class__.indent, fname, fun, wrap and 'will' or "won't")
|
||||
if not wrap: continue
|
||||
setattr(cls, fname, cls.wrap(name, fname, fun))
|
||||
|
||||
def wrap(cls, name, fname, fun):
|
||||
def verbose(*a, **b):
|
||||
args = ', '.join(map(repr, a)+map(lambda x:'%s=%r'%x, b.iteritems()))
|
||||
print '%s%s.%s(%s):' % (cls.__class__.indent, name, fname, args)
|
||||
cls.__class__.indent += '| '
|
||||
r = fun(*a, **b)
|
||||
cls.__class__.indent = cls.__class__.indent[:-4]
|
||||
print '%s+=%r' % (cls.__class__.indent, r)
|
||||
return r
|
||||
verbose.__name__ = fname
|
||||
return verbose
|
||||
|
||||
def nested_property(f):
|
||||
ret = f()
|
||||
p = {}
|
||||
for v in ('fget', 'fset', 'fdel', 'doc'):
|
||||
if v in ret: p[v]=ret[v]
|
||||
return property(**p)
|
|
@ -55,6 +55,10 @@ NS_HTTP_BIND ='http://jabber.org/protocol/httpbind' # XEP-01
|
|||
NS_IBB ='http://jabber.org/protocol/ibb'
|
||||
NS_INVISIBLE ='presence-invisible' # Jabberd2
|
||||
NS_IQ ='iq' # Jabberd2
|
||||
NS_JINGLE ='http://www.xmpp.org/extensions/xep-0166.html#ns' # XEP-0166
|
||||
NS_JINGLE_AUDIO ='http://www.xmpp.org/extensions/xep-0167.html#ns' # XEP-0167
|
||||
NS_JINGLE_RAW_UDP='http://www.xmpp.org/extensions/xep-0177.html#ns' # XEP-0177
|
||||
NS_JINGLE_ICE_UDP='http://www.xmpp.org/extensions/xep-0176.html#ns-udp' # XEP-0176
|
||||
NS_LAST ='jabber:iq:last'
|
||||
NS_MESSAGE ='message' # Jabberd2
|
||||
NS_MOOD ='http://jabber.org/protocol/mood' # XEP-0107
|
||||
|
|
Loading…
Reference in New Issue