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>
</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>

View File

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

View File

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

View File

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

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_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