Jingle: UI entry point and lots of small changes.

This commit is contained in:
Tomasz Melcer 2007-08-06 23:19:57 +00:00
parent 9b378d625a
commit 459c73f961
6 changed files with 171 additions and 45 deletions

View File

@ -65,6 +65,15 @@
</widget> </widget>
</child> </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> <child>
<widget class="GtkImageMenuItem" id="add_to_roster_menuitem"> <widget class="GtkImageMenuItem" id="add_to_roster_menuitem">
<property name="visible">True</property> <property name="visible">True</property>

View File

@ -1191,6 +1191,10 @@ class ChatControl(ChatControlBase):
gajim.config.set_per('contacts', self.contact.jid, 'gpg_enabled', gajim.config.set_per('contacts', self.contact.jid, 'gpg_enabled',
widget.get_active()) 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): def _update_gpg(self):
tb = self.xml.get_widget('gpg_togglebutton') tb = self.xml.get_widget('gpg_togglebutton')
# we can do gpg # we can do gpg
@ -1533,6 +1537,7 @@ class ChatControl(ChatControlBase):
history_menuitem = xml.get_widget('history_menuitem') history_menuitem = xml.get_widget('history_menuitem')
toggle_gpg_menuitem = xml.get_widget('toggle_gpg_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') add_to_roster_menuitem = xml.get_widget('add_to_roster_menuitem')
send_file_menuitem = xml.get_widget('send_file_menuitem') send_file_menuitem = xml.get_widget('send_file_menuitem')
information_menuitem = xml.get_widget('information_menuitem') information_menuitem = xml.get_widget('information_menuitem')
@ -1583,6 +1588,9 @@ class ChatControl(ChatControlBase):
id = toggle_gpg_menuitem.connect('activate', id = toggle_gpg_menuitem.connect('activate',
self._on_toggle_gpg_menuitem_activate) self._on_toggle_gpg_menuitem_activate)
self.handlers[id] = toggle_gpg_menuitem 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', id = information_menuitem.connect('activate',
self._on_contact_information_menuitem_activate) self._on_contact_information_menuitem_activate)
self.handlers[id] = information_menuitem self.handlers[id] = information_menuitem

View File

@ -38,6 +38,7 @@ from common import exceptions
from common.commands import ConnectionCommands from common.commands import ConnectionCommands
from common.pubsub import ConnectionPubSub from common.pubsub import ConnectionPubSub
from common.caps import ConnectionCaps from common.caps import ConnectionCaps
from common.jingle import ConnectionJingle
STATUS_LIST = ['offline', 'connecting', 'online', 'chat', 'away', 'xa', 'dnd', STATUS_LIST = ['offline', 'connecting', 'online', 'chat', 'away', 'xa', 'dnd',
'invisible', 'error'] 'invisible', 'error']
@ -1174,12 +1175,13 @@ class ConnectionVcard:
#('VCARD', {entry1: data, entry2: {entry21: data, ...}, ...}) #('VCARD', {entry1: data, entry2: {entry21: data, ...}, ...})
self.dispatch('VCARD', vcard) self.dispatch('VCARD', vcard)
class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco, ConnectionCommands, ConnectionPubSub, ConnectionCaps): class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco, ConnectionCommands, ConnectionPubSub, ConnectionCaps, ConnectionJingle):
def __init__(self): def __init__(self):
ConnectionVcard.__init__(self) ConnectionVcard.__init__(self)
ConnectionBytestream.__init__(self) ConnectionBytestream.__init__(self)
ConnectionCommands.__init__(self) ConnectionCommands.__init__(self)
ConnectionPubSub.__init__(self) ConnectionPubSub.__init__(self)
ConnectionJingle.__init__(self)
self.gmail_url=None self.gmail_url=None
# List of IDs we are waiting answers for {id: (type_of_request, data), } # List of IDs we are waiting answers for {id: (type_of_request, data), }
self.awaiting_answers = {} self.awaiting_answers = {}
@ -2099,6 +2101,10 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
con.RegisterHandler('iq', self._search_fields_received, 'result', con.RegisterHandler('iq', self._search_fields_received, 'result',
common.xmpp.NS_SEARCH) common.xmpp.NS_SEARCH)
con.RegisterHandler('iq', self._PubSubCB, 'result') 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._ErrorCB, 'error')
con.RegisterHandler('iq', self._IqCB) con.RegisterHandler('iq', self._IqCB)
con.RegisterHandler('iq', self._StanzaArrivedCB) con.RegisterHandler('iq', self._StanzaArrivedCB)

View File

@ -12,19 +12,24 @@
## ##
''' Handles the jingle signalling protocol. ''' ''' Handles the jingle signalling protocol. '''
import gajim
import xmpp import xmpp
import meta
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 WrongState(exception): pass class Exception(object): pass
class NoCommonCodec(exception): pass class WrongState(Exception): pass
class NoCommonCodec(Exception): pass
class JingleSession(object): class JingleSession(object):
''' This represents one jingle session. ''' ''' This represents one jingle session. '''
__metaclass__=meta.VerboseClassType
def __init__(self, con, weinitiate, jid): def __init__(self, con, weinitiate, jid):
''' con -- connection object, ''' con -- connection object,
weinitiate -- boolean, are we the initiator? weinitiate -- boolean, are we the initiator?
@ -32,48 +37,59 @@ class JingleSession(object):
self.contents={} # negotiated contents self.contents={} # negotiated contents
self.connection=con # connection to use self.connection=con # connection to use
# our full jid # our full jid
self.ourjid=gajim.get_full_jid_from_account(self.connection.name) self.ourjid=gajim.get_jid_from_account(self.connection.name)+'/'+con.server_resource
self.jid=jid # jid we connect to self.peerjid=jid # jid we connect to
# jid we use as the initiator # 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 # 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? # are we an initiator?
self.weinitiate=weinitiate self.weinitiate=weinitiate
# what state is session in? (one from JingleStates) # what state is session in? (one from JingleStates)
self.state=JingleStates.ended self.state=JingleStates.ended
self.sid=con.getAnID() # sessionid self.sid=con.connection.getAnID() # sessionid
# callbacks to call on proper contents # callbacks to call on proper contents
# use .prepend() to add new callbacks # use .prepend() to add new callbacks
self.callbacks=dict((key, [self.__defaultCB]) for key in 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', 'content-remove', 'session-accept', 'session-info',
'session-initiate', 'session-terminate', 'session-initiate', 'session-terminate',
'transport-info')) 'transport-info'))
self.callbacks['iq-result']=[] self.callbacks['iq-result']=[]
self.callbacks['iq-error']=[] self.callbacks['iq-error']=[]
self.callbacks['content-accept']=[self.__contentAcceptCB, self.__defaultCB]
''' 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, description, transport, profile=None): def addContent(self, name, content, initiator='we'):
''' Add new content to session. If the session is active, ''' Add new content to session. If the session is active,
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.
Initiator must be one of ('we', 'peer', 'initiator', 'responder')'''
if self.state==JingleStates.pending: if self.state==JingleStates.pending:
raise WrongState raise WrongState
content={'creator': 'initiator', if (initiator=='we' and self.weinitiate) or (initiator=='peer' and not self.weinitiate):
'name': name, initiator='initiator'
'description': description, elif (initiator=='peer' and self.weinitiate) or (initiator=='we' and not self.weinitiate):
'transport': transport} initiator='responder'
if profile is not None: content.creator = initiator
content['profile']=profile content.name = name
self.contents[('initiator', name)]=content self.contents[(initiator,name)]=content
if self.state==JingleStates.active: if self.state==JingleStates.active:
pass # TODO: send proper stanza, shouldn't be needed now 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. ''' ''' Middle-level function to do stanza exchange. '''
def startSession(self): def startSession(self):
''' Start session. ''' ''' Start session. '''
@ -92,7 +108,7 @@ class JingleSession(object):
if error: if error:
# it's an iq-error stanza # it's an iq-error stanza
callables = 'iq-error' callables = 'iq-error'
else if jingle: elif jingle:
# it's a jingle action # it's a jingle action
action = jingle.getAttr('action') action = jingle.getAttr('action')
callables = action callables = action
@ -115,11 +131,20 @@ class JingleSession(object):
self.connection.send(response) self.connection.send(response)
raise xmpp.NodeProcessed 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 ''' Methods that make/send proper pieces of XML. They check if the session
is in appropriate state. ''' is in appropriate state. '''
def makeJingle(self, action): def __makeJingle(self, action):
stanza = xmpp.Iq(typ='set', to=xmpp.JID(self.jid)) 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', 'xmlns': 'http://www.xmpp.org/extensions/xep-0166.html#ns',
'action': action, 'action': action,
'initiator': self.initiator, 'initiator': self.initiator,
@ -127,21 +152,18 @@ 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, full=True):
''' 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. '''
c=jingle.addChild('content', attrs={
'creator': content['creator'],
'name': content['name']})
if 'profile' in content:
c['profile']=content['profile']
if full: if full:
c.addChild(node=content['description']) jingle.addChild(node=content.toXML())
c.addChild(node=content['transport']) else:
jingle.addChild('content',
attrs={'name': content.name, 'creator': content.creator})
return c return c
def appendContents(self, jingle, full=True): def __appendContents(self, jingle, full=True):
''' 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'?
@ -150,6 +172,9 @@ class JingleSession(object):
def __sessionInitiate(self): def __sessionInitiate(self):
assert self.state==JingleStates.ended assert self.state==JingleStates.ended
stanza, jingle = self.__makeJingle('session-initiate')
self.__appendContents(jingle)
self.connection.send(jingle)
def __sessionAccept(self): def __sessionAccept(self):
assert self.state==JingleStates.pending assert self.state==JingleStates.pending
@ -197,7 +222,7 @@ class JingleSession(object):
self.sid = jingle['sid'] self.sid = jingle['sid']
for element in jingle.iterTags('content'): for element in jingle.iterTags('content'):
content={'creator': 'initiator', content={'creator': 'initiator',
'name': element['name'] 'name': element['name'],
'description': element.getTag('description'), 'description': element.getTag('description'),
'transport': element.getTag('transport')} 'transport': element.getTag('transport')}
if element.has_attr('profile'): if element.has_attr('profile'):
@ -207,6 +232,7 @@ class JingleSession(object):
def sessionTerminateCB(self, stanza): pass def sessionTerminateCB(self, stanza): pass
class JingleAudioSession(object): class JingleAudioSession(object):
__metaclass__=meta.VerboseClassType
class Codec(object): class Codec(object):
''' This class keeps description of a single codec. ''' ''' This class keeps description of a single codec. '''
def __init__(self, name, id=None, **params): def __init__(self, name, id=None, **params):
@ -232,15 +258,15 @@ class JingleAudioSession(object):
attrs=self.attrs, attrs=self.attrs,
payload=(xmpp.Node('parameter', {'name': k, 'value': v}) for k,v in self.params)) payload=(xmpp.Node('parameter', {'name': k, 'value': v}) for k,v in self.params))
def __init__(self, con, weinitiate, jid): def __init__(self, content):
JingleSession.__init__(self, con, weinitiate, jid) self.content = content
if weinitiate:
pass #add voice content
self.callbacks['session-initiate'].prepend(
self.initiator_codecs=[] self.initiator_codecs=[]
self.responder_codecs=[] self.responder_codecs=[]
def sessionInitiateCB(self, stanza, ourcontent):
pass
''' "Negotiation" of codecs... simply presenting what *we* can do, nothing more... ''' ''' "Negotiation" of codecs... simply presenting what *we* can do, nothing more... '''
def getOurCodecs(self, other=None): def getOurCodecs(self, other=None):
''' Get a list of codecs we support. Try to get them in the same ''' 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, xmlns=xmpp.NS_JINGLE_AUDIO,
payload=(codec.toXML() for codec in codecs)) 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): 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 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 ''' Jingle VoiP sessions consist of audio content transported
over an ICE UDP protocol. ''' over an ICE UDP protocol. '''
def __init__(*data): __metaclass__=meta.VerboseClassType
JingleAudioSession.__init__(*data) def __init__(self):
JingleICEUDPSession.__init__(*data) 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): 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. '''
@ -307,13 +363,13 @@ class ConnectionJingle(object):
''' Add a jingle session to a jingle stanza dispatcher ''' Add a jingle session to a jingle stanza dispatcher
jingle - a JingleSession object. jingle - a JingleSession object.
''' '''
self.__sessions[(jingle.jid, jingle.sid)]=jingle self.__sessions[(jingle.peerjid, jingle.sid)]=jingle
def deleteJingle(self, jingle): def deleteJingle(self, jingle):
''' Remove a jingle session from a jingle stanza dispatcher ''' ''' 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. ''' The jingle stanza dispatcher.
Route jingle stanza to proper JingleSession object, Route jingle stanza to proper JingleSession object,
or create one if it is a new session. or create one if it is a new session.
@ -330,6 +386,7 @@ class ConnectionJingle(object):
raise xmpp.NodeProcessed raise xmpp.NodeProcessed
jingle = stanza.getTag('jingle') jingle = stanza.getTag('jingle')
if not jingle: return
sid = jingle.getAttr('sid') sid = jingle.getAttr('sid')
# do we need to create a new jingle object # do we need to create a new jingle object
@ -341,5 +398,11 @@ class ConnectionJingle(object):
# we already have such session in dispatcher... # we already have such session in dispatcher...
return self.__sessions[(jid, sid)].stanzaCB(stanza) return self.__sessions[(jid, sid)].stanzaCB(stanza)
def addJingleIqCallback(jid, id, jingle): def addJingleIqCallback(self, jid, id, jingle):
self.__iq_responses[(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()

36
src/common/meta.py Normal file
View File

@ -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)

View File

@ -55,6 +55,10 @@ NS_HTTP_BIND ='http://jabber.org/protocol/httpbind' # XEP-01
NS_IBB ='http://jabber.org/protocol/ibb' NS_IBB ='http://jabber.org/protocol/ibb'
NS_INVISIBLE ='presence-invisible' # Jabberd2 NS_INVISIBLE ='presence-invisible' # Jabberd2
NS_IQ ='iq' # 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_LAST ='jabber:iq:last'
NS_MESSAGE ='message' # Jabberd2 NS_MESSAGE ='message' # Jabberd2
NS_MOOD ='http://jabber.org/protocol/mood' # XEP-0107 NS_MOOD ='http://jabber.org/protocol/mood' # XEP-0107