682 lines
17 KiB
C++
682 lines
17 KiB
C++
/**
|
|
* Client.cpp
|
|
* This file is part of the YATE Project http://YATE.null.ro
|
|
*
|
|
* Yet Another Telephony Engine - a fully featured software PBX and IVR
|
|
* Copyright (C) 2004, 2005 Null Team
|
|
*
|
|
* 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.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
*/
|
|
|
|
|
|
#include "yatecbase.h"
|
|
|
|
using namespace TelEngine;
|
|
|
|
class UIHandler : public MessageHandler
|
|
{
|
|
public:
|
|
UIHandler()
|
|
: MessageHandler("ui.action",150)
|
|
{ }
|
|
virtual bool received(Message &msg);
|
|
};
|
|
|
|
|
|
Window::Window(const char* id)
|
|
: m_id(id), m_visible(false), m_master(false)
|
|
{
|
|
}
|
|
|
|
Window::~Window()
|
|
{
|
|
if (Client::self())
|
|
Client::self()->m_windows.remove(this,false);
|
|
}
|
|
|
|
const String& Window::toString() const
|
|
{
|
|
return m_id;
|
|
}
|
|
|
|
bool Window::related(const Window* wnd) const
|
|
{
|
|
if ((wnd == this) || !wnd || wnd->master())
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
|
|
UIFactory::UIFactory(const char* type, const char* name)
|
|
: String(name)
|
|
{
|
|
if (ClientDriver::self() && ClientDriver::self()->factory(this,type))
|
|
return;
|
|
Debug(ClientDriver::self(),DebugGoOn,"Could not register '%s' factory type '%s'",
|
|
name,type);
|
|
}
|
|
|
|
UIFactory::~UIFactory()
|
|
{
|
|
if (ClientDriver::self())
|
|
ClientDriver::self()->factory(this,0);
|
|
}
|
|
|
|
|
|
Client* Client::s_client = 0;
|
|
int Client::s_changing = 0;
|
|
|
|
Client::Client(const char *name)
|
|
: Thread(name), m_line(0)
|
|
{
|
|
s_client = this;
|
|
Engine::install(new UIHandler);
|
|
}
|
|
|
|
Client::~Client()
|
|
{
|
|
m_windows.clear();
|
|
s_client = 0;
|
|
Engine::halt(0);
|
|
}
|
|
|
|
void Client::run()
|
|
{
|
|
loadWindows();
|
|
Message msg("ui.event");
|
|
msg.setParam("event","load");
|
|
Engine::dispatch(msg);
|
|
initWindows();
|
|
setStatus("");
|
|
msg.setParam("event","init");
|
|
Engine::dispatch(msg);
|
|
main();
|
|
}
|
|
|
|
Window* Client::getWindow(const String& name)
|
|
{
|
|
if (!s_client)
|
|
return 0;
|
|
ObjList* l = s_client->m_windows.find(name);
|
|
return static_cast<Window*>(l ? l->get() : 0);
|
|
}
|
|
|
|
bool Client::setVisible(const String& name, bool show)
|
|
{
|
|
Window* w = getWindow(name);
|
|
if (!w)
|
|
return false;
|
|
w->visible(show);
|
|
return true;
|
|
}
|
|
|
|
bool Client::getVisible(const String& name)
|
|
{
|
|
Window* w = getWindow(name);
|
|
return w && w->visible();
|
|
}
|
|
|
|
void Client::initWindows()
|
|
{
|
|
ObjList* l = &m_windows;
|
|
for (; l; l = l->next()) {
|
|
Window* w = static_cast<Window*>(l->get());
|
|
if (w)
|
|
w->init();
|
|
}
|
|
}
|
|
|
|
void Client::moveRelated(const Window* wnd, int dx, int dy)
|
|
{
|
|
if (!wnd)
|
|
return;
|
|
ObjList* l = &m_windows;
|
|
for (; l; l = l->next()) {
|
|
Window* w = static_cast<Window*>(l->get());
|
|
if (w && (w != wnd) && wnd->related(w))
|
|
w->moveRel(dx,dy);
|
|
}
|
|
}
|
|
|
|
bool Client::setShow(const String& name, bool visible, Window* wnd, Window* skip)
|
|
{
|
|
if (wnd)
|
|
return wnd->setShow(name,visible);
|
|
++s_changing;
|
|
bool ok = false;
|
|
ObjList* l = &m_windows;
|
|
for (; l; l = l->next()) {
|
|
wnd = static_cast<Window*>(l->get());
|
|
if (wnd && (wnd != skip))
|
|
ok = wnd->setShow(name,visible) || ok;
|
|
}
|
|
--s_changing;
|
|
return ok;
|
|
}
|
|
|
|
bool Client::setActive(const String& name, bool active, Window* wnd, Window* skip)
|
|
{
|
|
if (wnd)
|
|
return wnd->setActive(name,active);
|
|
++s_changing;
|
|
bool ok = false;
|
|
ObjList* l = &m_windows;
|
|
for (; l; l = l->next()) {
|
|
wnd = static_cast<Window*>(l->get());
|
|
if (wnd && (wnd != skip))
|
|
ok = wnd->setActive(name,active) || ok;
|
|
}
|
|
--s_changing;
|
|
return ok;
|
|
}
|
|
|
|
bool Client::setText(const String& name, const String& text, Window* wnd, Window* skip)
|
|
{
|
|
if (wnd)
|
|
return wnd->setText(name,text);
|
|
++s_changing;
|
|
bool ok = false;
|
|
ObjList* l = &m_windows;
|
|
for (; l; l = l->next()) {
|
|
wnd = static_cast<Window*>(l->get());
|
|
if (wnd && (wnd != skip))
|
|
ok = wnd->setText(name,text) || ok;
|
|
}
|
|
--s_changing;
|
|
return ok;
|
|
}
|
|
|
|
bool Client::setCheck(const String& name, bool checked, Window* wnd, Window* skip)
|
|
{
|
|
if (wnd)
|
|
return wnd->setCheck(name,checked);
|
|
++s_changing;
|
|
bool ok = false;
|
|
ObjList* l = &m_windows;
|
|
for (; l; l = l->next()) {
|
|
wnd = static_cast<Window*>(l->get());
|
|
if (wnd && (wnd != skip))
|
|
ok = wnd->setCheck(name,checked) || ok;
|
|
}
|
|
--s_changing;
|
|
return ok;
|
|
}
|
|
|
|
bool Client::setSelect(const String& name, const String& item, Window* wnd, Window* skip)
|
|
{
|
|
if (wnd)
|
|
return wnd->setSelect(name,item);
|
|
++s_changing;
|
|
bool ok = false;
|
|
ObjList* l = &m_windows;
|
|
for (; l; l = l->next()) {
|
|
wnd = static_cast<Window*>(l->get());
|
|
if (wnd && (wnd != skip))
|
|
ok = wnd->setSelect(name,item) || ok;
|
|
}
|
|
--s_changing;
|
|
return ok;
|
|
}
|
|
|
|
bool Client::addOption(const String& name, const String& item, bool atStart, Window* wnd, Window* skip)
|
|
{
|
|
if (wnd)
|
|
return wnd->addOption(name,item,atStart);
|
|
++s_changing;
|
|
bool ok = false;
|
|
ObjList* l = &m_windows;
|
|
for (; l; l = l->next()) {
|
|
wnd = static_cast<Window*>(l->get());
|
|
if (wnd && (wnd != skip))
|
|
ok = wnd->addOption(name,item,atStart) || ok;
|
|
}
|
|
--s_changing;
|
|
return ok;
|
|
}
|
|
|
|
bool Client::delOption(const String& name, const String& item, Window* wnd, Window* skip)
|
|
{
|
|
if (wnd)
|
|
return wnd->delOption(name,item);
|
|
++s_changing;
|
|
bool ok = false;
|
|
ObjList* l = &m_windows;
|
|
for (; l; l = l->next()) {
|
|
wnd = static_cast<Window*>(l->get());
|
|
if (wnd && (wnd != skip))
|
|
ok = wnd->delOption(name,item) || ok;
|
|
}
|
|
--s_changing;
|
|
return ok;
|
|
}
|
|
|
|
bool Client::getText(const String& name, String& text, Window* wnd, Window* skip)
|
|
{
|
|
if (wnd)
|
|
return wnd->getText(name,text);
|
|
ObjList* l = &m_windows;
|
|
for (; l; l = l->next()) {
|
|
wnd = static_cast<Window*>(l->get());
|
|
if (wnd && (wnd != skip) && wnd->getText(name,text))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Client::getCheck(const String& name, bool& checked, Window* wnd, Window* skip)
|
|
{
|
|
if (wnd)
|
|
return wnd->getCheck(name,checked);
|
|
ObjList* l = &m_windows;
|
|
for (; l; l = l->next()) {
|
|
wnd = static_cast<Window*>(l->get());
|
|
if (wnd && (wnd != skip) && wnd->getCheck(name,checked))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Client::getSelect(const String& name, String& item, Window* wnd, Window* skip)
|
|
{
|
|
return getText(name,item,wnd,skip);
|
|
}
|
|
|
|
bool Client::setStatus(const String& text, Window* wnd)
|
|
{
|
|
Debug(ClientDriver::self(),DebugInfo,"Status '%s' in window %p",text.c_str(),wnd);
|
|
return setText("status",text,wnd);
|
|
}
|
|
|
|
bool Client::setStatusLocked(const String& text, Window* wnd)
|
|
{
|
|
lock();
|
|
bool ok = setStatus(text,wnd);
|
|
unlock();
|
|
return ok;
|
|
}
|
|
|
|
bool Client::action(Window* wnd, const String& name)
|
|
{
|
|
DDebug(ClientDriver::self(),DebugInfo,"Action '%s' in %p",name.c_str(),wnd);
|
|
if (name == "call" || name == "callto") {
|
|
String target;
|
|
getText("callto",target,wnd);
|
|
String line;
|
|
getText("line",line,wnd);
|
|
String proto;
|
|
getText("proto",proto,wnd);
|
|
return callStart(target,line,proto);
|
|
}
|
|
else if (name.startsWith("callto:"))
|
|
return callStart(name.substr(7));
|
|
else if (name == "accept") {
|
|
callAccept(m_incoming);
|
|
return true;
|
|
}
|
|
else if (name.startsWith("accept:")) {
|
|
callAccept(name.substr(7));
|
|
return true;
|
|
}
|
|
else if (name == "reject") {
|
|
callReject(m_incoming);
|
|
return true;
|
|
}
|
|
else if (name.startsWith("reject:")) {
|
|
callReject(name.substr(7));
|
|
return true;
|
|
}
|
|
else if (name == "hangup") {
|
|
callHangup(m_incoming);
|
|
return true;
|
|
}
|
|
else if (name.startsWith("hangup:")) {
|
|
callHangup(name.substr(7));
|
|
return true;
|
|
}
|
|
else if (name.startsWith("digit:")) {
|
|
emitDigit(name.at(6));
|
|
return true;
|
|
}
|
|
else if (name.startsWith("line:")) {
|
|
int l = name.substr(5).toInteger(-1);
|
|
if (l >= 0) {
|
|
line(l);
|
|
return true;
|
|
}
|
|
}
|
|
Message* m = new Message("ui.event");
|
|
m->addParam("event","action");
|
|
m->addParam("name",name);
|
|
Engine::enqueue(m);
|
|
return false;
|
|
}
|
|
|
|
bool Client::toggle(Window* wnd, const String& name, bool active)
|
|
{
|
|
DDebug(ClientDriver::self(),DebugInfo,"Toggle '%s' %s in %p",
|
|
name.c_str(),String::boolText(active),wnd);
|
|
if (setVisible(name,active))
|
|
return true;
|
|
setCheck(name,active,0,wnd);
|
|
Message* m = new Message("ui.event");
|
|
m->addParam("event","toggle");
|
|
m->addParam("name",name);
|
|
m->addParam("active",String::boolText(active));
|
|
Engine::enqueue(m);
|
|
return false;
|
|
}
|
|
|
|
bool Client::select(Window* wnd, const String& name, const String& item)
|
|
{
|
|
DDebug(ClientDriver::self(),DebugInfo,"Select '%s' '%s' in %p",
|
|
name.c_str(),item.c_str(),wnd);
|
|
setSelect(name,item,0,wnd);
|
|
Message* m = new Message("ui.event");
|
|
m->addParam("event","select");
|
|
m->addParam("name",name);
|
|
m->addParam("item",item);
|
|
Engine::enqueue(m);
|
|
return false;
|
|
}
|
|
|
|
void Client::line(int newLine)
|
|
{
|
|
Debug(ClientDriver::self(),DebugInfo,"line(%d)",newLine);
|
|
m_line = newLine;
|
|
}
|
|
|
|
void Client::callAccept(const char* callId)
|
|
{
|
|
Debug(ClientDriver::self(),DebugInfo,"callAccept('%s')",callId);
|
|
ClientChannel* cc = static_cast<ClientChannel*>(ClientDriver::self()->find(callId));
|
|
if (cc) {
|
|
cc->openMedia();
|
|
Engine::enqueue(cc->message("call.answered",false,true));
|
|
}
|
|
}
|
|
|
|
void Client::callReject(const char* callId)
|
|
{
|
|
Debug(ClientDriver::self(),DebugInfo,"callReject('%s')",callId);
|
|
if (!ClientDriver::self())
|
|
return;
|
|
Message* m = new Message("call.drop");
|
|
m->addParam("id",callId ? callId : ClientDriver::self()->name().c_str());
|
|
m->addParam("error","rejected");
|
|
m->addParam("reason","Refused");
|
|
Engine::enqueue(m);
|
|
}
|
|
|
|
void Client::callHangup(const char* callId)
|
|
{
|
|
Debug(ClientDriver::self(),DebugInfo,"callHangup('%s')",callId);
|
|
if (!ClientDriver::self())
|
|
return;
|
|
Message* m = new Message("call.drop");
|
|
m->addParam("id",callId ? callId : ClientDriver::self()->name().c_str());
|
|
m->addParam("reason","User hangup");
|
|
Engine::enqueue(m);
|
|
}
|
|
|
|
bool Client::callStart(const String& target, const String& line, const String& proto)
|
|
{
|
|
Debug(ClientDriver::self(),DebugInfo,"callStart('%s','%s','%s')",
|
|
target.c_str(),line.c_str(),proto.c_str());
|
|
if (target.null())
|
|
return false;
|
|
ClientChannel* cc = new ClientChannel();
|
|
Message* m = cc->message("call.route");
|
|
Regexp r("^[a-z]\\+/");
|
|
if (r.matches(target.safe()))
|
|
m->setParam("callto",target);
|
|
else
|
|
m->setParam("called",target);
|
|
if (line)
|
|
m->setParam("line",line);
|
|
if (proto)
|
|
m->setParam("protocol",proto);
|
|
return cc->startRouter(m);
|
|
}
|
|
|
|
bool Client::emitDigit(char digit)
|
|
{
|
|
Debug(ClientDriver::self(),DebugInfo,"emitDigit('%c')",digit);
|
|
return false;
|
|
}
|
|
|
|
bool Client::callIncoming(const String& caller, const String& dest, Message* msg)
|
|
{
|
|
Debug(ClientDriver::self(),DebugAll,"callIncoming [%p]",this);
|
|
if (msg && msg->userData()) {
|
|
CallEndpoint* ch = static_cast<CallEndpoint*>(msg->userData());
|
|
ClientChannel* cc = new ClientChannel(ch->id());
|
|
if (cc->connect(ch)) {
|
|
m_incoming = cc->id();
|
|
msg->setParam("peerid",m_incoming);
|
|
msg->setParam("targetid",m_incoming);
|
|
Engine::enqueue(cc->message("call.ringing",false,true));
|
|
cc->deref();
|
|
// notify the UI about the call
|
|
String tmp("Call from:");
|
|
tmp << " " << caller;
|
|
lock();
|
|
setStatus(tmp);
|
|
setText("incoming",tmp);
|
|
setVisible("incoming");
|
|
unlock();
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Client::clearIncoming(const String& id)
|
|
{
|
|
if (id == m_incoming)
|
|
m_incoming.clear();
|
|
}
|
|
|
|
bool UIHandler::received(Message &msg)
|
|
{
|
|
if (!Client::self())
|
|
return false;
|
|
String action(msg.getValue("action"));
|
|
if (action.null())
|
|
return false;
|
|
Window* wnd = Client::getWindow(msg.getValue("window"));
|
|
if (action == "set_status")
|
|
return Client::self()->setStatusLocked(msg.getValue("status"),wnd);
|
|
String name(msg.getValue("name"));
|
|
if (name.null())
|
|
return false;
|
|
DDebug(ClientDriver::self(),DebugAll,"UI action '%s' on '%s' in %p",
|
|
action.c_str(),name.c_str(),wnd);
|
|
bool ok = false;
|
|
Client::self()->lock();
|
|
if (action == "set_text")
|
|
ok = Client::self()->setText(name,msg.getValue("text"),wnd);
|
|
else if (action == "set_toggle")
|
|
ok = Client::self()->setCheck(name,msg.getBoolValue("active"),wnd);
|
|
else if (action == "set_select")
|
|
ok = Client::self()->setSelect(name,msg.getValue("item"),wnd);
|
|
else if (action == "set_active")
|
|
ok = Client::self()->setActive(name,msg.getBoolValue("active"),wnd);
|
|
else if (action == "set_visible")
|
|
ok = Client::self()->setShow(name,msg.getBoolValue("visible"),wnd);
|
|
else if (action == "add_option")
|
|
ok = Client::self()->addOption(name,msg.getValue("item"),msg.getBoolValue("insert"),wnd);
|
|
else if (action == "del_option")
|
|
ok = Client::self()->delOption(name,msg.getValue("item"),wnd);
|
|
else if (action == "get_text") {
|
|
String text;
|
|
ok = Client::self()->getText(name,text,wnd);
|
|
if (ok)
|
|
msg.retValue() = text;
|
|
}
|
|
else if (action == "get_toggle") {
|
|
bool check;
|
|
ok = Client::self()->getCheck(name,check,wnd);
|
|
if (ok)
|
|
msg.retValue() = check;
|
|
}
|
|
else if (action == "get_select") {
|
|
String item;
|
|
ok = Client::self()->getSelect(name,item,wnd);
|
|
if (ok)
|
|
msg.retValue() = item;
|
|
}
|
|
else if (action == "window_show")
|
|
ok = Client::setVisible(name,true);
|
|
else if (action == "window_hide")
|
|
ok = Client::setVisible(name,false);
|
|
Client::self()->unlock();
|
|
return ok;
|
|
}
|
|
|
|
// IMPORTANT: having a target means "from inside Yate to the user"
|
|
// An user initiated call must be incoming (no target)
|
|
ClientChannel::ClientChannel(const char* target)
|
|
: Channel(ClientDriver::self(),0,target), m_line(0)
|
|
{
|
|
m_targetid = target;
|
|
Engine::enqueue(message("chan.startup"));
|
|
}
|
|
|
|
ClientChannel::~ClientChannel()
|
|
{
|
|
closeMedia();
|
|
String tmp("Hung up:");
|
|
tmp << " " << (address() ? address() : id());
|
|
if (Client::self()) {
|
|
Client::self()->clearIncoming(id());
|
|
Client::self()->setStatusLocked(tmp);
|
|
}
|
|
Engine::enqueue(message("chan.hangup"));
|
|
}
|
|
|
|
bool ClientChannel::openMedia()
|
|
{
|
|
String dev = ClientDriver::device();
|
|
if (dev.null())
|
|
return false;
|
|
Message m("chan.attach");
|
|
complete(m,true);
|
|
m.setParam("source",dev);
|
|
m.setParam("consumer",dev);
|
|
m.userData(this);
|
|
return Engine::dispatch(m);
|
|
}
|
|
|
|
void ClientChannel::closeMedia()
|
|
{
|
|
setSource();
|
|
setConsumer();
|
|
}
|
|
|
|
void ClientChannel::line(int newLine)
|
|
{
|
|
m_line = newLine;
|
|
m_address.clear();
|
|
if (m_line > 0)
|
|
m_address << "line/" << m_line;
|
|
}
|
|
|
|
bool ClientChannel::callRouted(Message& msg)
|
|
{
|
|
String tmp("Calling:");
|
|
tmp << " " << msg.retValue();
|
|
Client::self()->setStatusLocked(tmp);
|
|
return true;
|
|
}
|
|
|
|
void ClientChannel::callAccept(Message& msg)
|
|
{
|
|
Debug(ClientDriver::self(),DebugAll,"ClientChannel::callAccept() [%p]",this);
|
|
Client::self()->setStatusLocked("Call connected");
|
|
Channel::callAccept(msg);
|
|
}
|
|
|
|
void ClientChannel::callReject(const char* error, const char* reason)
|
|
{
|
|
Debug(ClientDriver::self(),DebugAll,"ClientChannel::callReject('%s','%s') [%p]",
|
|
error,reason,this);
|
|
if (!reason)
|
|
reason = error;
|
|
if (!reason)
|
|
reason = "Unknown reason";
|
|
String tmp("Call failed:");
|
|
tmp << " " << reason;
|
|
if (Client::self())
|
|
Client::self()->setStatusLocked(tmp);
|
|
Channel::callReject(error,reason);
|
|
}
|
|
|
|
bool ClientChannel::msgRinging(Message& msg)
|
|
{
|
|
Debug(ClientDriver::self(),DebugAll,"ClientChannel::msgRinging() [%p]",this);
|
|
Client::self()->setStatusLocked("Call ringing");
|
|
return Channel::msgRinging(msg);
|
|
}
|
|
|
|
bool ClientChannel::msgAnswered(Message& msg)
|
|
{
|
|
Debug(ClientDriver::self(),DebugAll,"ClientChannel::msgAnswered() [%p]",this);
|
|
Client::self()->setStatusLocked("Call answered");
|
|
openMedia();
|
|
return Channel::msgAnswered(msg);
|
|
}
|
|
|
|
|
|
ClientDriver* ClientDriver::s_driver = 0;
|
|
String ClientDriver::s_device;
|
|
|
|
ClientDriver::ClientDriver()
|
|
: Driver("client","misc")
|
|
{
|
|
s_driver = this;
|
|
}
|
|
|
|
ClientDriver::~ClientDriver()
|
|
{
|
|
s_driver = 0;
|
|
}
|
|
|
|
bool ClientDriver::factory(UIFactory* factory, const char* type)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool ClientDriver::msgExecute(Message& msg, String& dest)
|
|
{
|
|
Debug(this,DebugInfo,"msgExecute() '%s'",dest.c_str());
|
|
return (Client::self()) && (Client::self()->callIncoming(msg.getValue("caller"),dest,&msg));
|
|
}
|
|
|
|
ClientChannel* ClientDriver::findLine(int line)
|
|
{
|
|
if (line < 1)
|
|
return 0;
|
|
Lock mylock(this);
|
|
ObjList* l = &channels();
|
|
for (; l; l = l->next()) {
|
|
ClientChannel* cc = static_cast<ClientChannel*>(l->get());
|
|
if (cc && (cc->line() == line))
|
|
return cc;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* vi: set ts=8 sw=4 sts=4 noet: */
|