|
|
|
/*
|
|
|
|
jinglevoicesession.cpp - Define a Jingle voice session.
|
|
|
|
|
|
|
|
Copyright (c) 2006 by Michaël Larouche <michael.larouche@kdemail.net>
|
|
|
|
|
|
|
|
Kopete (c) 2001-2006 by the Kopete developers <kopete-devel@kde.org>
|
|
|
|
|
|
|
|
*************************************************************************
|
|
|
|
* *
|
|
|
|
* This program is free software; you can redistribute it and/or modify *
|
|
|
|
* it under the terms of the GNU General Public License as published by *
|
|
|
|
* the Free Software Foundation; either version 2 of the License, or *
|
|
|
|
* (at your option) any later version. *
|
|
|
|
* *
|
|
|
|
*************************************************************************
|
|
|
|
*/
|
|
|
|
|
|
|
|
// libjingle before everything else to not clash with TQt
|
|
|
|
#define POSIX
|
|
|
|
#include "talk/xmpp/constants.h"
|
|
|
|
#include "talk/base/sigslot.h"
|
|
|
|
#include "talk/xmpp/jid.h"
|
|
|
|
#include "talk/xmllite/xmlelement.h"
|
|
|
|
#include "talk/xmllite/xmlprinter.h"
|
|
|
|
#include "talk/base/network.h"
|
|
|
|
#include "talk/p2p/base/session.h"
|
|
|
|
#include "talk/p2p/base/sessionmanager.h"
|
|
|
|
#include "talk/p2p/base/helpers.h"
|
|
|
|
#include "talk/p2p/client/basicportallocator.h"
|
|
|
|
#include "talk/p2p/client/sessionclient.h"
|
|
|
|
#include "talk/base/physicalsocketserver.h"
|
|
|
|
#include "talk/base/thread.h"
|
|
|
|
#include "talk/base/socketaddress.h"
|
|
|
|
#include "talk/session/phone/call.h"
|
|
|
|
#include "talk/session/phone/phonesessionclient.h"
|
|
|
|
#include "talk/session/sessionsendtask.h"
|
|
|
|
|
|
|
|
#include "jinglevoicesession.h"
|
|
|
|
#include "jinglesessionmanager.h"
|
|
|
|
|
|
|
|
// TQt includes
|
|
|
|
#include <tqdom.h>
|
|
|
|
|
|
|
|
// KDE includes
|
|
|
|
#include <kdebug.h>
|
|
|
|
|
|
|
|
// Kopete Jabber includes
|
|
|
|
#include "jabberaccount.h"
|
|
|
|
#include "jabberprotocol.h"
|
|
|
|
|
|
|
|
#include <xmpp.h>
|
|
|
|
#include <xmpp_xmlcommon.h>
|
|
|
|
|
|
|
|
#define JINGLE_NS "http://www.google.com/session"
|
|
|
|
#define JINGLE_VOICE_SESSION_NS "http://www.google.com/session/phone"
|
|
|
|
|
|
|
|
static bool hasPeer(const JingleVoiceSession::JidList &jidList, const XMPP::Jid &peer)
|
|
|
|
{
|
|
|
|
JingleVoiceSession::JidList::ConstIterator it, itEnd = jidList.constEnd();
|
|
|
|
for(it = jidList.constBegin(); it != itEnd; ++it)
|
|
|
|
{
|
|
|
|
if( (*it).compare(peer, true) )
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
//BEGIN SlotsProxy
|
|
|
|
/**
|
|
|
|
* This class is used to receive signals from libjingle,
|
|
|
|
* which is are not compatible with TQt signals.
|
|
|
|
* So it's a proxy between JingeVoiceSession(qt)<->linjingle class.
|
|
|
|
*/
|
|
|
|
class JingleVoiceSession::SlotsProxy : public sigslot::has_slots<>
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
SlotsProxy(JingleVoiceSession *tqparent)
|
|
|
|
: voiceSession(tqparent)
|
|
|
|
{}
|
|
|
|
|
|
|
|
void OnCallCreated(cricket::Call* call)
|
|
|
|
{
|
|
|
|
kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "SlotsProxy: CallCreated." << endl;
|
|
|
|
|
|
|
|
call->SignalSessionState.connect(this, &JingleVoiceSession::SlotsProxy::PhoneSessionStateChanged);
|
|
|
|
voiceSession->setCall(call);
|
|
|
|
}
|
|
|
|
|
|
|
|
void PhoneSessionStateChanged(cricket::Call *call, cricket::Session *session, cricket::Session::State state)
|
|
|
|
{
|
|
|
|
kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "State changed: " << state << endl;
|
|
|
|
|
|
|
|
XMPP::Jid jid(session->remote_address().c_str());
|
|
|
|
|
|
|
|
// Do nothing if the session do not contain a peers.
|
|
|
|
//if( !voiceSession->peers().tqcontains(jid) )
|
|
|
|
if( !hasPeer(voiceSession->peers(), jid) )
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (state == cricket::Session::STATE_INIT)
|
|
|
|
{}
|
|
|
|
else if (state == cricket::Session::STATE_SENTINITIATE)
|
|
|
|
{}
|
|
|
|
else if (state == cricket::Session::STATE_RECEIVEDINITIATE)
|
|
|
|
{
|
|
|
|
voiceSession->setCall(call);
|
|
|
|
}
|
|
|
|
else if (state == cricket::Session::STATE_SENTACCEPT)
|
|
|
|
{}
|
|
|
|
else if (state == cricket::Session::STATE_RECEIVEDACCEPT)
|
|
|
|
{
|
|
|
|
emit voiceSession->accepted();
|
|
|
|
}
|
|
|
|
else if (state == cricket::Session::STATE_SENTMODIFY)
|
|
|
|
{}
|
|
|
|
else if (state == cricket::Session::STATE_RECEIVEDMODIFY)
|
|
|
|
{
|
|
|
|
//qWarning(TQString("jinglevoicecaller.cpp: RECEIVEDMODIFY not implemented yet (was from %1)").tqarg(jid.full()));
|
|
|
|
}
|
|
|
|
else if (state == cricket::Session::STATE_SENTREJECT)
|
|
|
|
{}
|
|
|
|
else if (state == cricket::Session::STATE_RECEIVEDREJECT)
|
|
|
|
{
|
|
|
|
emit voiceSession->declined();
|
|
|
|
}
|
|
|
|
else if (state == cricket::Session::STATE_SENTREDIRECT)
|
|
|
|
{}
|
|
|
|
else if (state == cricket::Session::STATE_SENTTERMINATE)
|
|
|
|
{
|
|
|
|
emit voiceSession->terminated();
|
|
|
|
}
|
|
|
|
else if (state == cricket::Session::STATE_RECEIVEDTERMINATE)
|
|
|
|
{
|
|
|
|
emit voiceSession->terminated();
|
|
|
|
}
|
|
|
|
else if (state == cricket::Session::STATE_INPROGRESS)
|
|
|
|
{
|
|
|
|
emit voiceSession->sessionStarted();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void OnSendingStanza(cricket::SessionClient*, const buzz::XmlElement *buzzStanza)
|
|
|
|
{
|
|
|
|
TQString irisStanza(buzzStanza->Str().c_str());
|
|
|
|
irisStanza.tqreplace("cli:iq","iq");
|
|
|
|
irisStanza.tqreplace(":cli=","=");
|
|
|
|
|
|
|
|
voiceSession->sendStanza(irisStanza);
|
|
|
|
}
|
|
|
|
private:
|
|
|
|
JingleVoiceSession *voiceSession;
|
|
|
|
};
|
|
|
|
//END SlotsProxy
|
|
|
|
|
|
|
|
//BEGIN JingleITQResponder
|
|
|
|
class JingleVoiceSession::JingleITQResponder : public XMPP::Task
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
JingleITQResponder(XMPP::Task *);
|
|
|
|
~JingleITQResponder();
|
|
|
|
|
|
|
|
bool take(const TQDomElement &);
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* \class JingleITQResponder
|
|
|
|
* \brief A task that responds to jingle candidate queries with an empty reply.
|
|
|
|
*/
|
|
|
|
|
|
|
|
JingleVoiceSession::JingleITQResponder::JingleITQResponder(Task *tqparent) :Task(tqparent)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
JingleVoiceSession::JingleITQResponder::~JingleITQResponder()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
bool JingleVoiceSession::JingleITQResponder::take(const TQDomElement &e)
|
|
|
|
{
|
|
|
|
if(e.tagName() != "iq")
|
|
|
|
return false;
|
|
|
|
|
|
|
|
TQDomElement first = e.firstChild().toElement();
|
|
|
|
if (!first.isNull() && first.attribute("xmlns") == JINGLE_NS) {
|
|
|
|
TQDomElement iq = createIQ(doc(), "result", e.attribute("from"), e.attribute("id"));
|
|
|
|
send(iq);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
//END JingleITQResponder
|
|
|
|
|
|
|
|
class JingleVoiceSession::Private
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
Private()
|
|
|
|
: phoneSessionClient(0L), currentCall(0L)
|
|
|
|
{}
|
|
|
|
|
|
|
|
~Private()
|
|
|
|
{
|
|
|
|
if(currentCall)
|
|
|
|
currentCall->Terminate();
|
|
|
|
|
|
|
|
delete currentCall;
|
|
|
|
}
|
|
|
|
|
|
|
|
cricket::PhoneSessionClient *phoneSessionClient;
|
|
|
|
cricket::Call* currentCall;
|
|
|
|
};
|
|
|
|
|
|
|
|
JingleVoiceSession::JingleVoiceSession(JabberAccount *account, const JidList &peers)
|
|
|
|
: JingleSession(account, peers), d(new Private)
|
|
|
|
{
|
|
|
|
slotsProxy = new SlotsProxy(this);
|
|
|
|
|
|
|
|
buzz::Jid buzzJid( account->client()->jid().full().ascii() );
|
|
|
|
|
|
|
|
// Create the phone(voice) session.
|
|
|
|
d->phoneSessionClient = new cricket::PhoneSessionClient( buzzJid, account->sessionManager()->cricketSessionManager() );
|
|
|
|
|
|
|
|
d->phoneSessionClient->SignalSendStanza.connect(slotsProxy, &JingleVoiceSession::SlotsProxy::OnSendingStanza);
|
|
|
|
d->phoneSessionClient->SignalCallCreate.connect(slotsProxy, &JingleVoiceSession::SlotsProxy::OnCallCreated);
|
|
|
|
|
|
|
|
// Listen to incoming packets
|
|
|
|
connect(account->client()->client(), TQT_SIGNAL(xmlIncoming(const TQString&)), this, TQT_SLOT(receiveStanza(const TQString&)));
|
|
|
|
|
|
|
|
new JingleITQResponder(account->client()->rootTask());
|
|
|
|
}
|
|
|
|
|
|
|
|
JingleVoiceSession::~JingleVoiceSession()
|
|
|
|
{
|
|
|
|
kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << endl;
|
|
|
|
delete slotsProxy;
|
|
|
|
delete d;
|
|
|
|
}
|
|
|
|
|
|
|
|
TQString JingleVoiceSession::sessionType()
|
|
|
|
{
|
|
|
|
return TQString(JINGLE_VOICE_SESSION_NS);
|
|
|
|
}
|
|
|
|
|
|
|
|
void JingleVoiceSession::start()
|
|
|
|
{
|
|
|
|
kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Starting a voice session..." << endl;
|
|
|
|
d->currentCall = d->phoneSessionClient->CreateCall();
|
|
|
|
|
|
|
|
TQString firstPeerJid = ((XMPP::Jid)peers().first()).full();
|
|
|
|
kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "With peer: " << firstPeerJid << endl;
|
|
|
|
d->currentCall->InitiateSession( buzz::Jid(firstPeerJid.ascii()) );
|
|
|
|
|
|
|
|
d->phoneSessionClient->SetFocus(d->currentCall);
|
|
|
|
}
|
|
|
|
|
|
|
|
void JingleVoiceSession::accept()
|
|
|
|
{
|
|
|
|
if(d->currentCall)
|
|
|
|
{
|
|
|
|
kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Accepting a voice session..." << endl;
|
|
|
|
|
|
|
|
d->currentCall->AcceptSession(d->currentCall->sessions()[0]);
|
|
|
|
d->phoneSessionClient->SetFocus(d->currentCall);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void JingleVoiceSession::decline()
|
|
|
|
{
|
|
|
|
if(d->currentCall)
|
|
|
|
{
|
|
|
|
d->currentCall->RejectSession(d->currentCall->sessions()[0]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void JingleVoiceSession::terminate()
|
|
|
|
{
|
|
|
|
if(d->currentCall)
|
|
|
|
{
|
|
|
|
d->currentCall->Terminate();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void JingleVoiceSession::setCall(cricket::Call *call)
|
|
|
|
{
|
|
|
|
kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Updating cricket::call object." << endl;
|
|
|
|
d->currentCall = call;
|
|
|
|
d->phoneSessionClient->SetFocus(d->currentCall);
|
|
|
|
}
|
|
|
|
|
|
|
|
void JingleVoiceSession::receiveStanza(const TQString &stanza)
|
|
|
|
{
|
|
|
|
TQDomDocument doc;
|
|
|
|
doc.setContent(stanza);
|
|
|
|
|
|
|
|
// Check if it is offline presence from an open chat
|
|
|
|
if( doc.documentElement().tagName() == "presence" )
|
|
|
|
{
|
|
|
|
XMPP::Jid from = XMPP::Jid(doc.documentElement().attribute("from"));
|
|
|
|
TQString type = doc.documentElement().attribute("type");
|
|
|
|
if( type == "unavailable" && hasPeer(peers(), from) )
|
|
|
|
{
|
|
|
|
//qDebug("JingleVoiceCaller: User went offline without closing a call.");
|
|
|
|
kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "User went offline without closing a call." << endl;
|
|
|
|
emit terminated();
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if the packet is destined for libjingle.
|
|
|
|
// We could use Session::IsClientStanza to check this, but this one crashes
|
|
|
|
// for some reason.
|
|
|
|
TQDomNode node = doc.documentElement().firstChild();
|
|
|
|
bool ok = false;
|
|
|
|
while( !node.isNull() && !ok )
|
|
|
|
{
|
|
|
|
TQDomElement element = node.toElement();
|
|
|
|
if( !element.isNull() && element.attribute("xmlns") == JINGLE_NS)
|
|
|
|
{
|
|
|
|
ok = true;
|
|
|
|
}
|
|
|
|
node = node.nextSibling();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Spread the word
|
|
|
|
if( ok )
|
|
|
|
{
|
|
|
|
kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Handing down buzz::stanza" << endl;
|
|
|
|
buzz::XmlElement *e = buzz::XmlElement::ForStr(stanza.ascii());
|
|
|
|
d->phoneSessionClient->OnIncomingStanza(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#include "jinglevoicesession.moc"
|