yate/engine/ClientLogic.cpp

7848 lines
245 KiB
C++

/**
* ClientLogic.cpp
* This file is part of the YATE Project http://YATE.null.ro
*
* Default client logic
*
* Yet Another Telephony Engine - a fully featured software PBX and IVR
* Copyright (C) 2004-2006 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "yatecbase.h"
#include <stdio.h>
namespace TelEngine {
// A client wizard
class ClientWizard : public String
{
public:
ClientWizard(const String& wndName, ClientAccountList* accounts, bool temp = false);
// Check if a given window is the wizard
inline bool isWindow(Window* w)
{ return w && w->id() == toString(); }
// Retrieve the wizard window
inline Window* window() const
{ return Client::valid() ? Client::self()->getWindow(toString()) : 0; }
// Retrieve the account
inline ClientAccount* account()
{ return (m_accounts && m_account) ? m_accounts->findAccount(m_account) : 0; }
// Start the wizard. Show the window
virtual void start() {
reset(true);
changePage(String::empty());
Client::self()->setVisible(toString(),true,true);
}
virtual void reset(bool full)
{}
// Handle actions from wizard window. Return true if handled
virtual bool action(Window* w, const String& name, NamedList* params = 0);
// Handle checkable widgets status changes in wizard window
// Return true if handled
virtual bool toggle(Window* w, const String& name, bool active);
// Handle selection changes notifications. Return true if handled
virtual bool select(Window* w, const String& name, const String& item,
const String& text = String::empty())
{ return false; }
// Handle user.notify messages. Restart the wizard if the operating account is offline
// Return true if handled
virtual bool handleUserNotify(const String& account, bool ok, const char* reason = 0);
// Widgets
static const String s_pagesWidget; // Wizard pages UI widget
static const String s_actionNext; // The name of the 'next' action
static const String s_actionPrev; // The name of the 'previous' action
static const String s_actionCancel; // The name of the 'cancel async operation' action
protected:
virtual void onNext()
{}
virtual void onPrev()
{}
virtual void onCancel()
{}
// Wizard window visibility changed notification.
virtual void windowVisibleChanged(bool visible) {
if (!visible)
reset(true);
}
// Retrieve the current page from UI
inline void currentPage(String& page) const {
Window* w = window();
if (w)
Client::self()->getSelect(s_pagesWidget,page,w);
}
// Check if a given page is the current one
inline bool isCurrentPage(const String& page) const {
String p;
currentPage(p);
return p && p == page;
}
// Retrieve the selected account
ClientAccount* account(const String& list);
// Update wizard actions active status
void updateActions(NamedList& p, bool canPrev, bool canNext, bool canCancel);
// Change the wizard page
virtual bool changePage(const String& page, const String& old = String::empty())
{ return false; }
ClientAccountList* m_accounts; // The list of accounts if needed
String m_account; // The account used by the wizard
bool m_temp; // Wizard window is a temporary one
};
// New account wizard
// The accounts list object is not owned by the wizard
class AccountWizard : public ClientWizard
{
public:
inline AccountWizard(ClientAccountList* accounts)
: ClientWizard("accountwizard",accounts)
{}
~AccountWizard()
{ reset(true); }
virtual void reset(bool full);
virtual bool handleUserNotify(const String& account, bool ok, const char* reason = 0);
protected:
virtual void onNext();
virtual void onPrev();
virtual void onCancel();
virtual bool changePage(const String& page, const String& old = String::empty());
};
// Join MUC room wizard
// The accounts list object is not owned by the wizard
class JoinMucWizard : public ClientWizard
{
public:
// Build a join MUC wizard. Show the join page if temporary
JoinMucWizard(ClientAccountList* accounts, NamedList* tempParams = 0);
~JoinMucWizard()
{ reset(true); }
virtual void reset(bool full);
// Handle actions from wizard window. Return true if handled
virtual bool action(Window* w, const String& name, NamedList* params = 0);
// Handle selection changes notifications. Return true if handled
virtual bool select(Window* w, const String& name, const String& item,
const String& text = String::empty());
// Process contact.info message
bool handleContactInfo(Message& msg, const String& account, const String& oper,
const String& contact);
// Handle user.notify messages. Update the accounts list
virtual bool handleUserNotify(const String& account, bool ok, const char* reason = 0);
protected:
virtual void onNext();
virtual void onPrev();
virtual void onCancel();
virtual bool changePage(const String& page, const String& old = String::empty());
// Handle the join room action
void joinRoom();
// Retrieve the selected MUC server if not currently requesting one
bool selectedMucServer(String* buf = 0);
// Set/reset servers query
void setQuerySrv(bool on);
// Set/reset rooms query
void setQueryRooms(bool on);
private:
bool m_queryRooms; // Requesting rooms from server
bool m_querySrv; // Requesting MUC server(s)
ObjList m_requests; // Info/items requests id
String m_lastPage; // Last visited page
};
// Class holding an account status item and
// global account status data (the list of available status items)
class AccountStatus : public String
{
public:
inline AccountStatus(const char* name)
: String(name), m_status(ClientResource::Offline)
{}
inline int status() const
{ return m_status; }
inline const String& text() const
{ return m_text; }
// Retrieve current status item
static inline AccountStatus* current()
{ return s_current; }
// Find an item
static inline AccountStatus* find(const String& name) {
ObjList* o = s_items.find(name);
return o ? static_cast<AccountStatus*>(o->get()) : 0;
}
// Change the current item. Save to config if changed
// Return true if an item with the given name was found
static bool setCurrent(const String& name);
// Append/set an item. Save to config if changed
static void set(const String& name, int stat, const String& text, bool save = false);
// Load the list from config
static void load();
// Initialize the list
static void init();
// Update current status in UI
static void updateUi();
private:
static ObjList s_items; // Items
static AccountStatus* s_current; // Current status
int m_status;
String m_text;
};
// This class holds a pending request sent by the client
class PendingRequest : public String
{
public:
inline PendingRequest(const char* id, const String& account, const String& target)
: String(id), m_account(account), m_target(target),
m_mucServer(false), m_mucRooms(false)
{}
// Find an item
static inline PendingRequest* find(const String& name) {
ObjList* o = s_items.find(name);
return o ? static_cast<PendingRequest*>(o->get()) : 0;
}
// Remove all account's requests
static void clear(const String& account);
// Request info/items from target
static PendingRequest* request(bool info, ClientAccount* acc, const String& target,
bool mucserver);
// Request MUC rooms from target
static bool requestMucRooms(ClientAccount* acc, const String& target);
static ObjList s_items;
String m_account; // The account
String m_target; // Request target
bool m_mucServer; // True if we are searching for MUC services
bool m_mucRooms; // True if we are serching for MUC rooms
};
// Chat state notificator
// This class is not thread safe. Data MUST be changed from client's thread
class ContactChatNotify : public String
{
public:
enum State {
None = 0,
Active,
Composing,
Paused,
Inactive,
};
// Update timers
inline void updateTimers(const Time& time) {
m_paused = time.msec() + s_pauseInterval;
m_inactive = time.msec() + s_inactiveInterval;
}
// Check for timeout. Reset the timer if a notification is returned
State timeout(Time& time);
// Send the notification
static void send(State state, ClientContact* c, MucRoom* room, MucRoomMember* member);
// Add or remove items from list. Notify active/composing if changed
// Don't notify active if empty and 'notify' is false
static void update(ClientContact* c, MucRoom* room, MucRoomMember* member,
bool empty, bool notify = true);
// Check timeouts. Send notifications
static bool checkTimeouts(ClientAccountList& list, Time& time);
// Clear list
static inline void clear()
{ s_items.clear(); }
// State names
static const TokenDict s_states[];
private:
inline ContactChatNotify(const String& id, bool mucRoom, bool mucMember,
const Time& time = Time())
: String(id), m_mucRoom(mucRoom), m_mucMember(mucMember),
m_paused(0), m_inactive(0)
{ updateTimers(time); }
static u_int64_t s_pauseInterval; // Interval to send paused notification
static u_int64_t s_inactiveInterval; // Interval to send gone notification
static ObjList s_items; // Item list
bool m_mucRoom; // Regular contact or muc room
bool m_mucMember; // Room member
u_int64_t m_paused; // Time to send paused
u_int64_t m_inactive; // Time to send gone
};
}; // namespace TelEngine
using namespace TelEngine;
// Windows
static const String s_wndMain = "mainwindow"; // mainwindow
static const String s_wndAccount = "account"; // Account edit/add
static const String s_wndAddrbook = "addrbook"; // Contact edit/add
static const String s_wndChatContact = "chatcontact"; // Chat contact edit/add
static const String s_wndMucInvite = "mucinvite"; // MUC invite
static const String s_wndAcountList = "accountlist"; // Accounts list
static const String s_wndFileTransfer = "fileprogress"; // File transfer
// Some UI widgets
static const String s_mainwindowTabs = "mainwindowTabs";
static const String s_channelList = "channels";
static const String s_accountList = "accounts"; // Global accounts list
static const String s_contactList = "contacts";
static const String s_logList = "log";
static const String s_calltoList = "callto";
static const String s_account = "account"; // Account selector
static const String s_chatContactList = "chat_contacts"; // List of chat contacts
static const String s_mucAccounts = "mucaccount"; // List of accounts supporting MUC
static const String s_mucSavedRooms = "mucsavedrooms"; // List of saved MUC rooms
static const String s_mucMembers = "muc_members"; // List of MUC room members
static const String s_accProtocol = "acc_protocol"; // List of protocols in account add/edit
static const String s_accWizProtocol = "accwiz_protocol"; // List of protocols in account wizard
static const String s_accProviders = "acc_providers"; // List of providers in account add/edit
static const String s_accWizProviders = "accwiz_providers"; // List of providers in account wizard
static const String s_inviteContacts = "invite_contacts"; // List of contacts in muc invite
// Actions
static const String s_actionShowCallsList = "showCallsList";
static const String s_actionShowNotification = "showNotification";
static const String s_actionPendingChat = "showPendingChat";
static const String s_actionCall = "call";
static const String s_actionAnswer = "answer";
static const String s_actionHangup = "hangup";
static const String s_actionTransfer = "transfer";
static const String s_actionConf = "conference";
static const String s_actionHold = "hold";
static const String s_actionLogin = "acc_login";
static const String s_actionLogout = "acc_logout";
static const String s_chat = "chatcontact_chat";
static const String s_chatCall = "chatcontact_call";
static const String s_chatNew = "chatcontact_new";
static const String s_chatShowLog = "chatcontact_showlog";
static const String s_chatEdit = "chatcontact_edit";
static const String s_chatDel = "chatcontact_del";
static const String s_chatInfo = "chatcontact_info";
static const String s_chatSub = "chatcontact_subscribe";
static const String s_chatUnsubd = "chatcontact_unsubscribed";
static const String s_chatUnsub = "chatcontact_unsubscribe";
static const String s_chatShowOffline = "chatcontact_showoffline";
static const String s_chatFlatList = "chatcontact_flatlist";
static const String s_chatSend = "send_chat";
static const String s_fileSend = "send_file";
static const String s_fileSendPrefix = "send_file:";
static const String s_mucChgSubject = "room_changesubject";
static const String s_mucChgNick = "room_changenick";
static const String s_mucInvite = "room_invite_contacts";
static const String s_mucPrivChat = "room_member_chat";
static const String s_mucKick = "room_member_kick";
static const String s_mucBan = "room_member_ban";
static const String s_mucRoomShowLog = "room_showlog";
static const String s_mucMemberShowLog = "room_member_showlog";
// Not selected string(s)
static String s_notSelected = "-none-";
// Maximum number of call log entries
static unsigned int s_maxCallHistory = 20;
// Global account status
ObjList AccountStatus::s_items;
AccountStatus* AccountStatus::s_current = 0;
// Pending requests
ObjList PendingRequest::s_items;
// Client wizard
const String ClientWizard::s_pagesWidget = "pages";
const String ClientWizard::s_actionNext = "next";
const String ClientWizard::s_actionPrev = "prev";
const String ClientWizard::s_actionCancel = "cancel";
// Wizards managed by the default logic
static AccountWizard* s_accWizard = 0;
static JoinMucWizard* s_mucWizard = 0;
// Chat state notificator
const TokenDict ContactChatNotify::s_states[] = {
{"active", Active},
{"composing", Composing},
{"paused", Paused},
{"inactive", Inactive},
{0,0}
};
u_int64_t ContactChatNotify::s_pauseInterval = 30000; // Paused notification
u_int64_t ContactChatNotify::s_inactiveInterval = 300000; // Inactive notification
ObjList ContactChatNotify::s_items; // Item list
// ClientLogic
ObjList ClientLogic::s_accOptions;
ObjList ClientLogic::s_protocols;
Mutex ClientLogic::s_protocolsMutex(true,"ClientProtocols");
// Parameters that are applied from provider template
const char* ClientLogic::s_provParams[] = {
"server",
"domain",
"outbound",
"port",
0
};
// Common account parameters (protocol independent)
static const String s_accParams[] = {
"username", "password", ""
};
// Common account boolean parameters (protocol independent)
static const String s_accBoolParams[] = {
"savepassword", ""
};
// Account protocol dependent parameters
static const String s_accProtoParams[] = {
"server", "domain", "outbound", "options", "resource", "port", "interval", ""
};
// Resource status images
static const TokenDict s_statusImage[] = {
{"status_offline.png", ClientResource::Offline},
{"status_connecting.png",ClientResource::Connecting},
{"status_online.png", ClientResource::Online},
{"status_busy.png", ClientResource::Busy},
{"status_dnd.png", ClientResource::Dnd},
{"status_away.png", ClientResource::Away},
{"status_xa.png", ClientResource::Xa},
{0,0}
};
// Saved rooms
static Configuration s_mucRooms;
// Actions from notification area
enum PrivateNotifAction {
PrivNotificationOk = 1,
PrivNotificationReject,
PrivNotificationLogin,
PrivNotificationAccEdit,
PrivNotificationAccounts,
PrivNotification1,
PrivNotification2,
PrivNotification3,
};
static const TokenDict s_notifPrefix[] = {
{"messages_ok:", PrivNotificationOk},
{"messages_reject:", PrivNotificationReject},
{"messages_login:", PrivNotificationLogin},
{"messages_acc_edit:", PrivNotificationAccEdit},
{"messages_accounts:", PrivNotificationAccounts},
{"messages_1:", PrivNotification1},
{"messages_2:", PrivNotification2},
{"messages_3:", PrivNotification3},
{0,0,}
};
enum ChatLogEnum {
ChatLogSaveAll = 1,
ChatLogSaveUntilLogout,
ChatLogNoSave
};
// Archive save data
const TokenDict s_chatLogDict[] = {
{"chat_save_all", ChatLogSaveAll},
{"chat_save_untillogout", ChatLogSaveUntilLogout},
{"chat_nosave", ChatLogNoSave},
{0,0}
};
static ChatLogEnum s_chatLog = ChatLogSaveAll;
// Posponed contact update
static Configuration s_postponedContacts;
// Temporary wizards
static ObjList s_tempWizards;
// Chat state templates
static NamedList s_chatStates("");
// Changing docked chat state
static bool s_changingDockedChat = false;
// Pending chat items managed in the client's thread
static ObjList s_pendingChat;
// Miscellaneous
static const String s_jabber = "jabber";
static const String s_gmailDomain = "gmail.com";
static const String s_googleDomain = "google.com";
static const String s_fileOpenSendPrefix = "send_fileopen:";
static const String s_fileOpenRecvPrefix = "recv_fileopen:";
static String s_lastFileDir; // Last directory used to send/recv file
static String s_lastFileFilter; // Last filter used to pick a file to send
// Dump a list of parameters to output if XDEBUG is defined
static inline void dumpList(const NamedList& p, const char* text, Window* w = 0)
{
#ifdef XDEBUG
String tmp;
p.dump(tmp,"\r\n");
String wnd;
if (w)
wnd << " window=" << w->id();
Debug(ClientDriver::self(),DebugInfo,"%s%s\r\n-----\r\n%s\r\n-----",text,wnd.safe(),tmp.safe());
#endif
}
// Build contact name: name <uri>
static inline void buildContactName(String& buf, ClientContact& c)
{
buf = c.m_name;
if (c.m_name != c.uri())
buf << " <" << c.uri() << ">";
}
// Compare list parameters given in array
// Return true if equal
static bool sameParams(const NamedList& l1, const NamedList& l2, const String* params)
{
if (!params)
return false;
while (*params && l1[*params] == l2[*params])
params++;
return params->null();
}
// Build an user.login message
// Clear account password if not saved
static Message* userLogin(ClientAccount* a, bool login)
{
if (!a)
return 0;
Message* m = a->userlogin(login);
if (login && !a->params().getBoolValue("savepassword"))
a->m_params.clearParam("password");
return m;
}
// Retrieve a contact or MUC room from name:id.
// For MUC rooms the id is assumed to be a member id.
// Return true if the prefix was found
static bool getPrefixedContact(const String& name, const String& prefix, String& id,
ClientAccountList* list, ClientContact** c, MucRoom** room)
{
if (!(list && (room || c)))
return false;
int pos = name.find(':');
if (pos < 0 || name.substr(0,pos) != prefix)
return false;
id = name.substr(pos + 1);
if (c)
*c = list->findContact(id);
if (!(c && *c) && room)
*room = list->findRoomByMember(id);
return true;
}
// Check if a protocol is a telephony one
// FIXME: find a better way to detect it
static inline bool isTelProto(const String& proto)
{
return proto != s_jabber;
}
// Check if a given account is a gmail one
static inline bool isGmailAccount(ClientAccount* acc)
{
if (!(acc && acc->contact()))
return false;
return (acc->contact()->uri().getHost() &= s_gmailDomain) ||
(acc->contact()->uri().getHost() &= s_googleDomain);
}
// Retrieve protocol specific page name in UI
static const String& getProtoPage(const String& proto)
{
static const String s_default = "default";
static const String s_none = "none";
if (proto == s_jabber)
return s_jabber;
if (proto)
return s_default;
return s_none;
}
// Show a confirm dialog box in a given window
static bool showInput(Window* wnd, const String& name, const char* text,
const char* context, const char* title, const char* input = 0)
{
if (!(Client::valid() && name))
return false;
NamedList p("");
p.addParam("inputdialog_text",text);
p.addParam("inputdialog_input",input);
p.addParam("property:" + name + ":_yate_context",context);
return Client::self()->createDialog("input",wnd,title,name,&p);
}
// Show a confirm dialog box in a given window
static bool showConfirm(Window* wnd, const char* text, const char* context)
{
static const String name = "confirm_dialog";
if (!Client::valid())
return false;
NamedList p("");
p.addParam("text",text);
p.addParam("property:" + name + ":_yate_context",context);
return Client::self()->createDialog("confirm",wnd,String::empty(),name,&p);
}
// Show an error dialog box in a given window
static void showError(Window* wnd, const char* text)
{
static const String name = "error_dialog";
if (!Client::valid())
return;
NamedList p("");
p.addParam("text",text);
Client::self()->createDialog("message",wnd,String::empty(),"error_dialog",&p);
}
// Show an error dialog box in a given window
static inline void showAccDupError(Window* wnd)
{
showError(wnd,"Another account with the same protocol, username and host already exists!");
}
// Retrieve resource status image with path
static inline String resStatusImage(int stat)
{
const char* img = lookup(stat,s_statusImage);
if (img)
return Client::s_skinPath + img;
return String();
}
// Set the image parameter of a list
static inline void setImageParam(NamedList& p, const char* param,
const String& image)
{
static const String suffix = "_image";
p.setParam(param + suffix,Client::s_skinPath + image);
}
// Set a list parameter and it's image
static inline void setImageParam(NamedList& p, const char* param,
const char* value, const String& image)
{
p.setParam(param,value);
setImageParam(p,param,image);
}
// Request to the client to log a chat entry
static bool logChat(ClientContact* c, unsigned int time, bool send, bool delayed,
const String& body, bool roomChat = true, const String& nick = String::empty())
{
if (!c)
return false;
if (s_chatLog != ChatLogSaveAll && s_chatLog != ChatLogSaveUntilLogout)
return false;
if (!Client::self())
return false;
MucRoom* room = c->mucRoom();
NamedList p("");
p.addParam("account",c->accountName());
p.addParam("contact",c->uri());
if (!room) {
p.addParam("contactname",c->m_name);
p.addParam("sender",send ? "" : c->m_name.c_str());
}
else {
p.addParam("muc",String::boolText(true));
p.addParam("roomchat",String::boolText(roomChat));
p.addParam("contactname",roomChat ? room->resource().m_name : nick);
p.addParam("sender",send ? "" : nick.c_str());
}
p.addParam("time",String(time));
p.addParam("send",String::boolText(send));
if (!send && delayed)
p.addParam("delayed",String::boolText(true));
p.addParam("text",body);
return Client::self()->action(0,"archive:logchat",&p);
}
// Show contact archive log
static bool logShow(ClientContact* c, bool roomChat = true,
const String& nick = String::empty())
{
if (!(c && Client::self()))
return false;
MucRoom* room = c->mucRoom();
NamedList p("");
p.addParam("account",c->accountName());
p.addParam("contact",c->uri());
if (room) {
p.addParam("muc",String::boolText(true));
p.addParam("roomchat",String::boolText(roomChat));
p.addParam("contactname",nick,false);
}
return Client::self()->action(0,"archive:showchat",&p);
}
// Close archive session
static bool logCloseSession(ClientContact* c, bool roomChat = true,
const String& nick = String::empty())
{
if (!(c && Client::self()))
return false;
MucRoom* room = c->mucRoom();
NamedList p("");
p.addParam("account",c->accountName());
p.addParam("contact",c->uri());
if (room) {
p.addParam("muc",String::boolText(true));
p.addParam("roomchat",String::boolText(roomChat));
p.addParam("contactname",nick,false);
}
return Client::self()->action(0,"archive:closechatsession",&p);
}
// Clear an account's log
static bool logClearAccount(const String& account)
{
if (!Client::self())
return false;
NamedList p("");
p.addParam("account",account);
return Client::self()->action(0,"archive:clearaccountnow",&p);
}
// Close all MUC log sessions of a room
static void logCloseMucSessions(MucRoom* room)
{
if (!room)
return;
Window* w = room->getChatWnd();
if (w) {
NamedList p("");
Client::self()->getOptions(ClientContact::s_dockedChatWidget,&p,w);
unsigned int n = p.length();
for (unsigned int i = 0; i < n; i++) {
NamedString* ns = p.getParam(i);
if (!(ns && ns->name()))
continue;
MucRoomMember* m = room->findMemberById(ns->name());
if (m)
logCloseSession(room,false,m->m_name);
}
}
else {
for (ObjList* o = room->resources().skipNull(); o; o = o->skipNext()) {
MucRoomMember* m = static_cast<MucRoomMember*>(o->get());
logCloseSession(room,false,m->m_name);
}
}
logCloseSession(room);
}
// Update protocol related page(s) in account edit/add or wizard
static void selectProtocolSpec(NamedList& p, const String& proto, bool advanced,
const String& protoList)
{
p.setParam("select:" + protoList,proto);
p.setParam("select:acc_proto_cfg","acc_proto_cfg_" + getProtoPage(proto));
p.setParam("select:acc_proto_advanced",
"acc_proto_advanced_" + getProtoPage(advanced ? proto : String::empty()));
}
// Update protocol specific data
// Set protocol specific widgets: options, address, port ....
// Text widgets' name should start with acc_proto_protocolpagename_
// Option widgets' name should start with acc_proto_protocolpagename_opt_
static void updateProtocolSpec(NamedList& p, const String& proto, bool edit,
const NamedList& params = NamedList::empty())
{
DDebug(ClientDriver::self(),DebugAll,"updateProtocolSpec(%s,%u,%s)",
proto.c_str(),edit,params.c_str());
// Account common params
String prefix = "acc_";
for (const String* par = s_accParams; !par->null(); par++)
p.setParam(prefix + *par,params.getValue(*par));
// Protocol specific params
prefix << "proto_" << getProtoPage(proto) << "_";
for (const String* par = s_accProtoParams; !par->null(); par++)
p.setParam(prefix + *par,params.getValue(*par));
// Set default resource for new accounts if not already set
if (!edit && proto == s_jabber) {
String rname = prefix + "resource";
if (!p.getValue(rname))
p.setParam(rname,Engine::config().getValue("client","resource","Yate"));
}
// Options
prefix << "opt_";
ObjList* opts = params["options"].split(',',false);
for (ObjList* o = ClientLogic::s_accOptions.skipNull(); o; o = o->skipNext()) {
String* opt = static_cast<String*>(o->get());
bool checked = (opts && 0 != opts->find(*opt));
p.setParam("check:" + prefix + *opt,String::boolText(checked));
}
TelEngine::destruct(opts);
dumpList(p,"updateProtocolSpec");
}
// Handle protocol/providers select for DefaultLogic in account edit/add or wizard
static bool handleProtoProvSelect(Window* w, const String& name, const String& item)
{
// Flag used to avoid resetting the providers list in provider change handler
static bool s_changing = false;
// Handle protocol selection in edit or wizard window:
// Show/hide protocol specific options
// Select nothing in providers
bool noWiz = (name == s_accProtocol);
if (noWiz || name == s_accWizProtocol) {
if (!Client::valid())
return false;
bool adv = false;
Client::self()->getCheck("acc_showadvanced",adv,w);
NamedList p("");
selectProtocolSpec(p,item,adv,name);
// Reset provider if not changing due to provider change
if (!s_changing)
p.setParam("select:" + (noWiz ? s_accProviders : s_accWizProviders),s_notSelected);
dumpList(p,"Handle protocol select",w);
Client::self()->setParams(&p,w);
return true;
}
// Apply provider template
noWiz = (name == s_accProviders);
if (!noWiz && name != s_accWizProviders)
return false;
if (Client::s_notSelected.matches(item))
return true;
if (!Client::valid())
return true;
// Get data and update UI
NamedList* sect = Client::s_providers.getSection(item);
if (!sect)
return true;
NamedList p("");
const String& proto = (*sect)["protocol"];
bool adv = false;
Client::self()->getCheck("acc_showadvanced",adv,w);
selectProtocolSpec(p,proto,adv,noWiz ? s_accProtocol : s_accWizProtocol);
updateProtocolSpec(p,proto,w && w->context(),*sect);
dumpList(p,"Handle provider select",w);
// Avoid resetting protocol while applying provider
s_changing = true;
Client::self()->setParams(&p,w);
s_changing = false;
return true;
}
// Update the protocol list from global
// filterTypeTel: Optional pointer to protocol telephony/IM type
// specParams: Optional pointer to parameters list used to update protocol specs
// firstProto: Optional pointer to String to be filled with the first protocol in the list
static void updateProtocolList(Window* w, const String& list, bool* filterTypeTel = 0,
NamedList* specParams = 0, String* firstProto = 0)
{
DDebug(ClientDriver::self(),DebugAll,"updateProtocolList(%p,%s,%p,%p,%p)",
w,list.c_str(),filterTypeTel,specParams,firstProto);
ObjList tmp;
ClientLogic::s_protocolsMutex.lock();
for (ObjList* o = ClientLogic::s_protocols.skipNull(); o; o = o->skipNext()) {
String* s = static_cast<String*>(o->get());
if (TelEngine::null(s))
continue;
if (!filterTypeTel || *filterTypeTel == isTelProto(*s))
tmp.append(new String(*s));
}
ClientLogic::s_protocolsMutex.unlock();
for (ObjList* o = tmp.skipNull(); o; o = o->skipNext()) {
String* s = static_cast<String*>(o->get());
if (TelEngine::null(s))
continue;
bool ok = list.null() || Client::self()->updateTableRow(list,*s,0,false,w);
if (ok && firstProto && firstProto->null())
*firstProto = *s;
if (specParams)
updateProtocolSpec(*specParams,*s,false);
}
}
// Update a provider item in a given list
// filterTypeTel: Optional pointer to protocol telephony/IM type
static bool updateProvidersItem(Window* w, const String& list, const NamedList& prov,
bool* filterTypeTel = 0)
{
if (!Client::valid())
return false;
const String& proto = prov["protocol"];
if (proto && (!filterTypeTel || *filterTypeTel == isTelProto(proto)))
return Client::self()->updateTableRow(list,prov,0,false,w);
return false;
}
// Utility function used to build channel status
static void buildStatus(String& status, const char* stat, const char* addr,
const char* id, const char* reason = 0)
{
status << stat;
if (addr || id)
status << ": " << (addr ? addr : id);
if (reason)
status << " reason: " << reason;
}
// Check if a given parameter is present in a list.
// Update it from UI if not present or empty
static bool checkParam(NamedList& p, const char* param, const String& widget,
bool checkNotSel, Window* wnd = 0)
{
NamedString* tmp = p.getParam(param);
if (tmp && *tmp)
return true;
if (!Client::self())
return false;
String value;
Client::self()->getText(widget,value,false,wnd);
value.trimBlanks();
bool ok = value && !(checkNotSel && Client::s_notSelected.matches(value));
if (ok)
p.setParam(param,value);
return ok;
}
// Utility: activate the calls page
static void activatePageCalls(Window* wnd = 0, bool selTab = true)
{
if (!Client::valid())
return;
NamedList p("");
p.addParam("check:ctrlCalls",String::boolText(true));
p.addParam("select:framePages","PageCalls");
if (selTab)
p.addParam("select:" + s_mainwindowTabs,"tabTelephony");
Client::self()->setParams(&p,wnd);
}
// Check if the calls page is active
static bool isPageCallsActive(Window* wnd, bool checkTab)
{
if (!Client::valid())
return false;
String sel;
if (checkTab) {
Client::self()->getSelect(s_mainwindowTabs,sel,wnd);
if (sel != "tabTelephony")
return false;
sel.clear();
}
Client::self()->getSelect("framePages",sel,wnd);
return sel == "PageCalls";
}
// Retrieve a contact edit/info window.
// Create it if requested and not found.
// Set failExists to true to return 0 if already exists
static Window* getContactInfoEditWnd(bool edit, ClientContact* c, bool create = false,
bool failExists = false)
{
if (!Client::valid())
return 0;
const char* wnd = edit ? "contactedit" : "contactinfo";
String wname(wnd);
wname << "_" << (c ? c->toString().c_str() : String((unsigned int)Time::msecNow()).c_str());
Window* w = Client::self()->getWindow(wname);
if (w)
return failExists ? 0 : w;
if (!create)
return 0;
Client::self()->createWindowSafe(wnd,wname);
w = Client::self()->getWindow(wname);
if (w && c) {
NamedList p("");
p.addParam("context",c->toString());
if (!edit)
p.addParam("property:" + s_chatEdit + ":_yate_identity",
s_chatEdit + ":" + c->toString());
Client::self()->setParams(&p,w);
}
return w;
}
// Retrieve an account's enter password window
// Create it if requested and not found.
static Window* getAccPasswordWnd(const String& account, bool create)
{
if (!(Client::valid() && account))
return 0;
String wname(account + "EnterPassword");
Window* w = Client::self()->getWindow(wname);
if (!create)
return w;
if (!w) {
Client::self()->createWindowSafe("inputpwd",wname);
w = Client::self()->getWindow(wname);
if (!w) {
Debug(ClientDriver::self(),DebugNote,"Failed to build account password window!");
return 0;
}
}
NamedList p("");
String text;
text << "Enter password for account '" << account << "'";
p.addParam("inputpwd_text",text);
p.addParam("inputpwd_password","");
p.addParam("check::inputpwd_savepassword",String::boolText(false));
p.addParam("context","loginpassword:" + account);
Client::self()->setParams(&p,w);
Client::self()->setVisible(wname,true,true);
return w;
}
// Build a chat history item parameter list
static NamedList* buildChatParams(const char* text, const char* sender,
unsigned int sec, bool delay = false, const char* delaysource = 0)
{
NamedList* p = new NamedList("");
p->addParam("text",text);
p->addParam("sender",sender,false);
String ts;
String dl;
if (!delay)
Client::self()->formatDateTime(ts,sec,"hh:mm:ss",false);
else {
Client::self()->formatDateTime(ts,sec,"dd.MM.yyyy hh:mm:ss",false);
if (!TelEngine::null(delaysource))
dl << "\r\nDelayed by: " << delaysource;
}
p->addParam("time",ts,false);
p->addParam("delayed_by",dl,false);
return p;
}
// Build a chat state history item parameter list
static bool buildChatState(String& buf, const NamedList& params, const char* sender)
{
const String& state = params["chatstate"];
if (!state)
return false;
buf = s_chatStates[state];
if (!buf)
return true;
NamedList tmp("");
tmp.addParam("sender",sender);
tmp.addParam("state",state);
tmp.replaceParams(buf);
return true;
}
// Add a notification text in contact's chat history
static void addChatNotify(ClientContact& c, const char* text,
unsigned int sec = Time::secNow(), const char* what = "notify",
const String& roomId = String::empty())
{
if (!c.hasChat())
return;
NamedList* p = buildChatParams(text,0,sec);
MucRoom* room = c.mucRoom();
if (!room)
c.addChatHistory(what,p);
else
room->addChatHistory(roomId ? roomId : room->resource().toString(),what,p);
}
// Add an online/offline notification text in contact's chat history
static inline void addChatNotify(ClientContact& c, bool online,
bool account = false, unsigned int sec = Time::secNow())
{
String text;
if (!account)
text << c.m_name;
else
text = "Account";
text << " is " << (online ? "online" : "offline");
addChatNotify(c,text,sec);
}
// Add/Update a contact list item
static void updateContactList(ClientContact& c, const String& inst = String::empty(),
const char* uri = 0)
{
DDebug(ClientDriver::self(),DebugAll,"updateContactList(%s,%s,%s)",
c.toString().c_str(),inst.c_str(),uri);
NamedList p("");
p.addParam("name",c.m_name);
p.addParam("number/uri",TelEngine::null(uri) ? c.uri().c_str() : uri);
String id;
c.buildInstanceId(id,inst);
Client::self()->updateTableRow(s_contactList,id,&p);
}
// Remove all contacts starting with a given string
static void removeContacts(const String& idstart)
{
NamedList p("");
if (!Client::self()->getOptions(s_contactList,&p))
return;
DDebug(ClientDriver::self(),DebugAll,"removeContacts(%s)",idstart.c_str());
unsigned int n = p.count();
for (unsigned int i = 0; i < n; i++) {
NamedString* param = p.getParam(i);
if (param && param->name().startsWith(idstart,false))
Client::self()->delTableRow(s_contactList,param->name());
}
}
// Contact deleted: clear UI
static void contactDeleted(ClientContact& c)
{
DDebug(ClientDriver::self(),DebugAll,"contactDeleted(%s)",c.toString().c_str());
// Add chat notification and update status
if (c.hasChat() && c.online()) {
addChatNotify(c,false);
NamedList p("");
String img = resStatusImage(ClientResource::Offline);
p.addParam("image:status_image",img);
p.addParam("status_text",ClientResource::statusDisplayText(ClientResource::Offline));
c.updateChatWindow(p,0,img);
}
// Remove from chat
Client::self()->delTableRow(s_chatContactList,c.toString());
// Remove instances from contacts list
String instid;
removeContacts(c.buildInstanceId(instid));
// Close chat session
logCloseSession(&c);
}
// Remove all account contacts from UI
static void clearAccountContacts(ClientAccount& a)
{
DDebug(ClientDriver::self(),DebugAll,"clearAccountContacts(%s)",a.toString().c_str());
ObjList* o = 0;
while (0 != (o = a.contacts().skipNull())) {
ClientContact* c = static_cast<ClientContact*>(o->get());
contactDeleted(*c);
a.removeContact(c->toString());
}
// Clear account own instances
if (a.contact() && a.contact()->resources().skipNull()) {
String instid;
a.contact()->buildInstanceId(instid);
a.contact()->resources().clear();
removeContacts(instid);
}
}
// Set account own contact
static void setAccountContact(ClientAccount* acc)
{
if (!acc)
return;
URI tmp(acc->toString());
String uri(tmp.getUser() + "@" + tmp.getHost());
String cId;
ClientContact::buildContactId(cId,acc->toString(),uri);
acc->setContact(new ClientContact(0,cId,acc->toString(),uri));
}
// Retrieve the selected account
static ClientAccount* selectedAccount(ClientAccountList& accounts, Window* wnd = 0,
const String& list = String::empty())
{
String account;
if (!Client::valid())
return 0;
if (!list)
Client::self()->getSelect(s_accountList,account,wnd);
else
Client::self()->getSelect(list,account,wnd);
return account ? accounts.findAccount(account) : 0;
}
// Retrieve the chat contact
static inline ClientContact* selectedChatContact(ClientAccountList& accounts, Window* wnd = 0)
{
String c;
if (Client::valid())
Client::self()->getSelect(s_chatContactList,c,wnd);
return c ? accounts.findContact(c) : 0;
}
// Build account action item from account id
static inline String& buildAccAction(String& buf, const String& action, ClientAccount* acc)
{
buf = action + ":" + acc->toString();
return buf;
}
// Fill acc_login/logout active parameters
static void fillAccLoginActive(NamedList& p, ClientAccount* acc)
{
if (acc && isTelProto(acc->protocol())) {
p.addParam("active:" + s_actionLogin,String::boolText(true));
p.addParam("active:" + s_actionLogout,String::boolText(true));
}
else {
bool offline = !acc || acc->resource().offline();
p.addParam("active:" + s_actionLogin,String::boolText(acc && offline));
p.addParam("active:" + s_actionLogout,String::boolText(!offline));
}
}
// Fill acc_del/edit active parameters
static inline void fillAccEditActive(NamedList& p, bool active)
{
const char* tmp = String::boolText(active);
p.addParam("active:acc_del",tmp);
p.addParam("active:acc_edit",tmp);
}
// Utility function used to save a widget's text
static inline void saveParam(NamedList& params,
const String& prefix, const String& param, Window* wnd)
{
String val;
Client::self()->getText(prefix + param,val,false,wnd);
params.setParam(param,val);
}
// Utility function used to save a widget's check state
static inline void saveCheckParam(NamedList& params, const String& prefix,
const String& param, Window* wnd, bool defVal = false)
{
Client::self()->getCheck(prefix + param,defVal,wnd);
params.setParam(param,String::boolText(defVal));
}
// Retrieve account protocol, username, host from UI
static bool getAccount(Window* w, String* proto, String* user, String* host)
{
if (!(proto || user || host))
return false;
bool noWiz = !s_accWizard->isWindow(w);
String p;
if (host && !proto)
proto = &p;
if (proto) {
Client::self()->getText(noWiz ? s_accProtocol : s_accWizProtocol,
*proto,false,w);
if (proto->null()) {
showError(w,"A protocol must be selected");
return false;
}
}
if (user) {
Client::self()->getText("acc_username",*user,false,w);
if (user->null()) {
showError(w,"Account username is mandatory");
return false;
}
}
if (host) {
String prefix;
prefix << "acc_proto_" << getProtoPage(*proto) << "_";
Client::self()->getText(prefix + "domain",*host,false,w);
if (host->null()) {
if (*proto == s_jabber) {
showError(w,"Account domain is mandatory for the selected protocol");
return false;
}
Client::self()->getText(prefix + "server",*host,false,w);
if (host->null()) {
showError(w,"You must enter a domain or server");
return false;
}
}
}
return true;
}
// Retrieve account data from UI
static bool getAccount(Window* w, NamedList& p, ClientAccountList& accounts)
{
if (!Client::valid())
return false;
String proto;
String user;
String host;
if (!getAccount(w,&proto,&user,&host))
return false;
p.assign(proto + ":" + user + "@" + host);
p.addParam("enabled",String::boolText(true));
// Account flags
p.addParam("protocol",proto);
String prefix = "acc_";
// Save account parameters
for (const String* par = s_accParams; !par->null(); par++)
saveParam(p,prefix,*par,w);
for (const String* par = s_accBoolParams; !par->null(); par++)
saveCheckParam(p,prefix,*par,w);
prefix << "proto_" << getProtoPage(proto) << "_";
for (const String* par = s_accProtoParams; !par->null(); par++)
saveParam(p,prefix,*par,w);
// Options
prefix << "opt_";
String options;
for (ObjList* o = ClientLogic::s_accOptions.skipNull(); o; o = o->skipNext()) {
String* opt = static_cast<String*>(o->get());
bool checked = false;
Client::self()->getCheck(prefix + *opt,checked,w);
if (checked)
options.append(*opt,",");
}
bool reg = false;
Client::self()->getCheck("acc_register",reg,w);
if (reg)
options.append("register",",");
p.setParam("options",options);
dumpList(p,"Got account",w);
return true;
}
// Update account status and login/logout active status if selected
static void updateAccountStatus(ClientAccount* acc, ClientAccountList* accounts,
Window* wnd = 0)
{
if (!acc)
return;
NamedList p("");
acc->fillItemParams(p);
p.addParam("check:enabled",String::boolText(acc->startup()));
p.addParam("status_image",resStatusImage(acc->resource().m_status),false);
Client::self()->updateTableRow(s_accountList,acc->toString(),&p,false,wnd);
// Remove pending requests if offline
if (acc->resource().offline())
PendingRequest::clear(acc->toString());
// Set login/logout enabled status
bool selected = accounts && acc == selectedAccount(*accounts,wnd);
NamedList pp("");
if (selected)
fillAccLoginActive(pp,acc);
Client::self()->setParams(&pp,wnd);
}
// Add account pending status
static void addAccPendingStatus(NamedList& p, ClientAccount* acc, AccountStatus* stat = 0)
{
if (!(acc && acc->hasPresence()))
return;
if (!stat)
stat = AccountStatus::current();
if (!stat)
return;
const char* s = lookup(stat->status(),ClientResource::s_statusName);
acc->m_params.addParam("internal.status.status",s,false);
p.addParam("show",s,false);
acc->m_params.addParam("internal.status.text",stat->text(),false);
p.addParam("status",stat->text(),false);
}
// Forward declaration needed in setAccountStatus()
void removeAccNotifications(ClientAccount* acc);
// Set account status from global. Update UI. Notify remote party
// Use current status if none specified
static void setAccountStatus(ClientAccountList* accounts, ClientAccount* acc,
AccountStatus* stat = 0, NamedList* upd = 0, bool checkPwd = true)
{
if (!acc)
return;
if (!stat)
stat = AccountStatus::current();
if (!stat)
return;
Debug(ClientDriver::self(),DebugInfo,
"setAccountsStatus(%s) set=(%u,%s) acc=(%u,%s)",
acc->toString().c_str(),stat->status(),stat->text().c_str(),
acc->resource().m_status,acc->resource().m_text.c_str());
if (acc->resource().m_status == ClientResource::Connecting &&
stat->status() != ClientResource::Offline)
return;
bool changed = false;
bool login = false;
bool logout = false;
switch (stat->status()) {
case ClientResource::Online:
if (acc->resource().m_status == ClientResource::Offline)
changed = login = true;
else {
changed = acc->resource().setStatus(stat->status());
if (acc->hasPresence())
changed = acc->resource().setStatusText(stat->text()) || changed;
}
break;
case ClientResource::Offline:
changed = logout = !acc->resource().offline();
break;
case ClientResource::Busy:
case ClientResource::Dnd:
case ClientResource::Away:
case ClientResource::Xa:
if (!acc->hasPresence()) {
// Just connect the account if not already connected
changed = login = acc->resource().offline();
break;
}
if (!acc->resource().offline()) {
changed = acc->resource().setStatus(stat->status());
changed = acc->resource().setStatusText(stat->text()) || changed;
}
else
changed = login = true;
break;
}
if (!changed)
return;
acc->m_params.clearParam("internal.status",'.');
Message* m = 0;
if (login || logout) {
if (login && checkPwd && !acc->params().getValue("password")) {
getAccPasswordWnd(acc->toString(),true);
return;
}
m = userLogin(acc,login);
// Update account status
if (login) {
acc->resource().m_status = ClientResource::Connecting;
addAccPendingStatus(*m,acc,stat);
}
else {
acc->resource().m_status = ClientResource::Offline;
// Avoid notification when disconnected
acc->m_params.setParam("internal.nologinfail",String::boolText(true));
// Remove account notifications now
removeAccNotifications(acc);
}
acc->resource().setStatusText();
}
else
m = Client::buildNotify(true,acc->toString(),acc->resource(false));
NamedList set("");
NamedList* p = 0;
if (upd)
p = new NamedList("");
else
p = &set;
p->addParam("status_image",resStatusImage(acc->resource().m_status),false);
const char* sName = acc->resource().statusName();
NamedString* status = new NamedString("status",sName);
status->append(acc->resource().m_text,": ");
p->addParam(status);
if (upd)
upd->addParam(new NamedPointer(acc->toString(),p,String::boolText(false)));
else
Client::self()->setTableRow(s_accountList,acc->toString(),p);
if (accounts)
updateAccountStatus(acc,accounts);
Engine::enqueue(m);
}
// Set enabled accounts status from global. Update UI. Notify remote party
static void setAccountsStatus(ClientAccountList* accounts)
{
if (!Client::s_engineStarted)
return;
if (!accounts)
return;
// Update UI
AccountStatus* stat = AccountStatus::current();
AccountStatus::updateUi();
NamedList upd("");
for (ObjList* o = accounts->accounts().skipNull(); o; o = o->skipNext()) {
ClientAccount* acc = static_cast<ClientAccount*>(o->get());
if (!acc->startup())
continue;
setAccountStatus(accounts,acc,stat,&upd);
}
if (upd.count())
Client::self()->updateTableRows(s_accountList,&upd);
}
// Login account proxy for DefaultLogic::loginAccount()
// Check password if required
static bool loginAccount(ClientLogic* logic, const NamedList& account, bool login,
bool checkPwd = true)
{
if (login && checkPwd && !account.getValue("password"))
return 0 != getAccPasswordWnd(account,true);
return logic && logic->loginAccount(account,login);
}
// Fill a list used to update a chat contact UI
// data: fill contact name, account ...
// status: fill contact status
static void fillChatContact(NamedList& p, ClientContact& c, bool data, bool status)
{
if (!(data || status))
return;
// Fill status
if (status) {
ClientResource* res = c.status();
int stat = c.online() ? ClientResource::Online : ClientResource::Offline;
if (res)
stat = res->m_status;
String img = resStatusImage(stat);
p.addParam("image:status_image",img,false);
p.addParam("name_image",img,false);
String text;
if (res)
text = res->m_text;
p.addParam("status_text",text ? text.c_str() : ClientResource::statusDisplayText(stat));
p.addParam("status",lookup(stat,ClientResource::s_statusName));
}
// Fill other data
if (!data)
return;
p.addParam("account",c.accountName());
p.addParam("name",c.m_name);
p.addParam("contact",c.uri());
p.addParam("subscription",c.m_subscription);
NamedString* groups = new NamedString("groups");
Client::appendEscape(*groups,c.groups());
p.addParam(groups);
}
// Enable/disable chat contacts actions
static void enableChatActions(ClientContact* c, bool checkVisible = true)
{
if (!Client::valid())
return;
// Check if the chat tab is visible
if (c && checkVisible) {
String tab;
Client::self()->getSelect(s_mainwindowTabs,tab);
if (tab != "tabChat")
c = 0;
}
const char* s = String::boolText(c != 0);
NamedList p("");
p.addParam("active:" + s_chat,s);
p.addParam("active:" + s_chatCall,String::boolText(c && c->findAudioResource()));
p.addParam("active:" + s_fileSend,String::boolText(c && c->findFileTransferResource()));
p.addParam("active:" + s_chatShowLog,s);
p.addParam("active:" + s_chatEdit,s);
p.addParam("active:" + s_chatDel,s);
p.addParam("active:" + s_chatInfo,s);
p.addParam("active:" + s_chatSub,s);
p.addParam("active:" + s_chatUnsubd,s);
p.addParam("active:" + s_chatUnsub,s);
Client::self()->setParams(&p);
}
// Change a contact's docked chat status
// Re-create the chat if the contact has one
static void changeDockedChat(ClientContact& c, bool on)
{
if (!c.hasChat()) {
c.setDockedChat(on);
return;
}
// Get chat history and message
String history;
String input;
c.getChatHistory(history,true);
c.getChatInput(input);
// Retrieve properties
String tempItemCount;
String tempItemReplace;
c.getChatProperty("history","_yate_tempitemcount",tempItemCount);
c.getChatProperty("history","_yate_tempitemreplace",tempItemReplace);
// Re-create chat
c.destroyChatWindow();
c.setDockedChat(on);
c.createChatWindow();
NamedList p("");
fillChatContact(p,c,true,true);
ClientResource* res = c.status();
c.updateChatWindow(p,"Chat [" + c.m_name + "]",
resStatusImage(res ? res->m_status : ClientResource::Offline));
// Set old chat state
c.setChatHistory(history,true);
c.setChatInput(input);
c.setChatProperty("history","_yate_tempitemcount",tempItemCount);
c.setChatProperty("history","_yate_tempitemreplace",tempItemReplace);
c.showChat(true);
}
// Retrieve the selected item in muc room members list
static MucRoomMember* selectedRoomMember(MucRoom& room)
{
Window* w = room.getChatWnd();
if (!w)
return 0;
NamedList p("");
String tmp("getselect:" + s_mucMembers);
p.addParam(tmp,"");
Client::self()->getTableRow(ClientContact::s_dockedChatWidget,room.resource().toString(),&p,w);
const String& id = p[tmp];
return room.findMemberById(id);
}
// Enable/disable MUC room actions
static void enableMucActions(NamedList& p, MucRoom& room, MucRoomMember* member,
bool roomActions = true)
{
// Room related actions
if (roomActions) {
p.addParam("active:" + s_mucChgSubject,String::boolText(room.canChangeSubject()));
p.addParam("active:" + s_mucChgNick,String::boolText(room.resource().online()));
p.addParam("active:" + s_mucInvite,String::boolText(room.canInvite()));
}
// Member related actions
if (member && !room.ownMember(member)) {
p.addParam("active:" + s_mucPrivChat,String::boolText(room.canChatPrivate()));
p.addParam("active:" + s_mucKick,String::boolText(member->online() && room.canKick(member)));
p.addParam("active:" + s_mucBan,
String::boolText(member->online() && member->m_uri && room.canBan(member)));
}
else {
const char* no = String::boolText(false);
p.addParam("active:" + s_mucPrivChat,no);
p.addParam("active:" + s_mucKick,no);
p.addParam("active:" + s_mucBan,no);
}
}
// Update the status of a MUC room member
static void updateMucRoomMember(MucRoom& room, MucRoomMember& item, Message* msg = 0)
{
NamedList* pList = new NamedList("");
NamedList* pChat = 0;
const char* upd = String::boolText(true);
bool canChat = false;
if (room.ownMember(item.toString())) {
canChat = room.canChat();
fillChatContact(*pList,room,true,true);
pChat = new NamedList(*pList);
// Override some parameters
pChat->setParam("name",room.uri());
pList->setParam("name",item.m_name);
pList->setParam("groups","Me");
// Enable actions
enableMucActions(*pChat,room,selectedRoomMember(room));
if (item.offline()) {
pChat->addParam("room_subject","");
// Mark all members offline
for (ObjList* o = room.resources().skipNull(); o; o = o->skipNext()) {
MucRoomMember* m = static_cast<MucRoomMember*>(o->get());
if (!m->offline()) {
m->m_status = ClientResource::Offline;
updateMucRoomMember(room,*m);
}
}
// Add destroy notification in room chat
if (msg && msg->getBoolValue("muc.destroyed")) {
String text("Room was destroyed");
// Room destroyed
const char* rr = msg->getValue("muc.destroyreason");
if (!TelEngine::null(rr))
text << " (" << rr << ")";
const char* altRoom = msg->getValue("muc.alternateroom");
if (!TelEngine::null(altRoom))
text << "\r\nPlease join " << altRoom;
addChatNotify(room,text,msg->msgTime().sec());
}
}
}
else {
pList->addParam("account",room.accountName());
pList->addParam("name",item.m_name);
pList->addParam("groups",lookup(item.m_role,MucRoomMember::s_roleName));
pList->addParam("status_text",ClientResource::statusDisplayText(item.m_status));
pList->addParam("image:status_image",resStatusImage(item.m_status));
if (room.hasChat(item.toString())) {
pChat = new NamedList(*pList);
pChat->setParam("name",room.uri() + " - " + item.m_name);
canChat = room.canChatPrivate() && item.online();
}
// Remove offline non members from members list
if (item.offline() && item.m_affiliation <= MucRoomMember::Outcast)
upd = 0;
}
// Update members list in room page
NamedList tmp("");
NamedList* params = new NamedList("");
params->addParam(new NamedPointer(item.toString(),pList,upd));
tmp.addParam(new NamedPointer("updatetablerows:" + s_mucMembers,params));
room.updateChatWindow(room.resource().toString(),tmp);
if (pChat) {
// Enable/disable chat
pChat->addParam("active:" + s_chatSend,String::boolText(canChat));
pChat->addParam("active:message",String::boolText(canChat));
room.updateChatWindow(item.toString(),*pChat);
TelEngine::destruct(pChat);
}
}
// Show a MUC room's chat. Create and initialize it if not found
static void createRoomChat(MucRoom& room, MucRoomMember* member = 0, bool active = true)
{
if (!member)
member = &room.resource();
if (room.hasChat(member->toString())) {
room.showChat(member->toString(),true,active);
return;
}
room.createChatWindow(member->toString());
updateMucRoomMember(room,*member);
if (!room.ownMember(member)) {
room.showChat(member->toString(),true,active);
return;
}
// Build context menu(s)
NamedList tmp("");
// Room context menu
String menuName("menu_" + room.resource().toString());
NamedList* pRoom = new NamedList(menuName);
pRoom->addParam("title","Room");
pRoom->addParam("item:" + s_mucChgNick,"");
pRoom->addParam("item:" + s_mucChgSubject,"");
pRoom->addParam("item:","");
pRoom->addParam("item:" + s_mucInvite,"");
pRoom->addParam("item:","");
pRoom->addParam("item:" + s_mucRoomShowLog,"");
tmp.addParam(new NamedPointer("setmenu",pRoom,""));
// Members context menu
menuName << "_" << s_mucMembers;
NamedList* pMembers = new NamedList(menuName);
pMembers->addParam("item:" + s_mucPrivChat,"");
pMembers->addParam("item:","");
pMembers->addParam("item:" + s_mucKick,"");
pMembers->addParam("item:" + s_mucBan,"");
pMembers->addParam("item:","");
pMembers->addParam("item:" + s_mucMemberShowLog,"");
NamedList* p = new NamedList("");
p->addParam(new NamedPointer("contactmenu",pMembers));
tmp.addParam(new NamedPointer("setparams:" + s_mucMembers,p));
room.updateChatWindow(room.resource().toString(),tmp);
// Show it
room.showChat(member->toString(),true,active);
}
// Build a parameter list used to update an item in notification area
static inline void buildNotifAreaId(String& id, const char* itemType, const String& account,
const String& contact = String::empty())
{
id = itemType;
ClientContact::buildContactId(id,account,contact);
}
// Build a parameter list used to update an item in notification area
static NamedList* buildNotifArea(NamedList& list, const char* itemType, const String& account,
const String& contact, const char* title = 0, const char* extraParams = 0)
{
String id;
buildNotifAreaId(id,itemType,account,contact);
NamedList* upd = new NamedList(id);
list.addParam(new NamedPointer(id,upd,String::boolText(true)));
upd->addParam("item_type",itemType);
upd->addParam("account",account);
upd->addParam("contact",contact,false);
upd->addParam("title",title,false);
String params("item_type,account,contact,title");
params.append(extraParams,",");
upd->addParam("_yate_itemparams",params);
return upd;
}
// Show/hide a button in generic notification. Set its title also
static inline void setGenericNotif(NamedList& list, int index, const char* title)
{
String name;
name << "messages_" << index;
list.addParam("show:" + name,String::boolText(!TelEngine::null(title)));
list.addParam(name,title);
}
// Customize buttons in generic notification
static void setGenericNotif(NamedList& list, const char* title1 = 0,
const char* title2 = 0, const char* title3 = 0)
{
setGenericNotif(list,1,title1);
setGenericNotif(list,2,title2);
setGenericNotif(list,3,title3);
}
// Remove a notification area account/contact item
static inline void removeNotifArea(const char* itemType, const String& account,
const String& contact = String::empty(), Window* wnd = 0)
{
String id;
buildNotifAreaId(id,itemType,account,contact);
Client::self()->delTableRow("messages",id,wnd);
}
// Remove all notifications belonging to an account
static void removeAccNotifications(ClientAccount* acc)
{
if (!acc)
return;
const String& account = acc->toString();
removeNotifArea("loginfail",account);
removeNotifArea("rosterreqfail",account);
}
// Show a contact's info window
// Update it and, optionally, activate it
static bool updateContactInfo(ClientContact* c, bool create = false, bool activate = false)
{
if (!c)
return false;
Window* w = getContactInfoEditWnd(false,c,create);
if (!w)
return false;
NamedList p("");
p.addParam("title","Contact info [" + c->uri() + "]");
p.addParam("name",c->m_name);
p.addParam("username",c->uri());
p.addParam("account",c->accountName());
p.addParam("subscription",c->m_subscription);
Client::self()->setParams(&p,w);
// Add groups
Client::self()->clearTable("groups",w);
for (ObjList* o = c->groups().skipNull(); o; o = o->skipNext())
Client::self()->addOption("groups",o->get()->toString(),false,String::empty(),w);
// Update resources
Client::self()->clearTable("resources",w);
NamedList upd("");
for (ObjList* o = c->resources().skipNull(); o; o = o->skipNext()) {
ClientResource* r = static_cast<ClientResource*>(o->get());
NamedList* l = new NamedList(r->toString());
l->addParam("name",r->m_name);
l->addParam("name_image",resStatusImage(r->m_status),false);
l->addParam("status",r->m_text);
if (r->m_audio)
l->addParam("audio_image",Client::s_skinPath + "phone.png");
upd.addParam(new NamedPointer(r->toString(),l,String::boolText(true)));
}
Client::self()->updateTableRows("resources",&upd,false,w);
// Show the window and activate it
Client::self()->setVisible(w->id(),true,activate);
return true;
}
// Show an edit/add chat contact window
static bool showContactEdit(ClientAccountList& accounts, ClientContact* c = 0)
{
Window* w = getContactInfoEditWnd(true,c,true,true);
if (!w) {
// Activate it if found
w = c ? getContactInfoEditWnd(true,c) : 0;
if (w)
Client::self()->setActive(w->id(),true,w);
return w != 0;
}
NamedList p("");
const char* add = String::boolText(c == 0);
const char* edit = String::boolText(c != 0);
p.addParam("show:chataccount",add);
p.addParam("show:frame_uri",add);
p.addParam("show:chatcontact_account",edit);
p.addParam("show:chatcontact_uri",edit);
Client::self()->clearTable("groups",w);
// Add groups used by all accounts
NamedList upd("");
for (ObjList* o = accounts.accounts().skipNull(); o; o = o->skipNext()) {
ClientAccount* a = static_cast<ClientAccount*>(o->get());
if (!a->hasChat())
continue;
for (ObjList* oc = a->contacts().skipNull(); oc; oc = oc->skipNext()) {
ClientContact* cc = static_cast<ClientContact*>(oc->get());
for (ObjList* og = cc->groups().skipNull(); og; og = og->skipNext()) {
const String& grp = og->get()->toString();
NamedString* param = upd.getParam(grp);
NamedList* p = 0;
if (!param) {
p = new NamedList(grp);
p->addParam("group",grp);
p->addParam("check:group",String::boolText(c == cc));
upd.addParam(new NamedPointer(grp,p,String::boolText(true)));
}
else if (c == cc) {
p = YOBJECT(NamedList,param);
if (p)
p->setParam("check:group",String::boolText(true));
}
}
}
}
Client::self()->updateTableRows("groups",&upd,false,w);
p.addParam("show:request_subscribe",String::boolText(c == 0));
if (c) {
p.addParam("context",c->toString());
String title("Edit friend ");
if (c->m_name && (c->m_name != c->uri()))
title << "'" << c->m_name << "' ";
title << "<" << c->uri() << ">";
p.addParam("title",title);
p.addParam("chatcontact_account",c->accountName());
p.addParam("chatcontact_uri",c->uri());
p.addParam("name",c->m_name);
}
else {
p.addParam("context","");
p.addParam("title","Add friend");
p.addParam("username","");
p.addParam("domain","");
p.addParam("name","");
p.addParam("check:request_subscribe",String::boolText(true));
// Fill accounts
Client::self()->addOption("chataccount",s_notSelected,false,String::empty(),w);
for (ObjList* o = accounts.accounts().skipNull(); o; o = o->skipNext()) {
ClientAccount* a = static_cast<ClientAccount*>(o->get());
if (a->hasChat())
Client::self()->addOption("chataccount",a->toString(),false,String::empty(),w);
}
}
Client::self()->setParams(&p,w);
Client::self()->setVisible(w->id(),true,true);
return true;
}
// Remove sections from postponed contacts. Save the file
static void removePostponedContacts(ObjList& list)
{
ObjList* o = list.skipNull();
if (!o)
return;
for (; o; o = o->skipNext())
s_postponedContacts.clearSection(o->get()->toString());
s_postponedContacts.save();
}
// Find a temporary wizard
static inline ClientWizard* findTempWizard(Window* wnd)
{
if (!wnd)
return 0;
ObjList* o = s_tempWizards.find(wnd->id());
return o ? static_cast<ClientWizard*>(o->get()) : 0;
}
// Show the MUC invite window
static bool showMucInvite(MucRoom& room, ClientAccountList* accounts)
{
if (!Client::valid())
return false;
Window* w = Client::self()->getWindow(s_wndMucInvite);
if (!w)
return false;
NamedList p("");
p.addParam("invite_room",room.uri());
p.addParam("invite_account",room.accountName());
p.addParam("invite_text","");
Client::self()->setParams(&p,w);
Client::self()->clearTable(s_inviteContacts,w);
if (accounts) {
NamedList rows("");
for (ObjList* oa = accounts->accounts().skipNull(); oa; oa = oa->skipNext()) {
ClientAccount* a = static_cast<ClientAccount*>(oa->get());
for (ObjList* oc = a->contacts().skipNull(); oc; oc = oc->skipNext()) {
ClientContact* c = static_cast<ClientContact*>(oc->get());
NamedList* p = new NamedList(c->toString());
fillChatContact(*p,*c,true,true);
rows.addParam(new NamedPointer(c->toString(),p,String::boolText(true)));
}
}
Client::self()->updateTableRows(s_inviteContacts,&rows,false,w);
}
Client::self()->setVisible(s_wndMucInvite,true,true);
return true;
}
// Build a muc.room message
static Message* buildMucRoom(const char* oper, const String& account, const String& room,
const char* reason = 0, const char* contact = 0)
{
Message* m = Client::buildMessage("muc.room",account,oper);
m->addParam("room",room,false);
m->addParam("contact",contact,false);
m->addParam("reason",reason,false);
return m;
}
// Handle 'ok' in MUC invite window
static bool mucInvite(Window* w, ClientAccountList* accounts)
{
if (!(w && accounts && Client::valid() ))
return false;
String account;
Client::self()->getText("invite_account",account,false,w);
ClientAccount* acc = accounts->findAccount(account);
if (!acc) {
showError(w,"Account not found!");
return false;
}
String room;
Client::self()->getText("invite_room",room,false,w);
MucRoom* r = acc->findRoomByUri(room);
if (!r) {
showError(w,"MUC room not found!");
return false;
}
String text;
Client::self()->getText("invite_text",text,false,w);
NamedList p("");
Client::self()->getOptions(s_inviteContacts,&p,w);
unsigned int n = p.length();
for (unsigned int i = 0; i < n; i++) {
NamedString* ns = p.getParam(i);
if (!(ns && ns->name()))
continue;
NamedList tmp("");
Client::self()->getTableRow(s_inviteContacts,ns->name(),&tmp,w);
if (tmp.getBoolValue("check:enabled"))
Engine::enqueue(buildMucRoom("invite",account,room,text,tmp["contact"]));
}
Client::self()->setVisible(w->id(),false);
return true;
}
// Show advanced UI controls
// Conditionally show call account selector and set its selection
static void setAdvancedMode(bool* show = 0)
{
if (!Client::valid())
return;
bool ok = show ? *show : Client::s_settings.getBoolValue("client","advanced_mode",true);
const char* val = String::boolText(ok);
NamedList p("");
p.addParam("check:advanced_mode",val);
p.addParam("show:frame_call_protocol",val);
// Show/hide account selector. Select single account
bool showAcc = ok;
NamedString* account = 0;
NamedList accounts("");
Client::self()->getOptions(s_account,&accounts);
for (unsigned int i = accounts.length(); i > 0; i--) {
NamedString* ns = accounts.getParam(i - 1);
if (!ns || Client::s_notSelected.matches(ns->name()))
continue;
if (!account)
account = ns;
else {
account = 0;
showAcc = true;
break;
}
}
p.addParam("show:frame_call_account",String::boolText(showAcc));
if (account)
p.addParam("select:" + s_account,account->name());
Client::self()->setParams(&p);
}
// Open a choose file dialog used to send/receive file(s)
static bool chooseFileTransfer(bool send, const String& action, Window* w,
const char* file = 0)
{
static const String s_allFilesFilter = "All files (*)";
if (!Client::valid())
return false;
NamedList p("");
p.addParam("action",action);
p.addParam("dir",s_lastFileDir,false);
if (send) {
String filters;
filters << "Image files (*.jpg *.jpeg *.png *bmp *gif *.tiff *.tif)";
filters << "|Video files (*.avi *.divx *.xvid *.mpg *.mpeg)";
filters << "|Portable Document Format files (*.pdf)";
filters << "|" << s_allFilesFilter;
p.addParam("filters",filters);
p.addParam("caption","Choose file to send");
p.addParam("selectedfilter",s_lastFileFilter ? s_lastFileFilter : s_allFilesFilter);
}
else {
p.addParam("save",String::boolText(true));
p.addParam("selectedfile",file,false);
p.addParam("chooseanyfile",String::boolText(true));
}
return Client::self()->chooseFile(w,p);
}
// Update a file transfer item
// addNew: true to add a new item if not found
static bool updateFileTransferItem(bool addNew, const String& id, NamedList& params,
bool setVisible = false)
{
if (!Client::valid())
return false;
Window* w = Client::self()->getWindow(s_wndFileTransfer);
if (!w)
return false;
NamedList p("");
NamedPointer* np = new NamedPointer(id,&params,String::boolText(addNew));
p.addParam(np);
bool ok = Client::self()->updateTableRows("fileprogresslist",&p,false,w);
np->takeData();
if (setVisible)
Client::self()->setVisible(s_wndFileTransfer,true);
return ok;
}
// Retrieve a file transfer item
// Delete the item from list. Drop the channel
static bool getFileTransferItem(const String& id, NamedList& params, Window* w = 0)
{
if (!Client::valid())
return false;
if (!w)
w = Client::self()->getWindow(s_wndFileTransfer);
return w && Client::self()->getTableRow("fileprogresslist",id,&params,w);
}
// Drop a file transfer item
// Delete the item from list. Drop the channel
static bool dropFileTransferItem(const String& id)
{
if (!Client::valid())
return false;
Window* w = Client::self()->getWindow(s_wndFileTransfer);
if (!w)
return false;
NamedList p("");
getFileTransferItem(id,p,w);
const String& chan = p["channel"];
if (chan) {
Message* m = Client::buildMessage("call.drop",String::empty());
m->addParam("id",chan);
m->addParam("reason",p.getBoolValue("send") ? "cancelled" : "closed");
Engine::enqueue(m);
}
bool ok = Client::self()->delTableRow("fileprogresslist",id,w);
// Close window if empty
NamedList items("");
Client::self()->getOptions("fileprogresslist",&items,w);
if (!items.getParam(0))
Client::self()->setVisible(s_wndFileTransfer,false);
return ok;
}
// Add a tray icon to the mainwindow stack
static bool addTrayIcon(const String& type)
{
int prio = 0;
String triggerAction;
NamedList* iconParams = 0;
String name;
name << "mainwindow_" << type << "_icon";
const char* specific = 0;
String info = "Yate Client";
if (type == "main") {
prio = Client::TrayIconMain;
iconParams = new NamedList(name);
iconParams->addParam("icon",Client::s_skinPath + "null_team-32.png");
triggerAction = "action_show_mainwindow";
}
else if (type == "incomingcall") {
prio = Client::TrayIconIncomingCall;
iconParams = new NamedList(name);
iconParams->addParam("icon",Client::s_skinPath + "tray_incomingcall.png");
info << "\r\nAn incoming call is waiting";
triggerAction = s_actionShowCallsList;
specific = "View calls";
}
else if (type == "notification") {
prio = Client::TrayIconNotification;
iconParams = new NamedList(name);
iconParams->addParam("icon",Client::s_skinPath + "tray_notification.png");
info << "\r\nA notification is requiring your attention";
triggerAction = s_actionShowNotification;
specific = "View notifications";
}
else if (type == "incomingchat") {
prio = Client::TrayIconIncomingChat;
iconParams = new NamedList(name);
iconParams->addParam("icon",Client::s_skinPath + "tray_incomingchat.png");
info << "\r\nYou have unread chat";
triggerAction = s_actionPendingChat;
specific = "View chat";
}
if (!iconParams)
return false;
iconParams->addParam("tooltip",info);
iconParams->addParam("dynamicActionTrigger:string",triggerAction,false);
iconParams->addParam("dynamicActionDoubleClick:string",triggerAction,false);
// Add the menu
NamedList* pMenu = new NamedList("menu_" + type);
pMenu->addParam("item:quit","Quit");
pMenu->addParam("item:","");
pMenu->addParam("item:action_show_mainwindow","Show application");
if (prio != Client::TrayIconMain && triggerAction && specific) {
pMenu->addParam("item:","");
pMenu->addParam("item:" + triggerAction,specific);
}
iconParams->addParam(new NamedPointer("menu",pMenu));
return Client::addTrayIcon("mainwindow",prio,iconParams);
}
// Remove a tray icon from mainwindow stack
static inline bool removeTrayIcon(const String& type)
{
return Client::removeTrayIcon("mainwindow","mainwindow_" + type + "_icon");
}
// Notify incoming chat to the user
static void notifyIncomingChat(ClientContact* c, const String& id = String::empty())
{
if (!(c && Client::valid()))
return;
MucRoom* room = c->mucRoom();
if (!room) {
if (c->isChatActive())
return;
c->flashChat();
}
else {
if (!id || room->isChatActive(id))
return;
room->flashChat(id);
}
const String& str = !room ? c->toString() : id;
if (!s_pendingChat.find(str))
s_pendingChat.append(new String(str));
addTrayIcon("incomingchat");
}
// Show the first chat item in pending chat
static void showPendingChat(ClientAccountList* accounts)
{
if (!(accounts && Client::valid()))
return;
bool tryAgain = true;
while (tryAgain) {
String* id = static_cast<String*>(s_pendingChat.remove(false));
if (!s_pendingChat.skipNull()) {
removeTrayIcon("incomingchat");
tryAgain = false;
}
if (!id)
break;
ClientContact* c = accounts->findContact(*id);
MucRoom* room = !c ? accounts->findRoomByMember(*id) : 0;
if (c) {
if (c->hasChat()) {
c->flashChat(false);
c->showChat(true,true);
}
else
c = 0;
}
else if (room) {
if (room->hasChat(*id)) {
room->flashChat(*id,false);
room->showChat(*id,true,true);
}
else
room = 0;
}
TelEngine::destruct(id);
tryAgain = !(c || room);
}
}
// Remove an item from pending chat
// Stop flashing it if a list is given
static void removePendingChat(const String& id, ClientAccountList* accounts = 0)
{
if (!(id && Client::valid()))
return;
s_pendingChat.remove(id);
if (!s_pendingChat.skipNull())
removeTrayIcon("incomingchat");
if (!accounts)
return;
ClientContact* c = accounts->findContact(id);
MucRoom* room = !c ? accounts->findRoomByMember(id) : 0;
if (c)
c->flashChat(false);
else if (room)
room->flashChat(id,false);
}
// Set offline to MUCs belonging to a given account
static void setOfflineMucs(ClientAccount* acc)
{
if (!acc || Client::exiting())
return;
for (ObjList* o = acc->mucs().skipNull(); o; o = o->skipNext()) {
MucRoom* room = static_cast<MucRoom*>(o->get());
if (room->resource().offline())
continue;
room->resource().m_status = ClientResource::Offline;
room->resource().m_affiliation = MucRoomMember::AffNone;
room->resource().m_role = MucRoomMember::RoleNone;
updateMucRoomMember(*room,room->resource());
}
}
// Select a single item in a list containing exactly 1 item not
// matching s_notSelected
// Select the last item otherwise if selLast is true
static bool selectListItem(const String& name, Window* w, bool selLast = true,
bool selNotSelected = true)
{
NamedList p("");
Client::self()->getOptions(name,&p,w);
NamedString* sel = 0;
unsigned int n = p.length();
for (unsigned int i = 0; i < n; i++) {
NamedString* ns = p.getParam(i);
if (!ns || Client::s_notSelected.matches(ns->name()))
continue;
if (!sel || selLast)
sel = ns;
else {
sel = 0;
break;
}
}
if (sel)
return Client::self()->setSelect(name,sel->name(),w);
return selNotSelected && Client::self()->setSelect(name,s_notSelected,w);
}
// Update telephony account selector(s)
static void updateTelAccList(bool ok, ClientAccount* acc)
{
if (!acc)
return;
DDebug(ClientDriver::self(),DebugAll,"updateTelAccList(%d,%p)",ok,acc);
if (ok && (isTelProto(acc->protocol()) || isGmailAccount(acc)))
Client::self()->updateTableRow(s_account,acc->toString());
else
Client::self()->delTableRow(s_account,acc->toString());
}
// Query roster on a given account
static bool queryRoster(ClientAccount* acc)
{
if (!acc)
return false;
Message* m = Client::buildMessage("user.roster",acc->toString(),"query");
m->copyParams(acc->params(),"protocol");
return Engine::enqueue(m);
}
/**
* ClientWizard
*/
ClientWizard::ClientWizard(const String& wndName, ClientAccountList* accounts, bool temp)
: String(wndName),
m_accounts(accounts),
m_temp(temp)
{
if (!temp)
return;
// Build a temporary window
String name = wndName;
name << (unsigned int)Time::msecNow();
assign(name);
if (Client::valid())
Client::self()->createWindowSafe(wndName,name);
Window* w = window();
if (w)
Client::self()->setProperty(*this,"_yate_destroyonhide",String::boolText(true),w);
}
// Handle actions from user interface
bool ClientWizard::action(Window* w, const String& name, NamedList* params)
{
if (!isWindow(w))
return false;
XDebug(ClientDriver::self(),DebugAll,"ClientWizard(%s)::action(%s,%p) [%p]",
c_str(),name.c_str(),params,this);
if (name == s_actionNext) {
onNext();
return true;
}
if (name == s_actionPrev) {
onPrev();
return true;
}
if (name == s_actionCancel) {
onCancel();
return true;
}
return false;
}
// Handle checkable widgets status changes in wizard window
bool ClientWizard::toggle(Window* w, const String& name, bool active)
{
if (!isWindow(w))
return false;
XDebug(ClientDriver::self(),DebugAll,"ClientWizard(%s)::toggle(%s,%u) [%p]",
c_str(),name.c_str(),active,this);
if (name == "window_visible_changed") {
windowVisibleChanged(active);
return false;
}
return false;
}
// Handle user.notify messages. Restart the wizard if the operating account is offline
// Return true if handled
bool ClientWizard::handleUserNotify(const String& account, bool ok, const char* reason)
{
if (!(m_account && m_account == account))
return false;
DDebug(ClientDriver::self(),DebugAll,"ClientWizard(%s)::handleUserNotify(%s,%u)",
c_str(),account.c_str(),ok);
if (ok)
return true;
reset(true);
if (Client::valid() && Client::self()->getVisible(toString())) {
start();
showError(window(),
"The selected account is offline.\r\nChoose another one or close the wizard");
}
return true;
}
// Retrieve the selected account
ClientAccount* ClientWizard::account(const String& list)
{
Window* w = m_accounts ? window() : 0;
ClientAccount* acc = w ? selectedAccount(*m_accounts,w,list) : 0;
if (acc)
m_account = acc->toString();
else
m_account.clear();
XDebug(ClientDriver::self(),DebugAll,"ClientWizard(%s) current account is %s",
c_str(),m_account.c_str());
return acc;
}
// Update next/prev actions active status
void ClientWizard::updateActions(NamedList& p, bool canPrev, bool canNext, bool canCancel)
{
p.addParam("active:" + s_actionPrev,String::boolText(canPrev));
p.addParam("active:" + s_actionNext,String::boolText(canNext));
p.addParam("active:" + s_actionCancel,String::boolText(canCancel));
}
/*
* AccountWizard
*/
// Release the account. Disconnect it if deleted
void AccountWizard::reset(bool full)
{
if (!m_account)
return;
if (full && m_accounts) {
if (!(Engine::exiting() || Client::exiting())) {
ClientAccount* acc = account();
if (acc) {
Engine::enqueue(userLogin(acc,false));
acc->m_params.setParam("internal.nologinfail",String::boolText(true));
}
}
m_accounts->removeAccount(m_account);
}
DDebug(ClientDriver::self(),DebugAll,
"AccountWizard(%s) reset account delObj=%u",c_str(),full);
m_account.clear();
}
// Handle user.notify messages
bool AccountWizard::handleUserNotify(const String& account, bool ok,
const char* reason)
{
if (!m_account || m_account != account)
return false;
DDebug(ClientDriver::self(),DebugAll,"AccountWizard(%s)::handleUserNotify(%s,%u)",
c_str(),account.c_str(),ok);
String s;
if (ok)
s << "Succesfully created account '" << account << "'";
else {
s << "Failed to connect account '" << account << "'";
s.append(reason,"\r\n");
}
Window* w = window();
if (w) {
NamedList p("");
p.addParam("accwiz_result",s);
updateActions(p,!ok,false,false);
Client::self()->setParams(&p,w);
}
reset(!ok);
return true;
}
void AccountWizard::onNext()
{
String page;
currentPage(page);
if (!page)
return;
if (page == "pageAccType")
changePage("pageServer",page);
else if (page == "pageServer") {
// Check if we have a host (domain or server)
String host;
if (getAccount(window(),0,0,&host))
changePage("pageAccount",page);
}
else if (page == "pageAccount") {
if (!m_accounts)
return;
Window* w = window();
// Check if we have a valid, non duplicate account
String proto, user, host;
if (getAccount(w,&proto,&user,&host)) {
if (!m_accounts->findAccount(URI(proto,user,host)))
changePage("pageConnect",page);
else
showAccDupError(w);
}
}
}
void AccountWizard::onPrev()
{
String page;
currentPage(page);
if (page == "pageServer")
changePage("pageAccType",page);
else if (page == "pageAccount")
changePage("pageServer",page);
else if (page == "pageConnect")
changePage("pageAccount",page);
}
void AccountWizard::onCancel()
{
handleUserNotify(m_account,false,"Cancelled");
}
// Change the wizard page
bool AccountWizard::changePage(const String& page, const String& old)
{
Window* w = window();
if (!w)
return false;
// Provider name (update server page after resetting it)
String provName;
const char* nextText = "Next";
bool canPrev = true;
bool canNext = true;
bool canCancel = false;
NamedList p("");
// Use a do {} while() to break to the end and set page
do {
if (!page || page == "pageAccType") {
canPrev = false;
// Init all wizard if first show
if (old)
break;
p.addParam("check:acc_type_telephony",String::boolText(true));
p.addParam("check:acc_type_gtalk",String::boolText(false));
p.addParam("check:acc_type_facebook",String::boolText(false));
p.addParam("check:acc_type_im",String::boolText(false));
p.addParam("check:acc_register",String::boolText(false));
break;
}
if (page == "pageServer") {
// Don't reset the page if not comming from previous
if (old && old != "pageAccType")
break;
bool tel = true;
Client::self()->getCheck("acc_type_telephony",tel,w);
// Fill protocols
Client::self()->clearTable(s_accWizProtocol,w);
String proto;
updateProtocolList(w,s_accWizProtocol,&tel,&p,&proto);
// Fill providers
Client::self()->clearTable(s_accWizProviders,w);
Client::self()->addOption(s_accWizProviders,s_notSelected,false,String::empty(),w);
unsigned int n = Client::s_providers.sections();
for (unsigned int i = 0; i < n; i++) {
NamedList* sect = Client::s_providers.getSection(i);
if (sect && sect->getBoolValue("enabled",true))
updateProvidersItem(w,s_accWizProviders,*sect,&tel);
}
Client::self()->setSelect(s_accWizProviders,s_notSelected,w);
// Select provider
bool prov = false;
Client::self()->getCheck("acc_type_gtalk",prov,w);
if (Client::self()->getCheck("acc_type_gtalk",prov,w) && prov)
provName = "GTalk";
else if (Client::self()->getCheck("acc_type_facebook",prov,w) && prov)
provName = "Facebook";
else {
// Show/hide the advanced page
bool adv = false;
Client::self()->getCheck("acc_showadvanced",adv,w);
selectProtocolSpec(p,proto,adv,s_accWizProtocol);
}
if (provName && !Client::self()->setSelect(s_accWizProviders,provName,w)) {
showError(w,"Provider data not found for selected account type!");
return false;
}
break;
}
if (page == "pageAccount") {
nextText = "Login";
// Don't reset the page if not comming from previous
if (old && old != "pageServer")
break;
p.addParam("acc_username","");
p.addParam("acc_password","");
break;
}
if (page == "pageConnect") {
if (!m_accounts || m_account)
return false;
Window* w = window();
if (!w)
return false;
NamedList a("");
if (!getAccount(w,a,*m_accounts))
return false;
// Build account. Start login
ClientAccount* acc = new ClientAccount(a);
if (!m_accounts->appendAccount(acc)) {
showAccDupError(w);
TelEngine::destruct(acc);
return false;
}
m_account = a;
setAccountContact(acc);
Message* m = userLogin(acc,true);
addAccPendingStatus(*m,acc);
m->addParam("send_presence",String::boolText(false));
m->addParam("request_roster",String::boolText(false));
acc->resource().m_status = ClientResource::Connecting;
TelEngine::destruct(acc);
Engine::enqueue(m);
p.addParam("accwiz_result","Connecting ...");
canPrev = canNext = false;
canCancel = true;
break;
}
return false;
}
while (false);
p.addParam(s_actionNext,nextText,false);
p.addParam("select:" + s_pagesWidget,page ? page : "pageAccType");
updateActions(p,canPrev,canNext,canCancel);
Client::self()->setParams(&p,w);
if (provName)
handleProtoProvSelect(w,s_accWizProviders,provName);
return true;
}
/*
* JoinMucWizard
*/
JoinMucWizard::JoinMucWizard(ClientAccountList* accounts, NamedList* tempParams)
: ClientWizard("joinmucwizard",accounts,tempParams != 0),
m_queryRooms(false),
m_querySrv(false)
{
if (!tempParams)
return;
reset(true);
Window* w = window();
if (!w)
return;
Client::self()->setParams(tempParams,w);
changePage("pageJoinRoom");
Client::self()->setVisible(toString(),true,true);
}
void JoinMucWizard::reset(bool full)
{
selectListItem(s_mucAccounts,window());
m_account.clear();
m_lastPage.clear();
setQuerySrv(false);
setQueryRooms(false);
}
// Handle actions from wizard window. Return true if handled
bool JoinMucWizard::action(Window* w, const String& name, NamedList* params)
{
if (!(Client::valid() && isWindow(w)))
return false;
if (ClientWizard::action(w,name,params))
return true;
// Query MUC services
if (name == "muc_query_servers") {
// Cancel
if (m_querySrv) {
setQuerySrv(false);
return true;
}
ClientAccount* acc = account();
if (!acc)
return true;
String domain;
Client::self()->getText("muc_domain",domain,false,w);
Message* m = Client::buildMessage("contact.info",acc->toString(),"queryitems");
if (domain)
m->addParam("contact",domain);
else if (acc->contact())
m->addParam("contact",acc->contact()->uri().getHost(),false);
String* id = new String("items_" + String((unsigned int)Time::msecNow()));
m->addParam("id",*id);
Engine::enqueue(m);
setQuerySrv(true);
m_requests.clear();
m_requests.append(id);
return true;
}
return false;
}
// Handle selection changes notifications. Return true if handled
bool JoinMucWizard::select(Window* w, const String& name, const String& item,
const String& text)
{
if (!isWindow(w))
return false;
XDebug(ClientDriver::self(),DebugAll,"JoinMucWizard(%s)::select(%s,%s,%s)",
c_str(),name.c_str(),item.c_str(),text.c_str());
if (name == s_mucAccounts) {
account(s_mucAccounts);
String page;
currentPage(page);
if (page == "pageAccount") {
NamedList p("");
updateActions(p,false,!m_account.null(),false);
Client::self()->setParams(&p,w);
}
return true;
}
if (name == "muc_rooms") {
// Update actions
setQueryRooms(m_queryRooms);
return true;
}
return false;
}
// Process contact.info message
bool JoinMucWizard::handleContactInfo(Message& msg, const String& account,
const String& oper, const String& contact)
{
if (m_temp)
return false;
if (!m_account || m_account != account)
return false;
bool info = (oper == "notifyinfo");
if (!info && oper != "notifyitems")
return false;
const String& id = msg["id"];
ObjList* o = m_requests.find(id);
if (!o)
return false;
DDebug(ClientDriver::self(),DebugAll,
"JoinMucWizard(%s) handleContactInfo() contact=%s oper=%s",
c_str(),contact.c_str(),oper.c_str());
if (!info && m_queryRooms) {
Window* w = window();
if (w) {
NamedList upd("");
int n = msg.getIntValue("item.count");
for (int i = 1; i <= n; i++) {
String pref("item." + String(i));
const String& item = msg[pref];
if (!item)
continue;
NamedList* p = new NamedList("");
p->addParam("room",item);
p->addParam("name",msg.getValue(pref + ".name"),false);
upd.addParam(new NamedPointer(item,p,String::boolText(true)));
}
Client::self()->updateTableRows("muc_rooms",&upd,false,w);
}
if (!msg.getBoolValue("partial")) {
o->remove();
setQueryRooms(false);
}
return true;
}
if (!m_querySrv)
return false;
if (info) {
if (contact && msg.getBoolValue("caps.muc")) {
Window* w = window();
if (w)
Client::self()->updateTableRow("muc_server",contact,0,false,w);
}
}
else {
NamedList upd("");
int n = msg.getIntValue("item.count");
for (int i = 1; i <= n; i++) {
String pref("item." + String(i));
const String& item = msg[pref];
if (!item)
continue;
DDebug(ClientDriver::self(),DebugAll,
"JoinMucWizard(%s) requesting info from %s",c_str(),item.c_str());
Message* m = Client::buildMessage("contact.info",m_account,"queryinfo");
m->addParam("contact",item,false);
String* id = new String("info_" + String((unsigned int)Time::msecNow()));
m->addParam("id",*id);
Engine::enqueue(m);
m_requests.append(id);
}
}
if (!msg.getBoolValue("partial"))
o->remove();
if (!o->skipNull())
setQuerySrv(false);
return true;
}
// Handle user.notify messages. Update the accounts list
bool JoinMucWizard::handleUserNotify(const String& account, bool ok, const char* reason)
{
if (!m_accounts || m_temp)
return false;
ClientAccount* acc = m_accounts->findAccount(account);
// Add/remove to/from MUC
if (!(acc && acc->hasChat()))
return false;
Window* w = window();
if (!w)
return false;
if (ok)
Client::self()->updateTableRow(s_mucAccounts,account,0,false,w);
else {
ClientWizard::account(s_mucAccounts);
// Avoid showing another selected account
if (m_account && m_account == account)
Client::self()->setSelect(s_mucAccounts,s_notSelected,w);
Client::self()->delTableRow(s_mucAccounts,account,w);
}
if (m_account && m_account == account)
return ClientWizard::handleUserNotify(account,ok,reason);
return true;
}
// Join MUC room wizard
void JoinMucWizard::onNext()
{
String page;
currentPage(page);
if (!page)
return;
if (page == "pageAccount")
changePage("pageChooseRoomServer",page);
else if (page == "pageChooseRoomServer") {
bool join = false;
Window* w = window();
if (w && Client::self()->getCheck("muc_use_saved_room",join,w))
changePage(join ? "pageJoinRoom" : "pageMucServer",page);
}
else if (page == "pageMucServer") {
Window* w = window();
bool join = true;
if (w && Client::self()->getCheck("mucserver_joinroom",join,w))
changePage(join ? "pageJoinRoom" : "pageRooms",page);
}
else if (page == "pageRooms")
changePage("pageJoinRoom",page);
else if (page == "pageJoinRoom")
joinRoom();
}
void JoinMucWizard::onPrev()
{
String page;
currentPage(page);
if (page == "pageChooseRoomServer")
changePage("pageAccount",page);
else if (page == "pageMucServer")
changePage("pageChooseRoomServer",page);
else if (page == "pageJoinRoom")
changePage(m_lastPage,page);
else if (page == "pageRooms")
changePage("pageMucServer",page);
}
void JoinMucWizard::onCancel()
{
if (isCurrentPage("pageMucServer"))
setQuerySrv(false);
else if (isCurrentPage("pageRooms"))
setQueryRooms(false);
}
// Change the wizard page
bool JoinMucWizard::changePage(const String& page, const String& old)
{
Window* w = window();
if (!w)
return false;
const char* nextText = "Next";
bool canPrev = true;
bool canNext = true;
bool canCancel = false;
NamedList p("");
// Use a do {} while() to break to the end and set page
do {
if (!page || page == "pageAccount") {
canPrev = false;
if (!old) {
Client::self()->updateTableRow(s_mucAccounts,s_notSelected,0,true,w);
selectListItem(s_mucAccounts,window());
}
canNext = (0 != account(s_mucAccounts));
break;
}
if (page == "pageChooseRoomServer") {
if (old == "pageAccount" && !account(s_mucAccounts)) {
showError(window(),"You must select an account");
return false;
}
bool useSaved = true;
if (w) {
String tmp;
Client::self()->getSelect(s_mucSavedRooms,tmp,w);
useSaved = !tmp.null();
}
if (useSaved)
p.addParam("check:muc_use_saved_room",String::boolText(true));
else
p.addParam("check:muc_choose_server",String::boolText(true));
break;
}
if (page == "pageMucServer") {
setQuerySrv(false);
setQueryRooms(false);
canNext = selectedMucServer();
// Reset the page if comming from previous
if (old == "pageChooseRoomServer")
p.addParam("check:mucserver_joinroom",String::boolText(true));
break;
}
if (page == "pageRooms") {
// Request rooms
if (old != "pageMucServer")
break;
ClientAccount* acc = account();
if (!acc)
return false;
String target;
selectedMucServer(&target);
if (target) {
Client::self()->clearTable("muc_rooms",w);
Message* m = Client::buildMessage("contact.info",acc->toString(),"queryitems");
m->addParam("contact",target);
String* id = new String("items_" + String((unsigned int)Time::msecNow()));
m->addParam("id",*id);
Engine::enqueue(m);
m_requests.clear();
m_requests.append(id);
}
else {
showError(w,"You must choose a MUC server");
return false;
}
break;
}
if (page == "pageJoinRoom") {
if (m_temp) {
canPrev = false;
nextText = "Join";
break;
}
ClientAccount* acc = account();
if (!acc)
return false;
String room;
String server;
String nick;
String pwd;
bool history = true;
unsigned int lastMinutes = 30;
if (old == "pageRooms") {
String sel;
Client::self()->getSelect("muc_rooms",sel,w);
int pos = sel ? sel.find('@') : -1;
if (pos > 0) {
room = sel.substr(0,pos);
server = sel.substr(pos + 1);
}
if (!(room && server)) {
showError(w,"You must choose a MUC room");
return false;
}
}
else if (old == "pageMucServer") {
selectedMucServer(&server);
if (!server) {
showError(w,"You must choose a MUC server");
return false;
}
}
else if (old == "pageChooseRoomServer") {
String tmp;
Client::self()->getSelect(s_mucSavedRooms,tmp,w);
if (!tmp)
return false;
bool ok = false;
NamedList* sect = s_mucRooms.getSection(tmp);
if (sect) {
URI uri(*sect);
room = uri.getUser();
server = uri.getHost();
ok = room && server;
}
if (ok) {
nick = (*sect)["nick"];
pwd = (*sect)["password"];
history = sect->getBoolValue("history",true);
if (history) {
int lm = sect->getIntValue("history.newer");
if (lm > 0)
lastMinutes = (unsigned int)lm;
}
}
else {
Client::self()->delTableRow(s_mucSavedRooms,tmp,w);
s_mucRooms.clearSection(tmp);
s_mucRooms.save();
showError(w,"Deleted unknown/invalid room");
return false;
}
}
p.addParam("room_account",acc->toString());
p.addParam("room_room",room);
p.addParam("room_server",server);
if (!nick && acc->contact())
nick = acc->contact()->uri().getUser();
p.addParam("room_nick",nick);
p.addParam("room_password",pwd);
p.addParam("check:room_history",String::boolText(history));
p.addParam("check:room_historylast",String::boolText(history && lastMinutes > 0));
if (lastMinutes > 0)
p.addParam("room_historylast_value",String(lastMinutes));
nextText = "Join";
break;
}
return false;
}
while (false);
p.addParam(s_actionNext,nextText,false);
p.addParam("select:" + s_pagesWidget,page ? page.c_str() : "pageAccount");
if (page != "pageRooms")
updateActions(p,canPrev,canNext,canCancel);
Client::self()->setParams(&p,w);
if (page == "pageRooms")
setQueryRooms(old == "pageMucServer");
// Safe to remember the last page here: it might be the received page
m_lastPage = old;
return true;
}
// Handle the join room action
void JoinMucWizard::joinRoom()
{
Window* w = window();
if (!w)
return;
ClientAccount* acc = 0;
if (!m_temp)
acc = account();
else if (m_accounts) {
String tmp;
Client::self()->getText("room_account",tmp,false,w);
acc = tmp ? m_accounts->findAccount(tmp) : 0;
}
if (!acc) {
showError(w,"There is no selected account");
return;
}
String room;
String server;
Client::self()->getText("room_room",room,false,w);
Client::self()->getText("room_server",server,false,w);
if (!(room && server)) {
showError(w,"There is no room or server to join");
return;
}
String id;
String uri(room + "@" + server);
ClientContact::buildContactId(id,acc->toString(),uri);
MucRoom* r = acc->findRoom(id);
if (r && !r->resource().offline()) {
Client::self()->setVisible(toString(),false);
createRoomChat(*r);
return;
}
String nick;
Client::self()->getText("room_nick",nick,false,w);
if (!nick) {
if (r)
nick = r->resource().m_name;
if (!nick && acc->contact())
nick = acc->contact()->uri().getUser();
}
if (!nick) {
showError(w,"No available nick name");
return;
}
if (!r)
r = new MucRoom(acc,id,room,uri,nick);
else
r->resource().m_name = nick;
bool history = false;
String lastHist;
// Get password and history
Client::self()->getText("room_password",r->m_password,false,w);
Client::self()->getCheck("room_history",history,w);
if (history) {
bool t = false;
if (Client::self()->getCheck("room_historylast",t,w) && t)
Client::self()->getText("room_historylast_value",lastHist,false,w);
}
unsigned int lastMinutes = lastHist.toInteger(0);
Message* m = r->buildJoin(true,history,lastMinutes * 60);
r->resource().m_status = ClientResource::Connecting;
// Ok. Show the window and request join
createRoomChat(*r);
Engine::enqueue(m);
// Save room
Client::self()->updateTableRow(s_mucSavedRooms,uri,0,false,w);
s_mucRooms.clearSection(uri);
NamedList* sect = s_mucRooms.createSection(uri);
if (sect) {
sect->addParam("nick",nick,false);
sect->addParam("password",r->m_password,false);
s_mucRooms.save();
}
Client::self()->setVisible(toString(),false);
}
// Retrieve the selected MUC server if not currently requesting one
bool JoinMucWizard::selectedMucServer(String* buf)
{
if (m_querySrv)
return false;
Window* w = window();
if (!w)
return false;
String tmp;
if (!buf)
buf = &tmp;
Client::self()->getText("muc_server",*buf,false,w);
return !buf->null();
}
// Set/reset servers query
void JoinMucWizard::setQuerySrv(bool on)
{
if (!on)
m_requests.clear();
m_querySrv = on;
XDebug(ClientDriver::self(),DebugAll,"JoinMucWizard(%s) query srv is %s",
c_str(),String::boolText(on));
Window* w = window();
if (!w)
return;
NamedList p("");
const char* active = String::boolText(!m_querySrv);
p.addParam("active:muc_server",active);
p.addParam("active:muc_domain",active);
p.addParam("active:muc_query_servers",active);
p.addParam("active:mucserver_joinroom",active);
p.addParam("active:mucserver_queryrooms",active);
p.addParam("show:frame_progress",String::boolText(m_querySrv));
if (isCurrentPage("pageMucServer"))
updateActions(p,!m_querySrv,selectedMucServer(),m_querySrv);
Client::self()->setParams(&p,w);
}
// Set/reset rooms query
void JoinMucWizard::setQueryRooms(bool on)
{
if (!isCurrentPage("pageRooms"))
return;
Window* w = window();
if (!w)
return;
m_queryRooms = on;
XDebug(ClientDriver::self(),DebugAll,"JoinMucWizard(%s) query rooms is %s",
c_str(),String::boolText(on));
NamedList p("");
p.addParam("active:muc_rooms",String::boolText(!m_queryRooms));
p.addParam("show:frame_progress",String::boolText(m_queryRooms));
String sel;
if (!m_queryRooms)
Client::self()->getSelect("muc_rooms",sel,w);
updateActions(p,!m_queryRooms,!sel.null(),m_queryRooms);
Client::self()->setParams(&p,w);
}
/*
* AccountStatus
*/
// Change the current item. Save to config if changed
bool AccountStatus::setCurrent(const String& name)
{
AccountStatus* s = find(name);
if (!s)
return false;
s_current = s;
updateUi();
Client::s_settings.setValue("accountstatus","default",s_current->toString());
Client::s_settings.save();
return true;
}
// Append/set an item. Save to config if changed
void AccountStatus::set(const String& name, int stat, const String& text, bool save)
{
if (stat == ClientResource::Unknown || stat == ClientResource::Connecting)
return;
AccountStatus* item = find(name);
if (!item) {
item = new AccountStatus(name);
s_items.append(item);
}
bool changed = item->m_status != stat || item->m_text != text;
if (!changed)
return;
item->m_status = stat;
item->m_text = text;
if (!save)
return;
String s = lookup(item->status(),ClientResource::s_statusName);
s << "," << item->text();
Client::s_settings.setValue("accountstatus",item->toString(),s);
Client::s_settings.save();
}
// Load the list from config
void AccountStatus::load()
{
static bool loaded = false;
if (loaded)
return;
NamedList* as = Client::s_settings.getSection("accountstatus");
if (!as)
return;
// Load the list from config
loaded = true;
unsigned int n = as->length();
for (unsigned int i = 0; i < n; i++) {
NamedString* ns = as->getParam(i);
if (!(ns && ns->name()) || ns->name() == "default")
continue;
int stat = ClientResource::Unknown;
String text;
int pos = ns->find(',');
if (pos > 0) {
stat = lookup(ns->substr(0,pos),ClientResource::s_statusName,stat);
text = ns->substr(pos + 1);
}
else
stat = lookup(*ns,ClientResource::s_statusName,stat);
set(ns->name(),stat,text);
}
setCurrent((*as)["default"]);
}
// Initialize the list
void AccountStatus::init()
{
if (s_items.skipNull())
return;
const TokenDict* d = ClientResource::s_statusName;
for (; d && d->token; d++)
set(d->token,d->value,String::empty());
setCurrent(lookup(ClientResource::Online,ClientResource::s_statusName));
}
// Update
void AccountStatus::updateUi()
{
if (!(s_current && Client::self()))
return;
NamedList p("");
p.addParam("image:global_account_status",resStatusImage(s_current->status()));
String info("Current status: ");
if (s_current->text())
info << s_current->text();
else
info << ClientResource::statusDisplayText(s_current->status());
p.addParam("property:global_account_status:toolTip",info);
Client::self()->setParams(&p);
}
/*
* PendingRequest
*/
// Remove all account's requests
void PendingRequest::clear(const String& account)
{
for (ObjList* o = s_items.skipNull(); o; ) {
PendingRequest* req = static_cast<PendingRequest*>(o->get());
if (req->m_account != account)
o = o->skipNext();
else {
o->remove();
o = o->skipNull();
}
}
}
// Request info/items from target
PendingRequest* PendingRequest::request(bool info, ClientAccount* acc, const String& target,
bool mucserver)
{
if (!acc)
return 0;
String id;
id << acc->toString() << "_" << target << "_" << info << "_" << mucserver;
PendingRequest* req = find(id);
if (req)
return req;
req = new PendingRequest(id,acc->toString(),target);
req->m_mucServer = mucserver;
s_items.append(req);
XDebug(ClientDriver::self(),DebugAll,"ClientLogic added pending request '%s'",
req->toString().c_str());
Message* m = Client::buildMessage("contact.info",acc->toString(),
info ? "queryinfo" : "queryitems");
m->addParam("contact",target,false);
m->addParam("notify",id);
Engine::enqueue(m);
return req;
}
// Request MUC rooms from target
bool PendingRequest::requestMucRooms(ClientAccount* acc, const String& target)
{
if (!acc)
return false;
String id;
id << acc->toString() << "_" << target << "_mucrooms";
if (find(id))
return true;
PendingRequest* req = new PendingRequest(id,acc->toString(),target);
req->m_mucRooms = true;
s_items.append(req);
Message* m = Client::buildMessage("contact.info",acc->toString(),"queryitems");
m->addParam("contact",target,false);
m->addParam("notify",id);
Engine::enqueue(m);
return true;
}
/*
* ContactChatNotify
*/
// Check for timeout. Reset the timer if a notification is returned
ContactChatNotify::State ContactChatNotify::timeout(Time& time)
{
if (m_paused) {
if (m_paused > time.msec())
return None;
m_paused = 0;
return Paused;
}
if (m_inactive) {
if (m_inactive > time.msec())
return None;
m_inactive = 0;
return Inactive;
}
return None;
}
// Send the notification
void ContactChatNotify::send(State state, ClientContact* c, MucRoom* room,
MucRoomMember* member)
{
const char* s = ::lookup(state,s_states);
if (!s)
return;
if (c)
c->sendChat(0,String::empty(),String::empty(),s);
else if (room)
room->sendChat(0,member ? member->m_name : String::empty(),String::empty(),s);
}
// Add or remove items from list. Notify active/composing if changed
void ContactChatNotify::update(ClientContact* c, MucRoom* room, MucRoomMember* member,
bool empty, bool notify)
{
if (!(c || room))
return;
const String& id = c ? c->toString() : (member ? member->toString() : room->toString());
if (!id)
return;
ObjList* found = s_items.find(id);
State st = Composing;
if (empty) {
if (!found)
return;
found->remove();
st = Active;
}
else {
Time time;
if (found) {
ContactChatNotify* item = static_cast<ContactChatNotify*>(found->get());
// Send composing if sent any other notification
notify = !(item->m_paused && item->m_inactive);
item->updateTimers(time);
}
else {
s_items.append(new ContactChatNotify(id,room != 0,member != 0,time));
notify = true;
}
// Make sure the logic is receiving timer notifications
Client::setLogicsTick();
}
if (notify)
send(st,c,room,member);
}
// Check timeouts. Send notifications
bool ContactChatNotify::checkTimeouts(ClientAccountList& list, Time& time)
{
ObjList* o = s_items.skipNull();
while (o) {
ContactChatNotify* item = static_cast<ContactChatNotify*>(o->get());
State state = item->timeout(time);
if (state != None) {
// Send notification
// Remove the item if there is no chat for it
ClientContact* c = 0;
MucRoom* room = 0;
MucRoomMember* member = 0;
if (!item->m_mucRoom) {
c = list.findContact(item->toString());
if (c && !c->hasChat())
c = 0;
}
else if (item->m_mucMember) {
room = list.findRoomByMember(item->toString());
if (room) {
member = room->findMemberById(item->toString());
if (!member)
room = 0;
}
if (room && !room->hasChat(member->toString()))
room = 0;
}
else {
room = list.findRoom(item->toString());
if (room && !room->hasChat(room->toString()))
room = 0;
}
if (c || room)
item->send(state,c,room,member);
else {
// Not found: remove from list
o->remove();
o = o->skipNull();
continue;
}
}
o = o->skipNext();
}
return 0 != s_items.skipNull();
}
/**
* ClientLogic
*/
// Constructor
ClientLogic::ClientLogic(const char* name, int priority)
: m_durationMutex(true,"ClientLogic::duration"), m_name(name), m_prio(priority)
{
Debug(ClientDriver::self(),DebugAll,"ClientLogic(%s) [%p]",m_name.c_str(),this);
Client::addLogic(this);
}
// destructor
ClientLogic::~ClientLogic()
{
Debug(ClientDriver::self(),DebugAll,"ClientLogic(%s) destroyed [%p]",m_name.c_str(),this);
clearDurationUpdate();
Client::removeLogic(this);
}
// obtain the name of the object
const String& ClientLogic::toString() const
{
return m_name;
}
// function which interprets given parameters and takes appropiate action
bool ClientLogic::setParams(const NamedList& params)
{
bool ok = true;
unsigned int l = params.length();
for (unsigned int i = 0; i < l; i++) {
const NamedString* s = params.getParam(i);
if (s) {
String n(s->name());
if (n.startSkip("show:",false))
ok = Client::self()->setShow(n,s->toBoolean()) && ok;
else if (n.startSkip("active:",false))
ok = Client::self()->setActive(n,s->toBoolean()) && ok;
else if (n.startSkip("focus:",false))
ok = Client::self()->setFocus(n,s->toBoolean()) && ok;
else if (n.startSkip("check:",false))
ok = Client::self()->setCheck(n,s->toBoolean()) && ok;
else if (n.startSkip("select:",false))
ok = Client::self()->setSelect(n,*s) && ok;
else if (n.find(':') < 0)
ok = Client::self()->setText(n,*s) && ok;
else
ok = false;
}
}
return ok;
}
// Add a duration object to this client's list
bool ClientLogic::addDurationUpdate(DurationUpdate* duration, bool autoDelete)
{
if (!duration)
return false;
Lock lock(m_durationMutex);
m_durationUpdate.append(duration)->setDelete(autoDelete);
DDebug(ClientDriver::self(),DebugInfo,
"Logic(%s) added duration ('%s',%p) owner=%u",
m_name.c_str(),duration->toString().c_str(),duration,autoDelete);
return true;
}
// Remove a duration object from list
bool ClientLogic::removeDurationUpdate(const String& name, bool delObj)
{
if (!name)
return false;
Lock lock(m_durationMutex);
DurationUpdate* duration = findDurationUpdate(name,false);
if (!duration)
return false;
m_durationUpdate.remove(duration,false);
DDebug(ClientDriver::self(),DebugInfo,
"Logic(%s) removed duration ('%s',%p) delObj=%u",
m_name.c_str(),duration->toString().c_str(),duration,delObj);
lock.drop();
duration->setLogic(0);
if (delObj)
TelEngine::destruct(duration);
return true;
}
// Remove a duration object from list
bool ClientLogic::removeDurationUpdate(DurationUpdate* duration, bool delObj)
{
if (!duration)
return false;
Lock lock(m_durationMutex);
ObjList* obj = m_durationUpdate.find(duration);
if (!obj)
return false;
obj->remove(false);
DDebug(ClientDriver::self(),DebugInfo,
"Logic(%s) removed duration ('%s',%p) delObj=%u",
m_name.c_str(),duration->toString().c_str(),duration,delObj);
lock.drop();
duration->setLogic(0);
if (delObj)
TelEngine::destruct(duration);
return true;
}
// Find a duration update by its name
DurationUpdate* ClientLogic::findDurationUpdate(const String& name, bool ref)
{
Lock lock(m_durationMutex);
ObjList* obj = m_durationUpdate.find(name);
if (!obj)
return 0;
DurationUpdate* duration = static_cast<DurationUpdate*>(obj->get());
return (!ref || duration->ref()) ? duration : 0;
}
// Remove all duration objects
void ClientLogic::clearDurationUpdate()
{
Lock lock(m_durationMutex);
// Reset logic pointer: some of them may not be destroyed when clearing the list
ListIterator iter(m_durationUpdate);
for (GenObject* o = 0; 0 != (o = iter.get());)
(static_cast<DurationUpdate*>(o))->setLogic();
m_durationUpdate.clear();
}
// Release memory. Remove from client's list
void ClientLogic::destruct()
{
clearDurationUpdate();
Client::removeLogic(this);
GenObject::destruct();
}
// Init static logic data
void ClientLogic::initStaticData()
{
// Build account status
AccountStatus::init();
// Build account options list
if (!s_accOptions.skipNull()) {
s_accOptions.append(new String("allowplainauth"));
s_accOptions.append(new String("noautorestart"));
s_accOptions.append(new String("oldstyleauth"));
s_accOptions.append(new String("tlsrequired"));
}
// Build protocol list
s_protocolsMutex.lock();
if (!s_protocols.skipNull()) {
s_protocols.append(new String("sip"));
s_protocols.append(new String("jabber"));
s_protocols.append(new String("h323"));
s_protocols.append(new String("iax"));
}
s_protocolsMutex.unlock();
}
// Called when the user selected a line
bool ClientLogic::line(const String& name, Window* wnd)
{
int l = name.toInteger(-1);
if (l >= 0 && Client::self()) {
Client::self()->line(l);
return true;
}
return false;
}
// Show/hide widget(s)
bool ClientLogic::display(NamedList& params, bool widget, Window* wnd)
{
if (!Client::self())
return false;
bool result = false;
unsigned int n = params.length();
for (unsigned int i = 0; i < n; i++) {
NamedString* p = params.getParam(i);
if (!p)
continue;
bool tmp = false;
if (widget)
tmp = Client::self()->setShow(p->name(),p->toBoolean(),wnd);
else
tmp = Client::self()->setVisible(p->name(),p->toBoolean(),true);
if (tmp)
params.clearParam(p->name());
else
result = false;
}
return result;
}
// Called when the user pressed the backspace key.
// Erase the last digit from the given item and set focus on it
bool ClientLogic::backspace(const String& name, Window* wnd)
{
if (!Client::self())
return false;
String str;
if (Client::self()->getText(name,str,false,wnd) &&
(!str || Client::self()->setText(name,str.substr(0,str.length()-1),false,wnd)))
Client::self()->setFocus(name,false,wnd);
return true;
}
// Called when the user pressed a command action
bool ClientLogic::command(const String& name, Window* wnd)
{
Message* m = new Message("engine.command");
m->addParam("line",name);
Engine::enqueue(m);
return true;
}
// Called when the user changes debug options
bool ClientLogic::debug(const String& name, bool active, Window* wnd)
{
// pos: module name
int pos = name.find(':');
if (pos <= 0)
return false;
// posLine: active/inactive command line
int posLine = name.find(':',pos + 1);
if (posLine < 0 || posLine - pos < 2)
return false;
// Get module/line and enqueue the message
ObjList* modules = name.substr(0,pos).split(',',false);
String line = (active ? name.substr(pos + 1,posLine - pos - 1) : name.substr(posLine + 1));
for (ObjList* o = modules->skipNull(); o; o = o->skipNext()) {
Message* m = new Message("engine.debug");
m->addParam("module",o->get()->toString());
m->addParam("line",line);
Engine::enqueue(m);
}
TelEngine::destruct(modules);
return true;
}
/**
* DefaultLogic
*/
// Constructor
DefaultLogic::DefaultLogic(const char* name, int prio)
: ClientLogic(name,prio),
m_accounts(0)
{
m_accounts = new ClientAccountList(name,new ClientAccount(NamedList::empty()));
s_accWizard = new AccountWizard(m_accounts);
s_mucWizard = new JoinMucWizard(m_accounts);
// Init chat states
s_chatStates.addParam("composing","${sender} is typing ...");
s_chatStates.addParam("paused","${sender} stopped typing");
s_chatStates.addParam("gone","${sender} ended chat session");
s_chatStates.addParam("inactive","${sender} is idle");
s_chatStates.addParam("active","");
}
// Destructor
DefaultLogic::~DefaultLogic()
{
TelEngine::destruct(s_accWizard);
TelEngine::destruct(s_mucWizard);
TelEngine::destruct(m_accounts);
}
// main function which considering de value of the "action" parameter
// Handle actions from user interface
bool DefaultLogic::action(Window* wnd, const String& name, NamedList* params)
{
DDebug(ClientDriver::self(),DebugAll,"Logic(%s) action '%s' in window (%p,%s)",
toString().c_str(),name.c_str(),wnd,wnd ? wnd->id().c_str() : "");
// Translate actions from confirmation boxes
// the window context specifies what action will be taken forward
if (wnd && wnd->context() && (name == "ok") && (wnd->context() != "ok")) {
bool ok = action(wnd,wnd->context(),params);
if (ok)
wnd->hide();
return ok;
}
// Show/hide widgets/windows
bool widget = (name == "display");
if (widget || name == "show")
return params ? display(*params,widget,wnd) : false;
// Start a call
if (name == s_actionCall || name == "callto") {
NamedList dummy("");
if (!params)
params = &dummy;
return callStart(*params,wnd,name);
}
// Start a call from an action specifying the target
if (name.startsWith("callto:")) {
NamedList dummy("");
if (!params)
params = &dummy;
params->setParam("target",name.substr(7));
return callStart(*params,wnd);
}
// Answer/Hangup
bool anm = (name == s_actionAnswer);
if (anm || name == s_actionHangup) {
if (!m_selectedChannel)
return false;
if (anm)
Client::self()->callAnswer(m_selectedChannel);
else
Client::self()->callTerminate(m_selectedChannel);
return true;
}
anm = name.startsWith("answer:");
if ((anm || name.startsWith("hangup:")) && name.at(7)) {
if (anm)
Client::self()->callAnswer(name.substr(7));
else
Client::self()->callTerminate(name.substr(7));
return true;
}
// Double click on channel: set the active call
if (name == s_channelList)
return m_selectedChannel && ClientDriver::self() &&
ClientDriver::self()->setActive(m_selectedChannel);
// Digit(s) pressed
if (name.startsWith("digit:")) {
NamedList dummy("");
if (!params)
params = &dummy;
params->setParam("digits",name.substr(6));
return digitPressed(*params,wnd);
}
// New line
if (name.startsWith("line:") && line(name.substr(5),wnd))
return false;
// Action taken when receiving a clear action
if (name.startsWith("clear:") && name.at(6))
return clearList(name.substr(6),wnd);
// Delete a list/table item
bool confirm = name.startsWith("deleteitemconfirm:");
if (confirm || name.startsWith("deleteitem:")) {
String list;
int start = confirm ? 18 : 11;
int pos = name.find(":",start);
if (pos > 0)
return deleteItem(name.substr(start,pos - start),name.substr(pos + 1),wnd,confirm);
return false;
}
// Delete a selected list/table item
if (name.startsWith("deleteselecteditem:") && name.at(19))
return deleteSelectedItem(name.substr(19),wnd);
// 'settext' action
if (name.startsWith("settext:") && name.at(8)) {
int pos = name.find(':',9);
String ctrl;
String text;
if (pos > 9) {
ctrl = name.substr(8,pos - 8);
text = name.substr(pos + 1);
}
else
ctrl = name.substr(8);
bool ok = Client::self() && Client::self()->setText(ctrl,text,false,wnd);
if (ok)
Client::self()->setFocus(ctrl,false,wnd);
return ok;
}
// action taken when receiving a backspace
if (name.startsWith("back:"))
return backspace(name.substr(5),wnd);
if (name.startsWith("command:") && name.at(8))
return command(name.substr(8),wnd);
// *** Specific action handlers
if (handleChatContactAction(name,wnd) ||
handleMucsAction(name,wnd,params) ||
handleFileTransferAction(name,wnd,params))
return true;
// *** MUC
if (name == "joinmuc_wizard") {
s_mucWizard->start();
return true;
}
// *** Account management
// Create a new account or edit an existing one
bool newAcc = (name == "acc_new");
if (newAcc || name == "acc_edit" || name == s_accountList)
return editAccount(newAcc,params,wnd);
if (name == "acc_new_wizard") {
s_accWizard->start();
return true;
}
// User pressed ok button in account edit window
if (name == "acc_accept")
return acceptAccount(params,wnd);
// Delete an account
if (name.startsWith("acc_del")) {
// Empty: delete the current selection
if (!name.at(7))
return delAccount(String::empty(),wnd);
// Handle 'acc_del:'
if (name.length() > 9 && name.at(7) == ':' && name.at(8))
return delAccount(name.substr(8),wnd);
}
// Login/logout
bool login = (name == s_actionLogin);
if (login || name == s_actionLogout) {
ClientAccount* acc = selectedAccount(*m_accounts,wnd);
return acc ? ::loginAccount(this,acc->params(),login) : false;
}
login = name.startsWith(s_actionLogin + ":",false);
if (login || name.startsWith(s_actionLogout + ":",false)) {
ClientAccount* acc = 0;
if (login)
acc = m_accounts->findAccount(name.substr(s_actionLogin.length() + 1));
else
acc = m_accounts->findAccount(name.substr(s_actionLogout.length() + 1));
return acc ? ::loginAccount(this,acc->params(),login) : false;
}
// Account status
if (name.startsWith("setStatus")) {
if (AccountStatus::setCurrent(name.substr(9).toLower()))
setAccountsStatus(m_accounts);
return true;
}
// *** Address book actions
// Call the current contact selection
if (name == "abk_call" || name == s_contactList)
return callContact(params,wnd);
// Add/edit contact
bool newCont = (name == "abk_new");
if (newCont || name == "abk_edit")
return editContact(newCont,params,wnd);
// Delete a contact
if (name.startsWith("abk_del")) {
// Empty: delete the current selection
if (!name.at(7))
return delContact(String::empty(),wnd);
// Handle 'abk_del:'
if (name.length() > 9 && name.at(7) == ':' && name.at(8))
return delContact(name.substr(8),wnd);
}
// User pressed "ok" in a pop-up window like the one
// for adding/editing a contact
if (name == "abk_accept")
return acceptContact(params,wnd);
// *** Call log management
bool logCall = (name == "log_call");
if (logCall || name == "log_contact") {
String billid;
if (Client::valid())
Client::self()->getSelect(s_logList,billid,wnd);
if (!billid)
return false;
if (logCall)
return callLogCall(billid,wnd);
return callLogCreateContact(billid);
}
if (name == "log_clear")
return callLogClear(s_logList,String::empty());
// *** Miscellaneous
// List item changed
if (name == "listitemchanged") {
if (!(params && Client::valid()))
return false;
const String& list = (*params)["list"];
if (!list)
return false;
const String& item = (*params)["item"];
if (!item)
return false;
if (list == s_accountList) {
NamedList tmp("");
if (!Client::self()->getTableRow(list,item,&tmp,wnd))
return false;
String* enabled = tmp.getParam("check:enabled");
if (enabled) {
bool ok = enabled->toBoolean();
ClientAccount* acc = m_accounts->findAccount(item);
if (acc && ok != acc->startup()) {
acc->startup(ok);
acc->save(true,acc->params().getBoolValue("savepassword"));
// Update telephony account selector(s)
updateTelAccList(ok,acc);
setAdvancedMode();
if (Client::s_engineStarted) {
if (ok)
setAccountStatus(m_accounts,acc);
else
loginAccount(acc->params(),false);
}
}
}
}
return false;
}
// OK actions
if (name == "ok") {
if (wnd && wnd->id() == s_wndMucInvite)
return mucInvite(wnd,m_accounts);
}
// Handle show window actions
if (name.startsWith("action_show_"))
Client::self()->setVisible(name.substr(12),true,true);
// Help commands
if (name.startsWith("help:"))
return help(name,wnd);
// Hide windows
if (name == "button_hide" && wnd)
return Client::self() && Client::self()->setVisible(wnd->toString(),false);
// Show/hide messages
bool showMsgs = (name == "messages_show" || name == s_actionShowNotification);
if (showMsgs || name == "messages_close") {
if (name == s_actionShowNotification) {
removeTrayIcon("notification");
if (wnd && Client::valid())
Client::self()->setVisible(wnd->id(),true,true);
}
return showNotificationArea(showMsgs,wnd);
}
// Dialog actions
// Return 'true' to close the dialog
bool dlgRet = false;
if (handleDialogAction(name,dlgRet,wnd))
return dlgRet;
// Wizard actions
if (s_accWizard->action(wnd,name,params) ||
s_mucWizard->action(wnd,name,params))
return true;
ClientWizard* wiz = findTempWizard(wnd);
if (wiz && wiz->action(wnd,name,params))
return true;
// Actions from notification area
if (handleNotificationAreaAction(name,wnd))
return true;
if (name == "textchanged")
return handleTextChanged(params,wnd);
if (name.startsWith("loginpassword:")) {
String account = name.substr(14);
ClientAccount* acc = account ? m_accounts->findAccount(account) : 0;
if (!acc)
return false;
saveParam(acc->m_params,"inputpwd_","password",wnd);
saveCheckParam(acc->m_params,"inputpwd_","savepassword",wnd);
acc->save(true,acc->params().getBoolValue("savepassword"));
if (acc->startup()) {
setAccountStatus(m_accounts,acc,0,0,false);
return true;
}
return ::loginAccount(this,acc->params(),true,false);
}
if (name == s_actionShowCallsList) {
if (Client::valid()) {
Client::self()->setVisible("mainwindow",true,true);
activatePageCalls();
removeTrayIcon("incomingcall");
}
return true;
}
if (name == s_actionPendingChat) {
showPendingChat(m_accounts);
return true;
}
// Quit
if (name == "quit") {
if (!Client::valid())
return false;
Client::self()->quit();
return true;
}
return false;
}
// Handle actions from checkable widgets
bool DefaultLogic::toggle(Window* wnd, const String& name, bool active)
{
DDebug(ClientDriver::self(),DebugAll,
"Logic(%s) toggle '%s'=%s in window (%p,%s)",
toString().c_str(),name.c_str(),String::boolText(active),
wnd,wnd ? wnd->id().c_str() : "");
// Check for window params
if (Client::self() && Window::isValidParamPrefix(name)) {
NamedList p("");
p.addParam(name,String::boolText(active));
return Client::self()->setParams(&p,wnd);
}
if (name.startsWith("setparams:") && name.at(10) && Client::self()) {
String tmp = name.substr(10);
ObjList* obj = tmp.split(';',false);
NamedList p("");
for (ObjList* o = obj->skipNull(); o; o = o->skipNext()) {
String* s = static_cast<String*>(o->get());
const char* param = s->c_str();
bool value = active;
if (s->at(0) == '!') {
param++;
value = !active;
}
if (*param)
p.addParam(param,String::boolText(active));
}
TelEngine::destruct(obj);
return Client::self()->setParams(&p);
}
// *** Channel actions
// Hold
if (name == s_actionHold) {
if (!ClientDriver::self())
return false;
bool ok = !active ? ClientDriver::self()->setActive() :
m_selectedChannel && ClientDriver::self()->setActive(m_selectedChannel);
if (!ok)
enableCallActions(m_selectedChannel);
return ok;
}
// Transfer
if (name == s_actionTransfer) {
// Active: set init flag and wait to select the target
// Else: reset transfer on currently selected channel
if (active)
m_transferInitiated = m_selectedChannel;
else if (m_selectedChannel)
ClientDriver::setAudioTransfer(m_selectedChannel);
return true;
}
// Conference
if (name == s_actionConf) {
bool ok = ClientDriver::setConference(m_selectedChannel,active);
if (!ok)
enableCallActions(m_selectedChannel);
return ok;
}
// Show/hide windows
if (name.startsWith("showwindow:") && name.at(11)) {
String what = name.substr(11);
if (what.startsWith("help:")) {
if (active)
return help(what,wnd);
else
return Client::valid() && Client::self()->setVisible("help",false);
}
return Client::valid() && Client::self()->setVisible(what,active,true);
}
// Wizard toggle
if (s_accWizard->toggle(wnd,name,active) ||
s_mucWizard->toggle(wnd,name,active))
return true;
// Visibility: update checkable widgets having the same name as the window
if (wnd && name == "window_visible_changed") {
if (!Client::valid())
return false;
const char* yText = String::boolText(active);
const char* nText = String::boolText(!active);
NamedList p("");
p.addParam("check:toggle_show_" + wnd->toString(),yText);
p.addParam("check:action_show_" + wnd->toString(),yText);
if (wnd->id() == s_wndAccount || s_accWizard->isWindow(wnd)) {
p.addParam("active:acc_new",nText);
p.addParam("active:acc_new_wizard",nText);
if (active)
fillAccEditActive(p,false);
else
fillAccEditActive(p,0 != selectedAccount(*m_accounts));
// Enable/disable account edit in notification area
NamedList params("messages");
NamedList* p = new NamedList("");
p->addParam("active:messages_acc_edit",String::boolText(!active));
params.addParam(new NamedPointer("applyall",p));
Client::self()->setParams(&params);
}
else if (wnd->id() == s_wndAddrbook) {
p.addParam("active:abk_new",nText);
fillContactEditActive(p,!active);
fillLogContactActive(p,!active);
}
else if (s_mucWizard->isWindow(wnd))
p.addParam("active:joinmuc_wizard",nText);
else if (wnd->id() == ClientContact::s_mucsWnd) {
// Destroy all MUCS when hidden
if (!active) {
ObjList* o = m_accounts->accounts().skipNull();
for (; o; o = o->skipNext()) {
ClientAccount* acc = static_cast<ClientAccount*>(o->get());
for (ObjList* l = acc->mucs().skipNull(); l; l = l->skipNext())
logCloseMucSessions(static_cast<MucRoom*>(l->get()));
acc->mucs().clear();
}
// Remove from pending chat
NamedList p("");
Client::self()->getOptions(ClientContact::s_dockedChatWidget,&p,wnd);
unsigned int n = p.length();
for (unsigned int i = 0; i < n; i++) {
NamedString* ns = p.getParam(i);
if (ns && ns->name())
removePendingChat(ns->name());
}
}
}
else if (wnd->id() == ClientContact::s_dockedChatWnd) {
// Clear chat pages when hidden
// Close chat sessions
if (!active) {
if (!s_changingDockedChat) {
NamedList p("");
Client::self()->getOptions(ClientContact::s_dockedChatWidget,&p,wnd);
unsigned int n = p.length();
for (unsigned int i = 0; i < n; i++) {
NamedString* ns = p.getParam(i);
if (ns && ns->name()) {
removePendingChat(ns->name());
logCloseSession(m_accounts->findContact(ns->name()));
}
}
}
Client::self()->clearTable(ClientContact::s_dockedChatWidget,wnd);
}
}
else if (wnd->id().startsWith(ClientContact::s_chatPrefix)) {
// Close chat session if not active and not destroyed due
// to docked chat changes
if (!(active || s_changingDockedChat))
logCloseSession(m_accounts->findContact(wnd->context()));
}
else {
ClientWizard* wiz = !active ? findTempWizard(wnd) : 0;
if (wiz)
s_tempWizards.remove(wnd->id());
}
Client::self()->setParams(&p);
return true;
}
// Window active changed
if (wnd && name == "window_active_changed") {
if (active) {
// Remove contact from pending when activated
if (wnd->id() == ClientContact::s_dockedChatWnd) {
String sel;
if (Client::self()->getSelect(ClientContact::s_dockedChatWidget,sel,wnd))
removePendingChat(sel,m_accounts);
}
else if (wnd->id().startsWith(ClientContact::s_chatPrefix))
removePendingChat(wnd->context());
}
return true;
}
// Select item if active. Return true if inactive
if (name.startsWith("selectitem:")) {
if (!active)
return true;
String tmp = name.substr(11);
if (!tmp)
return true;
int pos = tmp.find(':');
if (pos > 0 && Client::self())
return Client::self()->setSelect(tmp.substr(0,pos),tmp.substr(pos + 1),wnd);
return true;
}
// Set debug to window
if (name == "log_events_debug") {
bool ok = Client::self() && Client::self()->debugHook(active);
if (ok && !active) {
NamedList p("");
p.addParam("check:debug_sniffer",String::boolText(false));
p.addParam("check:debug_jingle",String::boolText(false));
p.addParam("check:debug_sip",String::boolText(false));
p.addParam("check:debug_h323",String::boolText(false));
p.addParam("check:debug_iax",String::boolText(false));
Client::self()->setParams(&p,wnd);
}
return ok;
}
// Enable the showing of debug information for a certain module or disable it
if (name.startsWith("debug:")) {
if (debug(name.substr(6),active,wnd))
return true;
}
// Save client settings
Client::ClientToggle clientOpt = Client::getBoolOpt(name);
if (clientOpt != Client::OptCount) {
setClientParam(name,String::boolText(active),true,false);
return true;
}
// Advanced button from account window
if (name == "acc_showadvanced") {
if (!Client::valid())
return false;
// Select the page(s)
String proto;
if (active) {
bool wiz = s_accWizard->isWindow(wnd);
Client::self()->getSelect(wiz ? s_accWizProtocol : s_accProtocol,proto);
}
toggle(wnd,"selectitem:acc_proto_advanced:acc_proto_advanced_" + getProtoPage(proto),true);
// Keep all in sync
Client::self()->setCheck(name,active);
// Save it
Client::s_settings.setValue("client",name,String::boolText(active));
Client::save(Client::s_settings);
return true;
}
if (name == "advanced_mode") {
setAdvancedMode(&active);
Client::s_settings.setValue("client",name,String::boolText(active));
Client::save(Client::s_settings);
return true;
}
// Commands
if (name.startsWith("command:") && name.at(8))
return command(name.substr(8) + (active ? " on" : " off"),wnd);
// Handle show window actions
if (name.startsWith("action_show_"))
Client::self()->setVisible(name.substr(12),active,true);
// Chat log options
if (active) {
int v = lookup(name,s_chatLogDict);
if (v == ChatLogSaveAll || v == ChatLogSaveUntilLogout || v == ChatLogNoSave) {
s_chatLog = (ChatLogEnum)v;
Client::s_settings.setValue("client","logchat",name);
Client::s_settings.save();
}
}
return false;
}
// Handle 'select' actions from user interface
bool DefaultLogic::select(Window* wnd, const String& name, const String& item,
const String& text)
{
DDebug(ClientDriver::self(),DebugAll,
"Logic(%s) select name='%s' item='%s' in window (%p,%s)",
toString().c_str(),name.c_str(),item.c_str(),wnd,wnd ? wnd->id().c_str() : "");
if (name == s_accountList) {
if (!Client::valid())
return false;
ClientAccount* a = item ? m_accounts->findAccount(item) : 0;
NamedList p("");
fillAccLoginActive(p,a);
fillAccEditActive(p,!item.null() && !Client::self()->getVisible(s_wndAccount));
Client::self()->setParams(&p,wnd);
return true;
}
if (name == s_contactList) {
if (!Client::valid())
return false;
NamedList p("");
p.addParam("active:abk_call",String::boolText(!item.null()));
fillContactEditActive(p,true,&item);
Client::self()->setParams(&p,wnd);
return true;
}
if (name == s_chatContactList) {
enableChatActions(item ? m_accounts->findContact(item) : 0);
return true;
}
if (name == s_mainwindowTabs) {
ClientContact* c = 0;
if (item == "tabChat")
c = selectedChatContact(*m_accounts,wnd);
else if (isPageCallsActive(wnd,false))
removeTrayIcon("incomingcall");
enableChatActions(c,false);
return true;
}
// Item selected in calls log list
if (name == s_logList) {
if (!Client::self())
return false;
const char* active = String::boolText(!item.null());
NamedList p("");
p.addParam("active:log_call",active);
p.addParam("active:log_del",active);
fillLogContactActive(p,true,&item);
Client::self()->setParams(&p,wnd);
return true;
}
// Page changed in telephony tab
if (name == "framePages") {
if (isPageCallsActive(wnd,true))
removeTrayIcon("incomingcall");
return false;
}
// keep the item in sync in all windows
// if the same object is present in more windows, we will synchronise all of them
if (Client::self())
Client::self()->setSelect(name,item,0,wnd);
// Enable specific actions when a channel is selected
if (name == s_channelList) {
if (isPageCallsActive(wnd,true))
removeTrayIcon("incomingcall");
updateSelectedChannel(&item);
return true;
}
// when an account is selected, the choice of protocol must be cleared
// when a protocol is chosen, the choice of account must be cleared
bool acc = (name == "account");
if (acc || name == "protocol") {
if (Client::s_notSelected.matches(item))
return true;
if (acc)
return Client::self()->setSelect("protocol",s_notSelected,wnd);
return Client::self()->setSelect("account",s_notSelected,wnd);
}
// Handle protocol/providers select in account edit/add or wizard
if (handleProtoProvSelect(wnd,name,item))
return true;
// Wizard select
if (s_accWizard->select(wnd,name,item,text) ||
s_mucWizard->select(wnd,name,item,text))
return true;
// Specific select handlers
if (handleMucsSelect(name,item,wnd,text))
return true;
// Selection changed in docked (room) chat
if (name == ClientContact::s_dockedChatWidget) {
if (item)
removePendingChat(item,m_accounts);
return true;
}
// No more notifications: remove the tray icon
if (name == "messages") {
if (!item)
removeTrayIcon("notification");
return true;
}
// Selection changed in 'callto': do nothing. Just return true to avoid enqueueing ui.event
if (name == "callto")
return true;
return false;
}
// Set a client's parameter. Save the settings file and/or update interface
bool DefaultLogic::setClientParam(const String& param, const String& value,
bool save, bool update)
{
DDebug(ClientDriver::self(),DebugAll,"Logic(%s) setClientParam(%s,%s,%s,%s)",
toString().c_str(),param.c_str(),value.c_str(),
String::boolText(save),String::boolText(update));
update = update && (0 != Client::self());
const char* section = 0;
bool changed = false;
// Bool params
Client::ClientToggle opt = Client::getBoolOpt(param);
if (opt != Client::OptCount) {
if (value.isBoolean()) {
section = "general";
if (Client::valid()) {
bool ok = value.toBoolean();
changed = Client::self()->setBoolOpt(opt,ok,update);
// Special care for some controls
if (opt == Client::OptKeypadVisible)
Client::self()->setShow("keypad",ok);
if (changed && opt == Client::OptDockedChat) {
// Change contacts docked chat
s_changingDockedChat = true;
for (ObjList* o = m_accounts->accounts().skipNull(); o; o = o->skipNext()) {
ClientAccount* a = static_cast<ClientAccount*>(o->get());
if (!a->hasChat())
continue;
for (ObjList* oo = a->contacts().skipNull(); oo; oo = oo->skipNext()) {
ClientContact* c = static_cast<ClientContact*>(oo->get());
changeDockedChat(*c,ok);
}
}
s_changingDockedChat = false;
}
// Clear notifications if disabled
if (opt == Client::OptNotifyChatState && !ok)
ContactChatNotify::clear();
}
}
}
else if (param == "username" || param == "callerid" || param == "domain") {
section = "default";
changed = true;
if (update)
Client::self()->setText("def_" + param,value);
}
if (!section)
return false;
if (!changed)
return true;
// Update/save settings
Client::s_settings.setValue(section,param,value);
if (save)
Client::save(Client::s_settings);
return true;
}
// Process an IM message
bool DefaultLogic::imIncoming(Message& msg)
{
bool stopLogic = false;
return defaultMsgHandler(msg,Client::MsgExecute,stopLogic);
}
// Call execute handler called by the client.
bool DefaultLogic::callIncoming(Message& msg, const String& dest)
{
if (!Client::self())
return false;
const String& fmt = msg["format"];
if (!fmt || fmt != "data")
return Client::self()->buildIncomingChannel(msg,dest);
if (!(msg.userData() && ClientDriver::self() && Client::self()))
return false;
CallEndpoint* peer = static_cast<CallEndpoint*>(msg.userData());
if (!peer)
return false;
const String& file = msg["file_name"];
if (!file)
return false;
const String& oper = msg["operation"];
if (oper != "receive")
return false;
Message m(msg);
m.userData(msg.userData());
m.setParam("callto","dumb/");
if (!Engine::dispatch(m))
return false;
String targetid = m["targetid"];
if (!targetid)
return false;
msg.setParam("targetid",targetid);
static const String extra = "targetid,file_name,file_size,file_md5,file_time";
const String& contact = msg["callername"];
const String& account = msg["in_line"];
ClientAccount* a = account ? m_accounts->findAccount(account) : 0;
ClientContact* c = a ? a->findContactByUri(contact) : 0;
NamedList rows("");
NamedList* upd = buildNotifArea(rows,"incomingfile",account,contact,"Incoming file",extra);
upd->copyParams(msg,extra);
String text;
text << "Incoming file '" << file << "'";
String buf;
if (c)
buildContactName(buf,*c);
else
buf = contact;
text.append(buf,"\r\nContact: ");
text.append(account,"\r\nAccount: ");
upd->addParam("text",text);
showNotificationArea(true,Client::self()->getWindow(s_wndMain),&rows);
return true;
}
// Start an outgoing call
bool DefaultLogic::callStart(NamedList& params, Window* wnd, const String& cmd)
{
if (!(Client::self() && fillCallStart(params,wnd)))
return false;
String target;
const String& ns = params["target"];
if (cmd == s_actionCall) {
// Check google voice target on gmail accounts
String account = params.getValue("account",params.getValue("line"));
if (account && isGmailAccount(m_accounts->findAccount(account))) {
// Allow calling user@domain
int pos = ns.find('@');
bool valid = (pos > 0) && (ns.find('.',pos + 2) >= pos);
if (!valid) {
target = ns;
Client::fixPhoneNumber(target,"().- ");
}
if (target) {
target = target + "@voice.google.com";
params.addParam("ojingle_version","0");
params.addParam("redirectcount","5");
params.addParam("checkcalled",String::boolText(false));
params.addParam("dtmfmethod","rfc2833");
String callParams = params["call_parameters"];
callParams.append("redirectcount,checkcalled,dtmfmethod,ojingle_version",",");
params.setParam("call_parameters",callParams);
}
else if (!valid) {
showError(wnd,"Incorrect number");
Debug(ClientDriver::self(),DebugNote,
"Failed to call: invalid gmail number '%s'",params.getValue("target"));
return false;
}
}
}
// Delete the number from the "callto" widget and put it in the callto history
if (ns) {
Client::self()->delTableRow(s_calltoList,ns);
Client::self()->addOption(s_calltoList,ns,true);
Client::self()->setText(s_calltoList,"");
}
if (target)
params.setParam("target",target);
if (!Client::self()->buildOutgoingChannel(params))
return false;
// Activate the calls page
activatePageCalls();
return true;
}
// function which is called when a digit is pressed
bool DefaultLogic::digitPressed(NamedList& params, Window* wnd)
{
if (!Client::valid())
return false;
// Send digits (DTMF) on active channel
// or add them to 'callto' box
const String& digits = params["digits"];
if (!digits)
return false;
if (Client::self()->emitDigits(digits))
return true;
String target;
if (isE164(digits) && Client::self()->getText("callto",target)) {
target += digits;
if (Client::self()->setText("callto",target)) {
Client::self()->setFocus("callto",false);
return true;
}
}
return false;
}
// Called when the user wants to add an account or edit an existing one
bool DefaultLogic::editAccount(bool newAcc, NamedList* params, Window* wnd)
{
return internalEditAccount(newAcc,0,params,wnd);
}
// Called when the user wants to save account data
bool DefaultLogic::acceptAccount(NamedList* params, Window* wnd)
{
if (!(Client::valid() && wnd))
return false;
NamedList p("");
if (!getAccount(wnd,p,*m_accounts))
return false;
const String& replace = wnd ? wnd->context() : String::empty();
if (replace) {
ClientAccount* edit = m_accounts->findAccount(replace);
if (edit) {
ClientAccount* acc = m_accounts->findAccount(p);
if (acc && acc != edit) {
// Don't know what to do: replace the duplicate or rename the editing one
showAccDupError(wnd);
return false;
}
}
}
if (!updateAccount(p,true,replace))
return false;
// Hide the window. Save some settings
Client::self()->setVisible(wnd->toString(),false);
Client::s_settings.setValue("client","acc_protocol",p["protocol"]);
Client::save(Client::s_settings);
return true;
}
// Called when the user wants to delete an existing account
bool DefaultLogic::delAccount(const String& account, Window* wnd)
{
if (!account)
return deleteSelectedItem(s_accountList + ":",wnd);
ClientAccount* acc = m_accounts->findAccount(account);
if (!acc)
return false;
// Disconnect
Engine::enqueue(userLogin(acc,false));
// Delete from memory and UI. Save the accounts file
removeAccNotifications(acc);
Window* w = getAccPasswordWnd(account,false);
if (w)
Client::self()->closeWindow(w->toString());
clearAccountContacts(*acc);
Client::self()->delTableRow(s_account,account);
Client::self()->delTableRow(s_accountList,account);
acc->save(false);
m_accounts->removeAccount(account);
return true;
}
// Add/set an account
bool DefaultLogic::updateAccount(const NamedList& account, bool login, bool save)
{
DDebug(ClientDriver::self(),DebugAll,"Logic(%s) updateAccount(%s,%s,%s)",
toString().c_str(),account.c_str(),String::boolText(login),String::boolText(save));
// Load account status if not already done
AccountStatus::load();
if (!Client::valid() || account.null())
return false;
return updateAccount(account,false,String::empty(),true);
}
// Login/logout an account
bool DefaultLogic::loginAccount(const NamedList& account, bool login)
{
DDebug(ClientDriver::self(),DebugAll,"Logic(%s) loginAccount(%s,%s)",
toString().c_str(),account.c_str(),String::boolText(login));
Message* m = 0;
ClientAccount* acc = m_accounts->findAccount(account);
ClientResource::Status newStat = ClientResource::Unknown;
if (acc) {
m = userLogin(acc,login);
if (login) {
if (acc->resource().offline() || !isTelProto(acc->protocol()))
newStat = ClientResource::Connecting;
}
else {
newStat = ClientResource::Offline;
// Don't show a notification when disconnected
if (!login)
acc->m_params.setParam("internal.nologinfail",String::boolText(true));
}
}
else {
m = Client::buildMessage("user.login",account,login ? "login" : "logout");
if (login)
m->copyParams(account);
else
m->copyParams(account,"protocol");
}
Engine::enqueue(m);
if (newStat != ClientResource::Unknown) {
acc->resource().setStatus(newStat);
acc->resource().setStatusText("");
updateAccountStatus(acc,m_accounts);
}
return true;
}
// Add/update a contact
bool DefaultLogic::updateContact(const NamedList& params, bool save, bool update)
{
if (!(Client::valid() && (save || update) && params))
return false;
const String& target = params["target"];
if (!target)
return false;
// Fix contact id
String id;
String pref;
ClientContact::buildContactId(pref,m_accounts->localContacts()->toString(),String::empty());
if (params.startsWith(pref,false))
id = params;
else
ClientContact::buildContactId(id,m_accounts->localContacts()->toString(),params);
ClientContact* c = m_accounts->findContact(id);
if (!c)
c = new ClientContact(m_accounts->localContacts(),params,id,target);
else if (c) {
const String& name = params["name"];
if (name)
c->m_name = name;
c->setUri(target);
}
else
return false;
// Update UI
if (update)
updateContactList(*c);
// Save file
bool ok = true;
if (save && m_accounts->isLocalContact(c)) {
String name;
c->getContactSection(name);
unsigned int n = params.length();
for (unsigned int i = 0; i < n; i++) {
NamedString* ns = params.getParam(i);
if (!ns)
continue;
if (*ns)
Client::s_contacts.setValue(name,ns->name(),*ns);
else
Client::s_contacts.clearKey(name,ns->name());
}
ok = Client::save(Client::s_contacts);
}
// Notify server if this is a client account (stored on server)
// TODO: implement
return true;
}
// Called when the user wants to save contact data
bool DefaultLogic::acceptContact(NamedList* params, Window* wnd)
{
if (!Client::valid())
return false;
const char* err = 0;
String id;
String name;
String target;
// Check required data
while (true) {
#define SET_ERR_BREAK(e) { err = e; break; }
Client::self()->getText("abk_name",name,false,wnd);
if (!name)
SET_ERR_BREAK("A contact name must be specified");
Client::self()->getText("abk_target",target,false,wnd);
if (!target)
SET_ERR_BREAK("Contact number/target field can't be empty");
// Check if adding/editing contact. Generate a new contact id
if (wnd && wnd->context())
id = wnd->context();
else {
String tmp;
tmp << (unsigned int)Time::msecNow() << "_" << (int)Engine::runId();
ClientContact::buildContactId(id,m_accounts->localContacts()->toString(),tmp);
}
ClientContact* existing = m_accounts->localContacts()->findContact(id);
ClientContact* dup = 0;
if (existing) {
if (existing->m_name == name && existing->uri() == target) {
// No changes: return
if (wnd)
Client::self()->setVisible(wnd->toString(),false);
return true;
}
dup = m_accounts->localContacts()->findContact(&name,0,&id);
}
else
dup = m_accounts->localContacts()->findContact(&name);
if (dup)
SET_ERR_BREAK("A contact with the same name already exists!");
break;
#undef SET_ERR_BREAK
}
if (err) {
Client::openMessage(err,wnd);
return false;
}
NamedList p(id);
p.addParam("name",name);
p.addParam("target",target);
if (!updateContact(p,true,true))
return false;
if (wnd)
Client::self()->setVisible(wnd->toString(),false);
return true;
}
// Called when the user wants to add a new contact or edit an existing one
bool DefaultLogic::editContact(bool newCont, NamedList* params, Window* wnd)
{
if (!Client::valid())
return false;
// Make sure we reset all controls in window
NamedList p("");
if (newCont) {
p.addParam("abk_name",params ? params->c_str() : "");
p.addParam("abk_target",params ? params->getValue("target") : "");
}
else {
String cont;
Client::self()->getSelect(s_contactList,cont);
ClientContact* c = cont ? m_accounts->findContactByInstance(cont) : 0;
if (!(c && m_accounts->isLocalContact(c)))
return false;
p.addParam("context",c->toString());
p.addParam("abk_name",c->m_name);
p.addParam("abk_target",c->uri());
}
return Client::openPopup(s_wndAddrbook,&p);
}
// Called when the user wants to delete an existing contact
bool DefaultLogic::delContact(const String& contact, Window* wnd)
{
if (!Client::valid())
return false;
if (!contact)
return deleteSelectedItem(s_contactList + ":",wnd);
ClientContact* c = m_accounts->findContactByInstance(contact);
if (!(c && m_accounts->isLocalContact(c)))
return false;
// Delete the contact from config and all UI controls
contactDeleted(*c);
String sectName;
c->getContactSection(sectName);
Client::s_contacts.clearSection(sectName);
m_accounts->localContacts()->removeContact(contact);
Client::save(Client::s_contacts);
return true;
}
// Add/set account providers data
bool DefaultLogic::updateProviders(const NamedList& provider, bool save, bool update)
{
if (!(save || update))
return false;
if (provider.null() || !provider.getBoolValue("enabled",true))
return false;
if (save && !Client::save(Client::s_providers))
return false;
return updateProvidersItem(0,s_accProviders,provider);
}
// Called when the user wants to call an existing contact
bool DefaultLogic::callContact(NamedList* params, Window* wnd)
{
if (!Client::valid())
return false;
NamedList dummy("");
if (!params) {
params = &dummy;
Client::self()->getSelect(s_contactList,*params);
}
if (!Client::self()->getTableRow(s_contactList,*params,params))
return false;
const String& target = (*params)["number/uri"];
if (!target)
return false;
bool call = true;
String account;
String proto;
String cmd;
ClientContact* c = m_accounts->findContactByInstance(*params);
if (!m_accounts->isLocalContact(c)) {
// Not a local contact: check if it belongs to registered account
if (c && c->account() && c->account()->resource().online()) {
account = c->account()->toString();
proto = c->account()->protocol();
}
call = !account.null();
}
else {
static const Regexp r("^[a-z0-9]\\+/");
if (!r.matches(target)) {
Client::self()->getSelect(s_account,account);
call = !account.null();
if (call)
cmd = s_actionCall;
}
}
if (call) {
NamedList p("");
p.addParam("line",account,false);
p.addParam("account",account,false);
p.addParam("target",target);
p.addParam("protocol",proto,false);
return callStart(p,0,cmd);
}
Client::self()->setText(s_calltoList,target);
activatePageCalls();
return true;
}
// Update the call log history
bool DefaultLogic::callLogUpdate(const NamedList& params, bool save, bool update)
{
if (!(save || update))
return false;
String* bid = params.getParam("billid");
const String& id = bid ? (const String&)(*bid) : params["id"];
if (!id)
return false;
if (Client::valid() && update) {
// Remember: directions are opposite of what the user expects
const String& dir = params["direction"];
bool outgoing = (dir == "incoming");
if (outgoing || dir == "outgoing") {
// Skip if there is no remote party
const String& party = cdrRemoteParty(params,outgoing);
if (party) {
NamedList p("");
String time;
Client::self()->formatDateTime(time,(unsigned int)params.getDoubleValue("time"),
"yyyy.MM.dd hh:mm",false);
p.addParam("party",party);
p.addParam("party_image",Client::s_skinPath + (outgoing ? "up.png" : "down.png"));
p.addParam("time",time);
time.clear();
Client::self()->formatDateTime(time,(unsigned int)params.getDoubleValue("duration"),
"hh:mm:ss",true);
p.addParam("duration",time);
Client::self()->updateTableRow(s_logList,id,&p);
}
}
}
if (!save)
return true;
// Update the call history file
// We don't hold information for more than s_maxCallHistory, so if we reached the
// limit, we delete the oldest entry to make room
while (Client::s_history.sections() >= s_maxCallHistory) {
NamedList* sect = Client::s_history.getSection(0);
if (!sect)
break;
Client::s_history.clearSection(*sect);
}
// Write to the file the information about the calls
NamedList* sect = Client::s_history.createSection(id);
if (!sect)
return false;
*sect = params;
sect->assign(id);
return Client::save(Client::s_history);
}
// Remove a call log item
bool DefaultLogic::callLogDelete(const String& billid)
{
if (!billid)
return false;
bool ok = true;
if (Client::valid())
ok = Client::self()->delTableRow(s_logList,billid);
NamedList* sect = Client::s_history.getSection(billid);
if (!sect)
return ok;
Client::s_history.clearSection(*sect);
return Client::save(Client::s_history) && ok;
}
// Clear the specified log and the entries from the history file and save the history file
bool DefaultLogic::callLogClear(const String& table, const String& direction)
{
// Clear history
bool save = false;
unsigned int n = Client::s_history.sections();
if (direction)
for (unsigned int i = 0; i < n; i++) {
NamedList* sect = Client::s_history.getSection(i);
NamedString* dir = sect ? sect->getParam("direction") : 0;
if (!dir || *dir != direction)
continue;
Client::s_history.clearSection(*sect);
save = true;
i--;
}
else {
save = (0 != n);
Client::s_history.clearSection();
}
// Clear table and save the file
if (Client::self())
Client::self()->clearTable(table);
if (save)
Client::save(Client::s_history);
return true;
}
// Make an outgoing call to a target picked from the call log
bool DefaultLogic::callLogCall(const String& billid, Window* wnd)
{
NamedList* sect = Client::s_history.getSection(billid);
if (!sect)
return false;
const String& party = cdrRemoteParty(*sect);
return party && action(wnd,"callto:" + party);
}
// Create a contact from a call log entry
bool DefaultLogic::callLogCreateContact(const String& billid)
{
NamedList* sect = Client::s_history.getSection(billid);
if (!sect)
return false;
const String& party = cdrRemoteParty(*sect);
NamedList p(party);
p.setParam("target",party);
return editContact(true,&p);
}
// Process help related actions
bool DefaultLogic::help(const String& name, Window* wnd)
{
if (!Client::self())
return false;
Window* help = Client::self()->getWindow("help");
if (!help)
return false;
// Set the the searched page
bool show = false;
int page = help->context().toInteger();
if (name == "help:home")
page = 0;
else if (name == "help:prev")
page--;
else if (name == "help:next")
page++;
else if (name.startsWith("help:")) {
page = name.substr(5).toInteger(page);
show = true;
}
if (page < 0)
page = 0;
// Get the help file from the help folder
String helpFile = Engine::config().getValue("client","helpbase");
if (!helpFile)
helpFile << Engine::sharedPath() << Engine::pathSeparator() << "help";
if (!helpFile.endsWith(Engine::pathSeparator()))
helpFile << Engine::pathSeparator();
helpFile << page << ".yhlp";
File f;
if (!f.openPath(helpFile)) {
Debug(ClientDriver::self(),DebugNote,"Failed to open help file '%s'",helpFile.c_str());
return false;
}
// if the opening of the help file succeeds, we set it as the text of the help window
int rd = 0;
unsigned int len = (unsigned int)f.length();
if (len != (unsigned int)-1) {
String helpText(' ',len);
rd = f.readData(const_cast<char*>(helpText.c_str()),len);
if (rd == (int)len) {
Client::self()->setText("help_text",helpText,true,help);
help->context(String(page));
if (show)
Client::self()->setVisible("help",true);
return true;
}
}
Debug(ClientDriver::self(),DebugNote,"Read only %d out of %u bytes in file '%s'",
rd,len,helpFile.c_str());
return false;
}
// Called by the client after loaded the callto history file
bool DefaultLogic::calltoLoaded()
{
if (!Client::self())
return false;
NamedList* sect = Client::s_calltoHistory.getSection("calls");
if (!sect)
return false;
unsigned int n = sect->length();
unsigned int max = 0;
for (unsigned int i = 0; max < s_maxCallHistory && i < n; i++) {
NamedString* s = sect->getParam(i);
if (!s || Client::self()->hasOption(s_calltoList,s->name()))
continue;
if (Client::self()->addOption(s_calltoList,s->name(),false))
max++;
}
Client::self()->setText(s_calltoList,"");
return false;
}
// Process ui.action message
bool DefaultLogic::handleUiAction(Message& msg, bool& stopLogic)
{
if (!Client::self())
return false;
// get action
NamedString* action = msg.getParam("action");
if (!action)
return false;
// block until client finishes initialization
while (!Client::self()->initialized())
Thread::idle();
// call the appropiate function for the given action
Window* wnd = Client::getWindow(msg.getValue("window"));
if (*action == "set_status")
return Client::self()->setStatusLocked(msg.getValue("status"),wnd);
else if (*action == "add_log")
return Client::self()->addToLog(msg.getValue("text"));
else if (*action == "show_message") {
Client::self()->lockOther();
bool ok = Client::openMessage(msg.getValue("text"),Client::getWindow(msg.getValue("parent")),msg.getValue("context"));
Client::self()->unlockOther();
return ok;
}
else if (*action == "show_confirm") {
Client::self()->lockOther();
bool ok = Client::openConfirm(msg.getValue("text"),Client::getWindow(msg.getValue("parent")),msg.getValue("context"));
Client::self()->unlockOther();
return ok;
}
// get the name of the widget for which the action is meant
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()->lockOther();
if (*action == "set_text")
ok = Client::self()->setText(name,msg.getValue("text"),false,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_focus")
ok = Client::self()->setFocus(name,msg.getBoolValue("select"),wnd);
else if (*action == "set_visible")
ok = Client::self()->setShow(name,msg.getBoolValue("visible"),wnd);
else if (*action == "has_option")
ok = Client::self()->hasOption(name,msg.getValue("item"),wnd);
else if (*action == "add_option")
ok = Client::self()->addOption(name,msg.getValue("item"),msg.getBoolValue("insert"),msg.getValue("text"),wnd);
else if (*action == "del_option")
ok = Client::self()->delTableRow(name,msg.getValue("item"),wnd);
else if (*action == "get_text") {
String text;
ok = Client::self()->getText(name,text,false,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);
else if (*action == "window_popup")
ok = Client::openPopup(name,&msg,Client::getWindow(msg.getValue("parent")));
Client::self()->unlockOther();
return ok;
}
// Process call.cdr message
bool DefaultLogic::handleCallCdr(Message& msg, bool& stopLogic)
{
if (!Client::self())
return false;
if (msg["operation"] != "finalize")
return false;
if (!msg["chan"].startsWith("client/",false))
return false;
if (Client::self()->postpone(msg,Client::CallCdr,false))
stopLogic = true;
else
callLogUpdate(msg,true,true);
return false;
}
// Process user.login message
bool DefaultLogic::handleUserLogin(Message& msg, bool& stopLogic)
{
return false;
}
// Process user.notify message
bool DefaultLogic::handleUserNotify(Message& msg, bool& stopLogic)
{
if (!Client::valid())
return false;
if (Client::self()->postpone(msg,Client::UserNotify,false)) {
stopLogic = true;
return false;
}
const String& account = msg["account"];
if (!account)
return false;
bool reg = msg.getBoolValue("registered");
const char* reason = msg.getValue("reason");
// Notify wizards
s_mucWizard->handleUserNotify(account,reg,reason);
bool save = s_accWizard->handleUserNotify(account,reg,reason);
bool fromWiz = save;
ClientAccount* acc = m_accounts->findAccount(account);
if (!acc)
return false;
// Always remove roster request notification when account status changed
removeNotifArea("rosterreqfail",account);
// Notify status
String txt = reg ? "Registered" : "Unregistered";
txt << " account " << account;
txt.append(reason," reason: ");
Client::self()->setStatusLocked(txt);
int stat = ClientResource::Online;
String regStat;
if (reg) {
// Remove account failure notification if still there
removeNotifArea("loginfail",account);
// Clear account register option
NamedString* opt = acc->m_params.getParam("options");
if (opt) {
ObjList* list = opt->split(',',false);
ObjList* o = list->find("register");
if (o) {
save = true;
o->remove();
opt->clear();
opt->append(list,",");
if (opt->null())
acc->m_params.clearParam(opt);
}
TelEngine::destruct(list);
}
acc->resource().m_id = msg.getValue("instance");
// Set account status from pending data
int tmp = acc->params().getIntValue("internal.status.status",ClientResource::s_statusName);
if (tmp > stat)
stat = tmp;
regStat = acc->params().getValue("internal.status.text");
// Handle postponed contacts
ObjList remove;
unsigned int n = s_postponedContacts.sections();
for (unsigned int i = 0; i < n; i++) {
NamedList* sect = s_postponedContacts.getSection(i);
if (!(sect && account == (*sect)["account"]))
continue;
remove.append(sect)->setDelete(false);
// Send contact update
const String& contact = (*sect)["contact"];
if (!contact)
continue;
Message* m = Client::buildUserRoster(true,account,contact);
bool reqSub = false;
unsigned int nc = sect->length();
for (unsigned int j = 0; j < nc; j++) {
NamedString* ns = sect->getParam(j);
if (!ns || ns->name() == "account" || ns->name() == "contact")
continue;
if (ns->name() != "request_subscribe")
m->addParam(ns->name(),*ns);
else
reqSub = ns->toBoolean();
}
Engine::enqueue(m);
if (reqSub)
Engine::enqueue(Client::buildSubscribe(true,true,account,contact));
}
removePostponedContacts(remove);
}
else {
bool noFail = acc->params().getBoolValue("internal.nologinfail");
bool reConn = acc->params().getBoolValue("internal.reconnect");
// Show login failure message if not requested by the user
if (!(noFail || reConn)) {
NamedList rows("");
NamedList* upd = buildNotifArea(rows,"loginfail",account,String::empty(),"Login failure");
String text;
text << "Failed to connect account '" << account << "'";
text.append(reason,"\r\nReason: ");
upd->addParam("text",text);
// Enable/disable account edit
const char* ok = String::boolText(!Client::self()->getVisible(s_wndAccount));
upd->addParam("active:messages_acc_edit",ok);
showNotificationArea(true,Client::self()->getWindow(s_wndMain),&rows);
}
if (msg.getBoolValue("autorestart"))
stat = ClientResource::Connecting;
else {
if (!reConn) {
stat = ClientResource::Offline;
if (s_chatLog == ChatLogSaveUntilLogout)
logClearAccount(account);
}
else {
stat = ClientResource::Connecting;
acc->m_params.clearParam("internal.reconnect");
// Re-connect the account
Message* m = userLogin(acc,true);
addAccPendingStatus(*m,acc);
Engine::enqueue(m);
// Clear the reason to avoid displaying it (we requested the disconnect)
reason = 0;
}
// Reset resource name to configured
acc->resource().m_id = acc->m_params.getValue("resource");
}
clearAccountContacts(*acc);
setOfflineMucs(acc);
}
// Clear some internal params
acc->m_params.clearParam("internal.nologinfail");
if (stat != ClientResource::Connecting)
acc->m_params.clearParam("internal.status",'.');
bool changed = acc->resource().setStatus(stat);
changed = acc->resource().setStatusText(reg ? regStat.c_str() : reason) || changed;
if (changed)
updateAccountStatus(acc,m_accounts);
else if (!reg)
PendingRequest::clear(acc->toString());
if (save)
acc->save(true,acc->params().getBoolValue("savepassword"));
// Update telephony account selector(s)
updateTelAccList(acc->startup() && reg,acc);
setAdvancedMode();
// Added from wizard
// Update account status to server: notify presence and request roster or
// disconnect it of global presence is 'offline'
if (fromWiz) {
if (AccountStatus::current() &&
AccountStatus::current()->status() != ClientResource::Offline) {
if (!isTelProto(acc->protocol())) {
Message* m = Client::buildNotify(true,acc->toString(),
acc->resource(false));
Engine::enqueue(m);
queryRoster(acc);
}
}
else
setAccountStatus(m_accounts,acc);
}
return false;
}
// Process user.roster message
bool DefaultLogic::handleUserRoster(Message& msg, bool& stopLogic)
{
if (!Client::valid() || Client::isClientMsg(msg))
return false;
const String& oper = msg["operation"];
if (!oper)
return false;
bool fail = false;
bool remove = (oper != "update");
if (remove && oper != "delete") {
if (oper != "queryerror")
return false;
fail = true;
}
// Postpone message processing
if (Client::self()->postpone(msg,Client::UserRoster)) {
stopLogic = true;
return false;
}
int n = msg.getIntValue("contact.count");
if (n < 1 && !fail)
return false;
const String& account = msg["account"];
ClientAccount* a = account ? m_accounts->findAccount(account) : 0;
if (!a)
return false;
if (fail) {
String reason = msg["error"];
if (reason) {
const String& res = msg["reason"];
if (res)
reason << " (" << res << ")";
}
else
reason = msg["reason"];
NamedList list("");
NamedList* upd = buildNotifArea(list,"rosterreqfail",account,
String::empty(),"Friends list failure");
setGenericNotif(*upd,"Retry");
String text;
text << "Failed to retrieve the friends list";
text.append(reason,": ");
text.append(account,"\r\nAccount: ");
upd->addParam("text",text);
showNotificationArea(true,Client::self()->getWindow(s_wndMain),&list);
return false;
}
if (msg.getBoolValue("queryrsp"))
removeNotifArea("rosterreqfail",account);
ObjList removed;
NamedList chatlist("");
for (int i = 1; i <= n; i++) {
String pref("contact." + String(i));
const String& uri = msg[pref];
if (!uri)
continue;
String id;
ClientContact::buildContactId(id,account,uri);
ClientContact* c = a->findContact(id);
// Avoid account's own contact
if (c && c == a->contact())
continue;
if (remove) {
if (!c)
continue;
removed.append(a->removeContact(id,false));
continue;
}
pref << ".";
// Add/update contact
const char* cName = msg.getValue(pref + "name",uri);
bool newContact = (c == 0);
bool changed = newContact;
if (c) {
changed = (c->m_name != cName);
if (changed)
c->m_name = cName;
}
else {
c = a->appendContact(id,cName,uri);
if (!c)
continue;
}
const String& sub = msg[pref + "subscription"];
if (c->m_subscription != sub) {
c->m_subscription = sub;
changed = true;
}
// Get groups
changed = c->setGroups(msg,pref + "group") || changed;
// Update info window if displayed
if (changed)
updateContactInfo(c);
if (!(changed && a->hasChat()))
continue;
NamedList* p = new NamedList(c->toString());
fillChatContact(*p,*c,true,newContact);
chatlist.addParam(new NamedPointer(c->toString(),p,String::boolText(true)));
if (c->hasChat())
c->updateChatWindow(*p,"Chat [" + c->m_name + "]");
}
// Update UI
for (ObjList* o = removed.skipNull(); o; o = o->skipNext())
contactDeleted(*static_cast<ClientContact*>(o->get()));
Client::self()->updateTableRows(s_chatContactList,&chatlist,false);
return true;
}
// Process resource.notify message
bool DefaultLogic::handleResourceNotify(Message& msg, bool& stopLogic)
{
if (!Client::valid() || Client::isClientMsg(msg))
return false;
const String& contact = msg["contact"];
if (!contact)
return false;
const String& oper = msg["operation"];
if (!oper)
return false;
// Postpone message processing
if (Client::self()->postpone(msg,Client::ResourceNotify)) {
stopLogic = true;
return false;
}
const String& account = msg["account"];
ClientAccount* a = account ? m_accounts->findAccount(account) : 0;
if (!a)
return false;
const String& inst = msg["instance"];
if (msg.getBoolValue("muc"))
return handleMucResNotify(msg,a,contact,inst,oper);
ClientContact* c = a->findContactByUri(contact);
if (!c)
return false;
Debug(ClientDriver::self(),DebugAll,
"Logic(%s) account=%s contact=%s instance=%s operation=%s",
name().c_str(),account.c_str(),contact.c_str(),inst.safe(),oper.c_str());
bool ownContact = c == a->contact();
String instid;
bool online = false;
bool statusChanged = false;
bool oldOnline = c->online();
// Use a while() to break to the end
while (true) {
// Avoid account own instance
if (ownContact && inst && inst == a->resource().toString())
return false;
online = (oper == "online");
if (online || oper == "offline") {
if (online) {
c->setOnline(true);
if (!inst) {
statusChanged = !oldOnline;
break;
}
statusChanged = true;
ClientResource* res = c->findResource(inst);
if (!res)
res = new ClientResource(inst);
// Update resource
res->setFileTransfer(msg.getBoolValue("caps.filetransfer"));
res->setAudio(msg.getBoolValue("caps.audio"));
res->setPriority(msg.getIntValue("priority"));
res->setStatusText(msg.getValue("status"));
int stat = msg.getIntValue("show",ClientResource::s_statusName);
if (stat < ClientResource::Online)
stat = ClientResource::Online;
res->setStatus(stat);
// (Re)insert the resource
c->insertResource(res);
// Update/set resource in contacts list (only for resources with audio caps)
if (res->m_audio)
instid = inst;
}
else {
if (inst) {
statusChanged = c->removeResource(inst);
if (!c->resources().skipNull()) {
statusChanged = statusChanged || oldOnline;
c->setOnline(false);
}
}
else if (c->online()) {
statusChanged = true;
c->resources().clear();
c->setOnline(false);
}
// Remove resource(s) from contacts list
c->buildInstanceId(instid,inst);
}
break;
}
// TODO: handle other operations like received errors
break;
}
if (instid) {
if (online)
updateContactList(*c,instid,msg.getValue("uri"));
else
removeContacts(instid);
}
if (statusChanged) {
NamedList p("");
fillChatContact(p,*c,false,true);
Client::self()->setTableRow(s_chatContactList,c->toString(),&p);
if (c->hasChat()) {
bool newOnline = c->online();
ClientResource* res = c->status();
int stat = newOnline ? ClientResource::Online : ClientResource::Offline;
c->updateChatWindow(p,0,resStatusImage(res ? res->m_status : stat));
if (oldOnline != newOnline)
addChatNotify(*c,newOnline,false,msg.msgTime().sec());
}
// Update info window if displayed
updateContactInfo(c);
}
return false;
}
// Process resource.subscribe message
bool DefaultLogic::handleResourceSubscribe(Message& msg, bool& stopLogic)
{
if (!Client::valid() || Client::isClientMsg(msg))
return false;
const String& account = msg["account"];
const String& contact = msg["subscriber"];
const String& oper = msg["operation"];
if (!(account && contact && oper))
return false;
// Postpone message processing
if (Client::self()->postpone(msg,Client::ResourceSubscribe)) {
stopLogic = true;
return false;
}
ClientAccount* a = m_accounts->findAccount(account);
if (!a)
return false;
bool sub = (oper == "subscribe");
if (!sub && oper != "unsubscribe")
return false;
ClientContact* c = a->findContactByUri(contact);
if (c && c == a->contact())
return false;
Debug(ClientDriver::self(),DebugAll,
"Logic(%s) account=%s contact=%s operation=%s",
name().c_str(),account.c_str(),contact.c_str(),oper.c_str());
if (sub && a->resource().online()) {
NamedList rows("");
NamedList* upd = buildNotifArea(rows,"subscription",account,contact,"Subscription request");
String cname;
if (c && c->m_name && (c->m_name != contact))
cname << "'" << c->m_name << "' ";
upd->addParam("name",cname);
String s = "Contact ${name}<${contact}> requested subscription on account '${account}'.";
upd->replaceParams(s);
upd->addParam("text",s);
showNotificationArea(true,Client::self()->getWindow(s_wndMain),&rows);
}
return true;
}
// Process chan.startup message
bool DefaultLogic::handleClientChanUpdate(Message& msg, bool& stopLogic)
{
#define CHANUPD_ID (chan ? chan->id() : *id)
#define CHANUPD_ADDR (chan ? chan->address() : String::empty())
if (!Client::self())
return false;
if (Client::self()->postpone(msg,Client::ClientChanUpdate,true)) {
stopLogic = true;
return false;
}
int notif = ClientChannel::lookup(msg.getValue("notify"));
if (notif == ClientChannel::Destroyed) {
if (!Client::valid())
return false;
String id = msg.getValue("id");
// Reset init transfer if destroyed
if (m_transferInitiated && m_transferInitiated == id)
m_transferInitiated = "";
// Stop incoming ringer if there are no more incoming channels
bool haveIncoming = false;
if (ClientDriver::self()) {
Lock lock(ClientDriver::self());
ObjList* o = ClientDriver::self()->channels().skipNull();
for (; o; o = o->skipNext())
if ((static_cast<Channel*>(o->get()))->isOutgoing()) {
haveIncoming = true;
break;
}
}
if (!haveIncoming) {
removeTrayIcon("incomingcall");
Client::self()->ringer(true,false);
Client::self()->ringer(false,false);
}
Client::self()->delTableRow(s_channelList,id);
enableCallActions(m_selectedChannel);
String status;
buildStatus(status,"Hung up",msg.getValue("address"),id,msg.getValue("reason"));
Client::self()->setStatusLocked(status);
return false;
}
// Set some data from channel
ClientChannel* chan = static_cast<ClientChannel*>(msg.userData());
// We MUST have an ID
NamedString* id = 0;
if (!chan)
id = msg.getParam("id");
if (!(chan || id))
return false;
bool outgoing = chan ? chan->isOutgoing() : msg.getBoolValue("outgoing");
bool noticed = chan ? chan->isNoticed() : msg.getBoolValue("noticed");
bool active = chan ? chan->active() : msg.getBoolValue("active");
bool silence = msg.getBoolValue("silence");
bool notConf = !(chan ? chan->conference() : msg.getBoolValue("conference"));
// Stop ringing on not silenced active outgoing channels
if (active && !outgoing && !silence)
Client::self()->ringer(false,false);
// Update UI
NamedList p("");
bool updateFormats = true;
bool enableActions = false;
bool setStatus = notConf;
String status;
switch (notif) {
case ClientChannel::Active:
enableActions = true;
updateFormats = false;
buildStatus(status,"Call active",CHANUPD_ADDR,CHANUPD_ID);
Client::self()->setSelect(s_channelList,CHANUPD_ID);
setImageParam(p,"party",outgoing ? "down_active.png" : "up_active.png");
if (outgoing) {
if (noticed)
Client::self()->ringer(true,false);
}
else {
Client::self()->ringer(true,false);
if (silence)
Client::self()->ringer(false,true);
}
break;
case ClientChannel::OnHold:
enableActions = true;
buildStatus(status,"Call inactive",CHANUPD_ADDR,CHANUPD_ID);
setImageParam(p,"party",outgoing ? "down.png" : "up.png");
if (outgoing) {
if (noticed)
Client::self()->ringer(true,false);
}
else {
Client::self()->ringer(true,false);
Client::self()->ringer(false,false);
}
break;
case ClientChannel::Ringing:
buildStatus(status,"Call ringing",CHANUPD_ADDR,CHANUPD_ID);
if (notConf)
setImageParam(p,"time","chan_ringing.png");
break;
case ClientChannel::Noticed:
// Stop incoming ringer
Client::self()->ringer(true,false);
buildStatus(status,"Call noticed",CHANUPD_ADDR,CHANUPD_ID);
break;
case ClientChannel::Progressing:
buildStatus(status,"Call progressing",CHANUPD_ADDR,CHANUPD_ID);
if (notConf)
setImageParam(p,"time","chan_progress.png");
break;
case ClientChannel::Startup:
enableActions = true;
// Create UI entry
if (chan && Client::self()->addTableRow(s_channelList,CHANUPD_ID,&p)) {
DurationUpdate* d = new DurationUpdate(this,false,CHANUPD_ID,"time");
chan->setClientData(d);
TelEngine::destruct(d);
}
else
return false;
if (outgoing) {
addTrayIcon("incomingcall");
Client::self()->setUrgent(s_wndMain,true,Client::self()->getWindow(s_wndMain));
}
setImageParam(p,"party",chan ? chan->party() : "",outgoing ? "down.png" : "up.png");
setImageParam(p,"time","",outgoing ? "chan_ringing.png" : "chan_idle.png");
// Start incoming ringer if there is no active channel
if (outgoing && notConf) {
ClientChannel* ch = ClientDriver::findActiveChan();
if (!ch)
Client::self()->ringer(true,true);
else
TelEngine::destruct(ch);
}
setStatus = false;
p.setParam("status",outgoing ? "incoming" : "outgoing");
break;
case ClientChannel::Accepted:
buildStatus(status,"Calling target",0,0);
break;
case ClientChannel::Answered:
if (outgoing)
removeTrayIcon("incomingcall");
enableActions = true;
buildStatus(status,"Call answered",CHANUPD_ADDR,CHANUPD_ID);
setImageParam(p,"time","answer.png");
// Stop incoming ringer
Client::self()->ringer(true,false);
if (active)
Client::self()->ringer(false,false);
break;
case ClientChannel::Routed:
updateFormats = false;
buildStatus(status,"Calling",chan ? chan->party() : "",0,0);
if (notConf)
setImageParam(p,"time","chan_routed.png");
break;
case ClientChannel::Rejected:
updateFormats = false;
buildStatus(status,"Call failed",CHANUPD_ADDR,CHANUPD_ID,msg.getValue("reason"));
break;
case ClientChannel::Transfer:
updateFormats = false;
enableActions = true;
// Transferred
if (chan && chan->transferId() && notConf) {
setStatus = false;
ClientChannel* trans = ClientDriver::findChan(chan->transferId());
setImageParam(p,"status",trans ? trans->party() : "","transfer.png");
TelEngine::destruct(trans);
buildStatus(status,"Call transferred",CHANUPD_ADDR,CHANUPD_ID);
}
else if (notConf)
setImageParam(p,"status","","");
break;
case ClientChannel::Conference:
updateFormats = false;
enableActions = true;
if (notConf)
setImageParam(p,"status","","");
else {
const char* s = (chan && chan->transferId()) ? chan->transferId().safe() : "";
setImageParam(p,"status",s,"conference.png");
}
break;
default:
enableActions = true;
updateFormats = false;
buildStatus(status,String("Call notification=") + msg.getValue("notify"),
CHANUPD_ADDR,CHANUPD_ID);
}
if (enableActions && m_selectedChannel == CHANUPD_ID)
enableCallActions(m_selectedChannel);
if (status)
Client::self()->setStatusLocked(status);
if (updateFormats && chan) {
String fmt;
fmt << (chan->peerOutFormat() ? chan->peerOutFormat().c_str() : "-");
fmt << "/";
fmt << (chan->peerInFormat() ? chan->peerInFormat().c_str() : "-");
p.addParam("format",fmt);
}
if (setStatus && chan)
p.setParam("status",chan->status());
Client::self()->setTableRow(s_channelList,CHANUPD_ID,&p);
return false;
#undef CHANUPD_ID
#undef CHANUPD_ADDR
}
// Process contact.info message
bool DefaultLogic::handleContactInfo(Message& msg, bool& stopLogic)
{
if (!Client::valid() || Client::isClientMsg(msg))
return false;
const String& account = msg["account"];
if (!account)
return false;
const String& oper = msg["operation"];
if (!oper)
return false;
// Postpone message processing
if (Client::self()->postpone(msg,Client::ContactInfo)) {
stopLogic = true;
return false;
}
const String& contact = msg["contact"];
DDebug(ClientDriver::self(),DebugAll,
"Logic(%s)::handleContactInfo() account=%s contact=%s operation=%s",
name().c_str(),account.c_str(),contact.c_str(),oper.c_str());
// Notify the MUC wizard
s_mucWizard->handleContactInfo(msg,account,oper,contact);
return false;
}
// Default message processor called for id's not defined in client.
bool DefaultLogic::defaultMsgHandler(Message& msg, int id, bool& stopLogic)
{
if (id == Client::ChanNotify) {
String event = msg.getValue("event");
if (event != "left")
return false;
// Check if we have a channel in conference whose peer is the one who left
const char* peer = msg.getValue("lastpeerid");
ClientChannel* chan = ClientDriver::findChanByPeer(peer);
if (!chan)
return false;
if (chan->conference()) {
DDebug(ClientDriver::self(),DebugInfo,
"Channel %s left the conference. Terminating %s",
peer,chan->id().c_str());
// Try to use Client's way first
if (Client::self())
Client::self()->callTerminate(chan->id());
else
chan->disconnect("Peer left the conference");
}
TelEngine::destruct(chan);
return false;
}
if (id == Client::MsgExecute) {
if (!Client::valid() || Client::isClientMsg(msg))
return false;
if (Client::self()->postpone(msg,Client::MsgExecute))
return true;
const String& account = msg["account"];
if (!account)
return false;
ClientAccount* acc = m_accounts->findAccount(account);
if (!acc)
return false;
const String& type = msg["type"];
String tmp;
ClientContact::buildContactId(tmp,account,msg.getValue("caller"));
ClientContact* c = acc->findContact(tmp);
bool chat = (!type || type == "chat");
if (c) {
if (chat) {
String* delay = msg.getParam("delay_time");
unsigned int time = !delay ? msg.msgTime().sec() : (unsigned int)delay->toInteger(0);
const char* ds = !delay ? "" : msg.getValue("delay_by");
String chatState;
bool hasState = !delay && buildChatState(chatState,msg,c->m_name);
const String& body = msg["body"];
NamedList* p = 0;
if (body || !hasState)
p = buildChatParams(body,c->m_name,time,0 != delay,ds);
// Active state with no body or notification: remove last notification
// if the contact has a chat
bool resetNotif = !p && !chatState && c->hasChat() && msg["chatstate"] == "active";
if (p || chatState || resetNotif) {
if (!c->hasChat()) {
c->createChatWindow();
NamedList p("");
fillChatContact(p,*c,true,true);
ClientResource* res = c->status();
c->updateChatWindow(p,"Chat [" + c->m_name + "]",
resStatusImage(res ? res->m_status : ClientResource::Offline));
}
c->showChat(true);
if (chatState)
addChatNotify(*c,chatState,msg.msgTime().sec(),"tempnotify");
if (p) {
logChat(c,time,false,delay != 0,body);
c->addChatHistory(!delay ? "chat_in" : "chat_delayed",p);
notifyIncomingChat(c);
}
if (resetNotif)
c->setChatProperty("history","_yate_tempitemcount",String((int)0));
}
}
else
DDebug(ClientDriver::self(),DebugStub,
"DefaultLogic unhandled message type=%s",type.c_str());
return true;
}
MucRoom* room = acc->findRoom(tmp);
if (!room)
return false;
bool mucChat = !chat && type == "groupchat";
if (!(mucChat || chat)) {
Debug(ClientDriver::self(),DebugStub,
"DefaultLogic unhandled MUC message type=%s",type.c_str());
return true;
}
const String& body = msg["body"];
String* delay = mucChat ? msg.getParam("delay_time") : 0;
const String& nick = msg["caller_instance"];
MucRoomMember* member = room->findMember(nick);
// Accept delayed (history) group chat from unknown nick
if (!member && !(mucChat && delay))
return false;
unsigned int time = !delay ? msg.msgTime().sec() : (unsigned int)delay->toInteger(0);
// Check subject changes (empty subject is allowed)
String* subject = mucChat ? msg.getParam("subject") : 0;
if (subject) {
NamedList tmp("");
tmp.addParam("room_subject",*subject);
room->updateChatWindow(room->resource().toString(),tmp);
// Show any notification from room
if (body)
addChatNotify(*room,body,msg.msgTime().sec());
String text(nick);
text << " changed room subject to '" << *subject << "'";
if (delay) {
NamedList* p = buildChatParams(text,"",time,0,0);
room->addChatHistory(room->resource().toString(),"chat_delayed",p);
notifyIncomingChat(room,room->resource().toString());
}
else
addChatNotify(*room,text,msg.msgTime().sec());
return true;
}
// Ignore non delayed chat returned by the room
if (!delay && (!member || room->ownMember(member)))
return true;
String chatState;
bool hasState = !delay && chat && buildChatState(chatState,msg,member->m_name);
NamedList* p = 0;
if (body || !hasState)
p = buildChatParams(body,member ? member->m_name : nick,time,0,0);
const String& id = mucChat ? room->resource().toString() : member->toString();
// Active state with no body or notification: remove last notification
bool resetNotif = !p && !chatState && room->hasChat(id) && msg["chatstate"] == "active";
if (p || chatState || resetNotif) {
if (chat)
createRoomChat(*room,member,false);
if (chatState)
addChatNotify(*room,chatState,msg.msgTime().sec(),"tempnotify",id);
if (p) {
room->addChatHistory(id,!delay ? "chat_in" : "chat_delayed",p);
notifyIncomingChat(room,id);
if (body)
logChat(room,time,false,delay != 0,body,mucChat,nick);
}
if (resetNotif)
room->setChatProperty(id,"history","_yate_tempitemcount",String((int)0));
}
return true;
}
if (id == Client::MucRoom) {
static const char* extra = "room,password,reason,contact_instance";
if (!Client::valid() || Client::isClientMsg(msg))
return false;
if (Client::self()->postpone(msg,Client::MucRoom))
return true;
const String& account = msg["account"];
ClientAccount* acc = account ? m_accounts->findAccount(account) : 0;
if (!acc)
return false;
const String& oper = msg["operation"];
const String& room = msg["room"];
String tmp;
if (room)
ClientContact::buildContactId(tmp,account,room);
MucRoom* r = tmp ? acc->findRoom(tmp) : 0;
// Invite
if (oper == "invite") {
// Account already there
if (r && r->resource().online())
return false;
const String& contact = msg["contact"];
if (!contact) {
Message* m = buildMucRoom("decline",account,room,"Unnaceptable anonymous invitation!");
return Engine::enqueue(m);
}
NamedList rows("");
NamedList* upd = buildNotifArea(rows,"mucinvite",account,contact,"Join chat room",extra);
upd->copyParams(msg,extra);
String cname;
ClientContact* c = acc->findContactByUri(contact);
if (c && c->m_name && (c->m_name != contact))
cname << "'" << c->m_name << "' ";
upd->addParam("name",cname);
String s = "Contact ${name}<${contact}> invites you to join chat room '${room}' on account '${account}'.\r\n${reason}";
upd->replaceParams(s);
upd->addParam("text",s);
showNotificationArea(true,Client::self()->getWindow(s_wndMain),&rows);
return true;
}
return false;
}
if (id == Client::TransferNotify)
return handleFileTransferNotify(msg,stopLogic);
return false;
}
// Client created and initialized all windows
void DefaultLogic::initializedWindows()
{
if (!Client::valid())
return;
// Add 'not selected' item
Client::self()->updateTableRow("protocol",s_notSelected,0,true);
Client::self()->updateTableRow(s_accProviders,s_notSelected,0,true);
Client::self()->updateTableRow("account",s_notSelected,0,true);
// Fill protocol lists
bool tel = true;
updateProtocolList(0,"protocol",&tel);
updateProtocolList(0,s_accProtocol);
// Make sure the active page is the calls one
activatePageCalls(0,false);
}
// Utility: set check parameter from another list
static inline void setCheck(NamedList& p, const NamedList& src, const String& param,
bool defVal = true)
{
bool ok = src.getBoolValue(param,defVal);
p.addParam("check:" + param,String::boolText(ok));
}
// Initialize client from settings
bool DefaultLogic::initializedClient()
{
if (!Client::self())
return false;
addTrayIcon("main");
// Load postponed contact update
s_postponedContacts = Engine::configFile("contactupd",true);
s_postponedContacts.load(false);
// Load account status
AccountStatus::load();
AccountStatus::updateUi();
// Load muc rooms
s_mucRooms = Engine::configFile("client_mucrooms",true);
s_mucRooms.load(false);
Window* w = s_mucWizard->window();
if (w) {
unsigned int n = s_mucRooms.sections();
for (unsigned int i = 0; i < n; i++) {
NamedList* sect = s_mucRooms.getSection(i);
if (sect)
Client::self()->updateTableRow(s_mucSavedRooms,*sect,0,false,w);
}
}
Window* wMain = Client::self()->getWindow(s_wndMain);
NamedList dummy("client");
NamedList* cSect = Client::s_settings.getSection("client");
if (!cSect)
cSect = &dummy;
NamedList* cGen = Client::s_settings.getSection("general");
if (!cGen)
cGen = &dummy;
// Check if global settings override the users'
bool globalOverride = Engine::config().getBoolValue("client","globaloverride",false);
// Booleans
for (unsigned int i = 0; i < Client::OptCount; i++) {
bool tmp = Client::self()->getBoolOpt((Client::ClientToggle)i);
bool active = true;
if (globalOverride) {
String* over = Engine::config().getKey("client",Client::s_toggles[i]);
if (over) {
tmp = over->toBoolean(tmp);
active = false;
}
else
tmp = cGen->getBoolValue(Client::s_toggles[i],tmp);
}
else {
tmp = Engine::config().getBoolValue("client",Client::s_toggles[i],tmp);
tmp = cGen->getBoolValue(Client::s_toggles[i],tmp);
}
Client::self()->setActive(Client::s_toggles[i],active);
setClientParam(Client::s_toggles[i],String::boolText(tmp),false,true);
}
setAdvancedMode();
// Other string parameters
setClientParam("username",Client::s_settings.getValue("default","username"),false,true);
setClientParam("callerid",Client::s_settings.getValue("default","callerid"),false,true);
setClientParam("domain",Client::s_settings.getValue("default","domain"),false,true);
// Create default ring sound
String ring = cGen->getValue("ringinfile",Client::s_soundPath + "ring.wav");
Client::self()->createSound(Client::s_ringInName,ring);
ring = cGen->getValue("ringoutfile",Client::s_soundPath + "tone.wav");
Client::self()->createSound(Client::s_ringOutName,ring);
// Enable call actions
enableCallActions(m_selectedChannel);
// Set handlers
Client::self()->installRelay("chan.notify",Client::ChanNotify,100);
Client::self()->installRelay("muc.room",Client::MucRoom,100);
Client::self()->installRelay("transfer.notify",Client::TransferNotify,100);
// File transfer
s_lastFileDir = Client::s_settings.getValue("filetransfer","dir");
s_lastFileFilter = Client::s_settings.getValue("filetransfer","filter");
// Chat log
int v = lookup(cSect->getValue("logchat"),s_chatLogDict);
if (v == ChatLogSaveAll || v == ChatLogSaveUntilLogout || v == ChatLogNoSave)
s_chatLog = (ChatLogEnum)v;
// Update settings
NamedList p("");
// Chat contacts list options
String tmp;
Client::self()->getProperty(s_chatContactList,"_yate_showofflinecontacts",tmp,wMain);
p.addParam("check:" + s_chatShowOffline,String(tmp.toBoolean(true)));
tmp.clear();
Client::self()->getProperty(s_chatContactList,"_yate_flatlist",tmp,wMain);
p.addParam("check:" + s_chatFlatList,String(tmp.toBoolean(true)));
tmp.clear();
Client::self()->getProperty(s_chatContactList,"_yate_hideemptygroups",tmp,wMain);
p.addParam("check:chatcontact_hideemptygroups",String(tmp.toBoolean(true)));
// Show last page in main tab
p.addParam("select:" + s_mainwindowTabs,cSect->getValue("main_active_page","tabChat"));
// Settings
p.addParam("check:" + String(lookup(s_chatLog,s_chatLogDict)),String::boolText(true));
// Account edit defaults
setCheck(p,*cSect,"acc_showadvanced",false);
setCheck(p,*cSect,"acc_enabled");
Client::self()->setParams(&p);
// Build chat contacts context menu(s)
NamedList pcm(s_chatContactList);
NamedList* pChat = new NamedList("menu_" + s_chatContactList);
pChat->addParam("item:" + s_chatNew,"");
pChat->addParam("item:","");
pChat->addParam("item:" + s_chatShowOffline,"");
pChat->addParam("item:" + s_chatFlatList,"");
pcm.addParam(new NamedPointer("menu",pChat));
NamedList* pChatMenu = new NamedList("menu_" + s_chatContactList + "_contact");
pChatMenu->addParam("item:" + s_chat,"");
pChatMenu->addParam("item:" + s_chatCall,"");
pChatMenu->addParam("item:" + s_fileSend,"");
pChatMenu->addParam("item:" + s_chatShowLog,"");
pChatMenu->addParam("item:" + s_chatInfo,"");
pChatMenu->addParam("item:" + s_chatEdit,"");
pChatMenu->addParam("item:" + s_chatDel,"");
pChatMenu->addParam("item:","");
pChatMenu->addParam("item:" + s_chatNew,"");
pChatMenu->addParam("item:","");
pChatMenu->addParam("item:" + s_chatShowOffline,"");
pChatMenu->addParam("item:" + s_chatFlatList,"");
pcm.addParam(new NamedPointer("contactmenu",pChatMenu));
Client::self()->setParams(&pcm);
enableChatActions(0);
// Set gobal account status menu
NamedList pStatusMenu("");
pStatusMenu.addParam("owner","global_account_status");
pStatusMenu.addParam("item:setStatusOnline","");
pStatusMenu.addParam("item:setStatusBusy","");
pStatusMenu.addParam("item:setStatusAway","");
pStatusMenu.addParam("item:setStatusXa","");
pStatusMenu.addParam("item:setStatusDnd","");
pStatusMenu.addParam("item:","");
pStatusMenu.addParam("item:setStatusOffline","");
Client::self()->buildMenu(pStatusMenu);
// Activate the main window if not disabled from UI
if (wMain) {
String a;
Client::self()->getProperty(wMain->id(),"_yate_activateonstartup",a,wMain);
if (a.toBoolean(true))
Client::self()->setActive(wMain->id(),true,wMain);
}
return false;
}
// Client is exiting: save settings
void DefaultLogic::exitingClient()
{
clearDurationUpdate();
if (!Client::valid())
return;
// Avoid open account add the next time we start if the user closed the window
if (!Client::self()->getVisible(s_accWizard->toString()))
setClientParam(Client::s_toggles[Client::OptAddAccountOnStartup],
String(false),true,false);
// Reset wizards
s_accWizard->reset(true);
s_mucWizard->reset(true);
Client::self()->setVisible(s_accWizard->toString(),false);
Client::self()->setVisible(s_mucWizard->toString(),false);
// Hide some windows to avoid displaying them the next time we start
Client::self()->setVisible(s_wndAccount,false);
Client::self()->setVisible(s_wndChatContact,false);
Client::self()->setVisible(ClientContact::s_dockedChatWnd,false);
Client::self()->setVisible(s_wndAddrbook,false);
Client::self()->setVisible(s_wndMucInvite,false);
Client::self()->setVisible(s_wndFileTransfer,false);
// Save some settings identity
String tmp;
if (Client::self()->getText("def_username",tmp))
Client::s_settings.setValue("default","username",tmp);
tmp.clear();
if (Client::self()->getText("def_callerid",tmp))
Client::s_settings.setValue("default","callerid",tmp);
tmp.clear();
if (Client::self()->getText("def_domain",tmp))
Client::s_settings.setValue("default","domain",tmp);
tmp.clear();
Window* wMain = Client::self()->getWindow(s_wndMain);
if (wMain)
Client::self()->getSelect(s_mainwindowTabs,tmp,wMain);
Client::s_settings.setValue("client","main_active_page",tmp);
Client::save(Client::s_settings);
// Save callto history
NamedList p("");
if (Client::self()->getOptions(s_calltoList,&p)) {
NamedList* sect = Client::s_calltoHistory.createSection("calls");
sect->clearParams();
unsigned int n = p.length();
unsigned int max = 0;
for (unsigned int i = 0; max < s_maxCallHistory && i < n; i++) {
NamedString* s = p.getParam(i);
if (!s)
continue;
max++;
sect->addParam(s->name(),*s);
}
Client::save(Client::s_calltoHistory);
}
}
// Update from UI the selected item in channels list
void DefaultLogic::updateSelectedChannel(const String* item)
{
String old = m_selectedChannel;
if (item)
m_selectedChannel = *item;
else if (Client::self())
Client::self()->getSelect(s_channelList,m_selectedChannel);
else
m_selectedChannel = "";
if (old != m_selectedChannel)
channelSelectionChanged(old);
}
// Engine start notification. Connect startup accounts
void DefaultLogic::engineStart(Message& msg)
{
// Remove from postponed contacts missing accounts or invalid sections
ObjList remove;
unsigned int n = s_postponedContacts.sections();
for (unsigned int i = 0; i < n; i++) {
NamedList* sect = s_postponedContacts.getSection(i);
if (sect && sect->c_str() && !m_accounts->findAccount((*sect)["account"]))
remove.append(sect)->setDelete(false);
}
removePostponedContacts(remove);
// Set account status or start add wizard
if (m_accounts->accounts().skipNull())
setAccountsStatus(m_accounts);
else if (Client::valid() &&
Client::self()->getBoolOpt(Client::OptAddAccountOnStartup)) {
// Start add account wizard
s_accWizard->start();
}
}
// Method called by the client when idle
void DefaultLogic::idleTimerTick(Time& time)
{
for (ObjList* o = m_durationUpdate.skipNull(); o; o = o->skipNext())
(static_cast<DurationUpdate*>(o->get()))->update(time.sec(),&s_channelList);
if (Client::valid() && Client::self()->getBoolOpt(Client::OptNotifyChatState) &&
ContactChatNotify::checkTimeouts(*m_accounts,time))
Client::setLogicsTick();
}
// Enable call actions
bool DefaultLogic::enableCallActions(const String& id)
{
if (!Client::self())
return false;
ClientChannel* chan = id.null() ? 0 : ClientDriver::findChan(id);
DDebug(ClientDriver::self(),DebugInfo,"enableCallActions(%s) chan=%p",
id.c_str(),chan);
NamedList p("");
// Answer/Hangup/Hold
p.addParam("active:" + s_actionAnswer,String::boolText(chan && chan->isOutgoing() && !chan->isAnswered()));
p.addParam("active:" + s_actionHangup,String::boolText(0 != chan));
p.addParam("active:" + s_actionHold,String::boolText(chan != 0));
p.addParam("check:" + s_actionHold,String::boolText(chan && chan->active()));
// Transfer
// Not allowed on conference channels
bool active = false;
bool checked = false;
bool conf = chan && chan->conference();
if (chan && !conf) {
Lock lock(chan->driver());
if (chan->driver() && chan->driver()->channels().count() > 1)
active = true;
lock.drop();
checked = (0 != chan->transferId());
}
p.addParam("active:" + s_actionTransfer,String::boolText(active));
p.addParam("check:" + s_actionTransfer,String::boolText(active && checked));
// Activate/deactivate conference button
active = (0 != chan && chan->isAnswered());
p.addParam("active:" + s_actionConf,String::boolText(active));
p.addParam("check:" + s_actionConf,String::boolText(active && conf));
TelEngine::destruct(chan);
Client::self()->setParams(&p);
return true;
}
// Fill call start parameter list from UI
bool DefaultLogic::fillCallStart(NamedList& p, Window* wnd)
{
if (!checkParam(p,"target","callto",false,wnd))
return false;
checkParam(p,"line","account",true,wnd);
checkParam(p,"protocol","protocol",true,wnd);
checkParam(p,"account","account",true,wnd);
checkParam(p,"caller","def_username",false);
checkParam(p,"callername","def_callerid",false);
checkParam(p,"domain","def_domain",false);
return true;
}
// Notification on selection changes in channels list
void DefaultLogic::channelSelectionChanged(const String& old)
{
DDebug(ClientDriver::self(),DebugInfo,"channelSelectionChanged() to '%s' old='%s'",
m_selectedChannel.c_str(),old.c_str());
while (true) {
// Check if the transfer button was pressed
if (m_transferInitiated && m_transferInitiated == old) {
m_transferInitiated = "";
bool transfer = false;
if (Client::self())
Client::self()->getCheck(s_actionTransfer,transfer);
if (transfer) {
if (ClientDriver::setAudioTransfer(old,m_selectedChannel))
break;
else if (Client::self())
Client::self()->setStatusLocked("Failed to transfer");
}
}
m_transferInitiated = "";
// Set the active channel
if (Client::self()->getBoolOpt(Client::OptActivateCallOnSelect) &&
m_selectedChannel && ClientDriver::self())
ClientDriver::self()->setActive(m_selectedChannel);
break;
}
enableCallActions(m_selectedChannel);
}
// Fill contact edit/delete active parameters
void DefaultLogic::fillContactEditActive(NamedList& list, bool active, const String* item)
{
if (active) {
if (!Client::self())
return;
if (!Client::self()->getVisible(s_wndAddrbook)) {
ClientContact* c = 0;
if (item)
c = !item->null() ? m_accounts->findContactByInstance(*item) : 0;
else {
String sel;
Client::self()->getSelect(s_contactList,sel);
c = sel ? m_accounts->findContactByInstance(sel) : 0;
}
active = c && m_accounts->isLocalContact(c);
}
else
active = false;
}
const char* ok = String::boolText(active);
list.addParam("active:abk_del",ok);
list.addParam("active:abk_edit",ok);
}
// Fill log contact active parameter
void DefaultLogic::fillLogContactActive(NamedList& list, bool active, const String* item)
{
if (active) {
if (!Client::self())
return;
if (!Client::self()->getVisible(s_wndAddrbook)) {
if (item)
active = !item->null();
else {
String sel;
active = Client::self()->getSelect(s_logList,sel) && sel;
}
}
else
active = false;
}
list.addParam("active:log_contact",String::boolText(active));
}
// Clear a list/table. Handle specific lists like CDR, accounts, contacts
bool DefaultLogic::clearList(const String& action, Window* wnd)
{
if (!(Client::valid() && action))
return false;
// Check for a confirmation text
int pos = action.find(":");
String list;
if (pos > 0)
list = action.substr(0,pos);
else if (pos < 0)
list = action;
if (!list)
return false;
if (pos > 0) {
String text = action.substr(pos + 1);
if (!text) {
// Handle some known lists
if (list == s_logList)
text = "Clear call history?";
}
if (text)
return showConfirm(wnd,text,"clear:" + list);
}
DDebug(ClientDriver::self(),DebugAll,"DefaultLogic::clearList(%s,%p)",
list.c_str(),wnd);
// Handle CDR
if (list == s_logList)
return callLogClear(s_logList,String::empty());
bool ok = Client::self()->clearTable(list,wnd) || Client::self()->setText(list,"",false,wnd);
if (ok)
Client::self()->setFocus(list,false,wnd);
return ok;
}
// Delete a list/table item. Handle specific lists like CDR
bool DefaultLogic::deleteItem(const String& list, const String& item, Window* wnd, bool confirm)
{
if (!(Client::valid() && list && item))
return false;
DDebug(ClientDriver::self(),DebugAll,"DefaultLogic::deleteItem(%s,%s,%p,%u)",
list.c_str(),item.c_str(),wnd,confirm);
String context;
if (confirm)
context << "deleteitem:" << list << ":" << item;
// Handle known lists
if (list == s_chatContactList) {
ClientContact* c = m_accounts->findContact(item);
if (!c)
return false;
if (context) {
String text;
text << "Delete friend '" << c->m_name << "' from account '";
text << c->accountName() << "'?";
return showConfirm(wnd,text,context);
}
Engine::enqueue(Client::buildUserRoster(false,c->accountName(),c->uri()));
return true;
}
if (list == s_contactList) {
if (context) {
ClientContact* c = m_accounts->findContactByInstance(item);
if (!(c && m_accounts->isLocalContact(c)))
return false;
return showConfirm(wnd,"Delete contact '" + c->m_name + "'?",context);
}
return delContact(item,wnd);
}
if (list == s_accountList) {
if (context)
return showConfirm(wnd,"Delete account '" + item + "'?",context);
return delAccount(item,wnd);
}
if (list == s_logList) {
if (context)
return showConfirm(wnd,"Delete the selected call log?",context);
return callLogDelete(item);
}
if (list == ClientContact::s_dockedChatWidget) {
if (wnd && wnd->id() == ClientContact::s_mucsWnd) {
MucRoom* room = m_accounts->findRoomByMember(item);
if (room && room->ownMember(item)) {
if (context) {
// Request confirmation if there is an opened private chat
ObjList* o = room->resources().skipNull();
for (; o; o = o->skipNext()) {
MucRoomMember* m = static_cast<MucRoomMember*>(o->get());
if (room->hasChat(m->toString())) {
String text;
text << "You have active chat in room " << room->uri();
text << ".\r\nDo you want to proceed?";
return showConfirm(wnd,text,context);
}
}
}
logCloseMucSessions(room);
TelEngine::destruct(room);
return true;
}
else if (room) {
MucRoomMember* m = room->findMemberById(item);
if (m)
logCloseSession(room,false,m->m_name);
}
}
if (wnd && wnd->id() == ClientContact::s_dockedChatWnd) {
if (!s_changingDockedChat)
logCloseSession(m_accounts->findContact(item));
Client::self()->delTableRow(ClientContact::s_dockedChatWidget,item,wnd);
return true;
}
}
// Remove table row
return Client::self()->delTableRow(list,item,wnd);
}
// Handle list/table selection deletion
bool DefaultLogic::deleteSelectedItem(const String& action, Window* wnd)
{
if (!Client::valid())
return false;
DDebug(ClientDriver::self(),DebugAll,"DefaultLogic::deleteSelectedItem(%s,%p) wnd=%s",
action.c_str(),wnd,wnd ? wnd->id().c_str() : "");
// Check for a confirmation text
int pos = action.find(":");
String list;
if (pos > 0)
list = action.substr(0,pos);
else if (pos < 0)
list = action;
if (!list)
return false;
String item;
Client::self()->getSelect(list,item,wnd);
return item && deleteItem(list,item,wnd,pos > 0);
}
// Handle text changed notification
bool DefaultLogic::handleTextChanged(NamedList* params, Window* wnd)
{
if (!(params && wnd))
return false;
String sender = (*params)["sender"];
if (!sender)
return false;
// Chat input changes
if (Client::valid() && Client::self()->getBoolOpt(Client::OptNotifyChatState)) {
ClientContact* c = 0;
MucRoom* room = 0;
String id;
if (sender == ClientContact::s_chatInput)
c = m_accounts->findContact(wnd->context());
else
getPrefixedContact(sender,ClientContact::s_chatInput,id,m_accounts,&c,&room);
MucRoomMember* m = (!c && room) ? room->findMemberById(id) : 0;
if (c || m) {
String* text = params->getParam("text");
String tmp;
if (!text) {
text = &tmp;
if (c)
c->getChatInput(tmp);
else
room->getChatInput(id,tmp);
}
ContactChatNotify::update(c,room,m,text->null());
}
}
return false;
}
// Handle file transfer actions
bool DefaultLogic::handleFileTransferAction(const String& name, Window* wnd,
NamedList* params)
{
if (!Client::valid())
return false;
ClientContact* c = 0;
String file;
if (name == s_fileSend) {
String contact;
if (params)
contact = params->getValue("contact");
if (!contact)
Client::self()->getSelect(s_chatContactList,contact,wnd);
c = contact ? m_accounts->findContact(contact) : 0;
}
else if (name.startsWith(s_fileSendPrefix,false))
c = m_accounts->findContact(name.substr(s_fileSendPrefix.length()));
else if (name.startsWith(s_fileOpenSendPrefix,false)) {
// Choose file dialog action (params ? ok : cancel)
file = params ? params->getValue("file") : "";
if (!file)
return true;
// Update/save last dir and filter
s_lastFileDir = params->getValue("dir");
s_lastFileFilter = params->getValue("filter");
Client::s_settings.setValue("filetransfer","dir",s_lastFileDir);
Client::s_settings.setValue("filetransfer","filter",s_lastFileFilter);
// Retrieve the contact
c = m_accounts->findContact(name.substr(s_fileOpenSendPrefix.length()));
}
else if (name.startsWith(s_fileOpenRecvPrefix,false)) {
file = params ? params->getValue("file") : "";
if (!file)
return true;
String id = name.substr(s_fileOpenRecvPrefix.length());
NamedList item("");
Client::self()->getTableRow("messages",id,&item,wnd);
const String& chan = item["targetid"];
if (chan) {
// Add file transfer item
NamedList p(chan);
String text;
String buf;
const String& account = item["account"];
const String& contact = item["contact"];
ClientAccount* a = account ? m_accounts->findAccount(account) : 0;
ClientContact* c = a ? a->findContactByUri(contact) : 0;
if (c)
buildContactName(buf,*c);
else
buf = contact;
text << "Receiving '" << file << "'";
text.append(buf," from ");
p.addParam("send",String::boolText(false));
p.addParam("text",text);
p.addParam("select:progress","0");
p.addParam("account",account);
p.addParam("contact",contact);
p.addParam("contact_name",buf,false);
p.addParam("file",file);
p.addParam("channel",chan);
updateFileTransferItem(true,p,p,true);
// Remove the file
File::remove(file);
// Attach the consumer
Message m("chan.masquerade");
m.addParam("message","chan.attach");
m.addParam("id",chan);
m.addParam("consumer","filetransfer/receive/" + file);
m.copyParams(item);
m.addParam("autoclose",String::boolText(false));
m.addParam("notify",chan);
m.addParam("notify_progress",String::boolText(true));
Engine::dispatch(m);
// Answer the call
Message* anm = new Message("chan.masquerade");
anm->addParam("message","call.answered");
anm->addParam("id",chan);
Engine::enqueue(anm);
}
// Remove notification
Client::self()->delTableRow("messages",id,wnd);
// Update/save last dir
s_lastFileDir = params->getValue("dir");
Client::s_settings.setValue("filetransfer","dir",s_lastFileDir);
return true;
}
else if (name.startsWith("fileprogress_close:",false)) {
// Close file transfer
String id = name.substr(19);
if (id)
dropFileTransferItem(id);
return true;
}
else
return false;
if (!c)
return false;
if (!file)
return chooseFileTransfer(true,s_fileOpenSendPrefix + c->toString(),wnd);
ClientResource* res = c->findFileTransferResource();
Message m("call.execute");
m.addParam("callto","filetransfer/send/" + file);
String direct("jingle/" + c->uri());
if (res)
direct << "/" << res->toString();
m.addParam("direct",direct);
m.addParam("line",c->accountName(),false);
m.addParam("getfilemd5",String::boolText(true));
m.addParam("getfileinfo",String::boolText(true));
m.addParam("notify_progress",String::boolText(true));
m.addParam("autoclose",String::boolText(false));
m.addParam("send_chunk_size","4096");
m.addParam("send_interval","10");
String notify(c->toString());
notify << String(file.hash()) << (int)Time::now();
m.addParam("notify",notify);
if (!Engine::dispatch(m)) {
String s;
s << "Failed to send '" << file << "' to " << c->uri();
s.append(m.getValue("error"),"\r\n");
showError(wnd,s);
return false;
}
NamedList p(notify);
String text;
String buf;
buildContactName(buf,*c);
text << "Sending '" << file << "' to " << buf;
p.addParam("send",String::boolText(true));
p.addParam("text",text);
p.addParam("select:progress","0");
p.addParam("account",c->accountName());
p.addParam("contact",c->uri());
p.addParam("contact_name",buf,false);
p.addParam("file",file);
p.addParam("channel",m["id"]);
updateFileTransferItem(true,notify,p,true);
return true;
}
// Handle file transfer notifications
bool DefaultLogic::handleFileTransferNotify(Message& msg, bool& stopLogic)
{
const String& id = msg["targetid"];
if (!id)
return false;
if (Client::self()->postpone(msg,Client::TransferNotify)) {
stopLogic = true;
return true;
}
const String& status = msg["status"];
String progress;
String text;
bool running = status != "terminated";
if (running) {
int trans = msg.getIntValue("transferred");
int total = msg.getIntValue("total");
if (total && total > trans)
progress = (int)((int64_t)trans * 100 / total);
}
else {
NamedList p("");
getFileTransferItem(id,p);
const String& error = msg["error"];
bool send = msg.getBoolValue("send");
if (!error) {
progress = "100";
text << "Succesfully " << (send ? "sent '" : "received '");
text << p["file"] << "'";
text << (send ? " to " : " from ") << p["contact_name"];
}
else {
text << "Failed to " << (send ? "send '" : "receive '");
text << p["file"] << "'";
text << (send ? " to " : " from ") << p["contact_name"];
text << "\r\nError: " << error;
}
}
if (!(progress || text))
return true;
NamedList p(id);
p.addParam("text",text,false);
p.addParam("select:progress",progress,false);
if (!running)
p.addParam("cancel","Close");
updateFileTransferItem(false,id,p);
return true;
}
// Add/set an account
bool DefaultLogic::updateAccount(const NamedList& account, bool save,
const String& replace, bool loaded)
{
DDebug(ClientDriver::self(),DebugAll,
"ClientLogic(%s)::updateAccount(%s) save=%u replace=%s loaded=%u",
toString().c_str(),account.c_str(),save,replace.safe(),loaded);
ClientAccount* repl = replace ? m_accounts->findAccount(replace,true) : 0;
ClientAccount* acc = m_accounts->findAccount(account,true);
// This should never happen
if (repl && acc && acc != repl) {
TelEngine::destruct(repl);
TelEngine::destruct(acc);
Debug(ClientDriver::self(),DebugWarn,
"Attempt to replace an existing account with another account");
return false;
}
if (repl) {
TelEngine::destruct(acc);
acc = repl;
}
bool changed = false;
// Update account
if (acc) {
if (acc->toString() != account) {
// Account id changed:
// Disconnect the account, remove it and add a new one
if (!acc->resource().offline())
Engine::enqueue(userLogin(acc,false));
delAccount(acc->toString(),0);
TelEngine::destruct(acc);
}
else {
// Compare account parameters
changed = !(sameParams(acc->params(),account,s_accParams) &&
sameParams(acc->params(),account,s_accBoolParams) &&
sameParams(acc->params(),account,s_accProtoParams));
if (changed)
acc->m_params.copyParams(account);
}
}
if (!acc) {
String id;
// Adjust loaded account id to internally generated id
if (loaded) {
URI uri(account);
if (!(uri.getProtocol() && uri.getUser() && uri.getHost())) {
const String& proto = account["protocol"];
const String& user = account["username"];
const char* host = account.getValue("domain",account.getValue("server"));
if (proto && user && host)
id.assign(proto + ":" + user + "@" + host);
else {
Debug(ClientDriver::self(),DebugNote,
"Ignoring loaded account '%s' proto=%s user=%s host=%s",
account.c_str(),proto.c_str(),user.c_str(),host);
return false;
}
}
}
if (!id)
acc = new ClientAccount(account);
else {
NamedList p(account);
if (id != account) {
Debug(ClientDriver::self(),DebugInfo,
"Renaming loaded account '%s' to '%s'",
account.c_str(),id.c_str());
p.assign(id);
}
acc = new ClientAccount(p);
if (id != account)
acc->m_params.setParam("old_id",account.c_str());
}
if (loaded && !acc->params().getParam("savepassword"))
acc->m_params.setParam("savepassword",
String::boolText(0 != acc->params().getParam("password")));
if (!m_accounts->appendAccount(acc)) {
Debug(ClientDriver::self(),DebugNote,
"Failed to append duplicate account '%s'",acc->toString().c_str());
TelEngine::destruct(acc);
return false;
}
changed = true;
}
if (!changed) {
TelEngine::destruct(acc);
return true;
}
// Clear pending params
acc->m_params.clearParam("internal.status",'.');
// Save the account
if (save)
acc->save(true,acc->params().getBoolValue("savepassword"));
// (Re)set account own contact
setAccountContact(acc);
// Update account list
NamedList p("");
acc->fillItemParams(p);
p.addParam("check:enabled",String::boolText(acc->startup()));
p.addParam("status_image",resStatusImage(acc->resource().m_status),false);
Client::self()->updateTableRow(s_accountList,acc->toString(),&p);
// Make sure the account is selected in accounts list
Client::self()->setSelect(s_accountList,acc->toString());
// Update telephony account selector(s)
updateTelAccList(acc->startup(),acc);
// Reset selection if loaded: it will be set in setAdvancedMode() if appropriate
if (loaded)
Client::self()->setSelect(s_account,s_notSelected);
setAdvancedMode();
// (Dis)connect account
if (acc->resource().offline()) {
if (acc->startup())
setAccountStatus(m_accounts,acc);
}
else {
Engine::enqueue(userLogin(acc,false));
acc->m_params.setParam("internal.reconnect",String::boolText(true));
}
TelEngine::destruct(acc);
return true;
}
// Add/edit an account
bool DefaultLogic::internalEditAccount(bool newAcc, const String* account, NamedList* params,
Window* wnd)
{
if (!Client::valid() || Client::self()->getVisible(s_wndAccount))
return false;
NamedList dummy("");
if (!params)
params = &dummy;
// Make sure we reset all controls in window
params->setParam("select:" + s_accProviders,s_notSelected);
String proto;
ClientAccount* a = 0;
if (newAcc) {
proto = Client::s_settings.getValue("client","acc_protocol","sip");
// Check if the protocol is valid. Retrieve the first one if invalid
s_protocolsMutex.lock();
if (proto && !s_protocols.find(proto))
proto = "";
if (!proto) {
ObjList* o = s_protocols.skipNull();
if (o)
proto = o->get()->toString();
}
s_protocolsMutex.unlock();
}
else {
if (TelEngine::null(account))
a = selectedAccount(*m_accounts,wnd);
else
a = m_accounts->findAccount(*account);
if (!a)
return false;
proto = a->protocol();
}
const String& acc = a ? a->toString() : String::empty();
// Protocol combo and specific widget (page) data
bool adv = Client::s_settings.getBoolValue("client","acc_showadvanced",true);
params->setParam("check:acc_showadvanced",String::boolText(adv));
selectProtocolSpec(*params,proto,adv,s_accProtocol);
// Save password ?
bool save = false;
if (a)
save = a->params().getBoolValue("savepassword");
params->setParam("check:acc_savepassword",String::boolText(save));
// Reset all protocol specific data
updateProtocolList(0,String::empty(),0,params);
if (a)
updateProtocolSpec(*params,proto,true,a->params());
params->setParam("title",newAcc ? "Add account" : ("Edit account: " + acc).c_str());
params->setParam("context",acc);
return Client::openPopup(s_wndAccount,params);
}
// Utility used in handleDialogAction() to retrieve the room from context if input
// is valid
static inline MucRoom* getInput(ClientAccountList* list, const String& id, Window* w,
String& input, bool emptyOk = false)
{
if (!(list && id))
return 0;
Client::self()->getText("inputdialog_input",input,false,w);
return (emptyOk || input) ? list->findRoom(id) : 0;
}
// Handle dialog actions. Return true if handled
bool DefaultLogic::handleDialogAction(const String& name, bool& retVal, Window* wnd)
{
String n(name);
if (!n.startSkip("dialog:",false))
return false;
int pos = n.find(":");
if (pos < 0)
return false;
String dlg = n.substr(0,pos);
String ctrl = n.substr(pos + 1);
DDebug(ClientDriver::self(),DebugAll,
"DefaultLogic handleDialogAction(%s) dlg=%s action=%s wnd=%s",
name.c_str(),dlg.c_str(),ctrl.c_str(),wnd ? wnd->id().c_str() : "");
if (ctrl == "button_hide") {
retVal = true;
return true;
}
if (ctrl != "ok")
return false;
String context;
if (wnd && Client::valid())
Client::self()->getProperty(dlg,"_yate_context",context,wnd);
// Handle OK
if (dlg == s_mucChgSubject) {
// Accept MUC room subject change
String subject;
MucRoom* room = getInput(m_accounts,context,wnd,subject,true);
retVal = room && room->canChangeSubject();
if (retVal) {
Message* m = room->buildMucRoom("setsubject");
m->addParam("subject",subject);
retVal = Engine::enqueue(m);
}
}
else if (dlg == s_mucChgNick) {
// Accept MUC room nick change
String nick;
MucRoom* room = getInput(m_accounts,context,wnd,nick);
retVal = room && room->resource().online();
if (retVal && nick != room->resource().m_name) {
Message* m = room->buildMucRoom("setnick");
m->addParam("nick",nick);
retVal = Engine::enqueue(m);
}
}
else
retVal = context && Client::self()->action(wnd,context);
return true;
}
// Handle chat and contact related actions. Return true if handled
bool DefaultLogic::handleChatContactAction(const String& name, Window* wnd)
{
ClientContact* c = 0;
MucRoom* room = 0;
String id;
// Send chat action from single chat window, docked chat or MUC room
bool ok = getPrefixedContact(name,s_chatSend,id,m_accounts,&c,&room);
if (ok || name == s_chatSend) {
// Single chat window
if (!ok && wnd && wnd->context())
c = m_accounts->findContact(wnd->context());
if (c) {
DDebug(ClientDriver::self(),DebugAll,
"DefaultLogic sending chat for contact=%s",c->toString().c_str());
String text;
c->getChatInput(text);
if (text && c->sendChat(text)) {
unsigned int time = Time::secNow();
NamedList* tmp = buildChatParams(text,"me",time);
c->setChatProperty("history","_yate_tempitemreplace",String(false));
c->addChatHistory("chat_out",tmp);
c->setChatProperty("history","_yate_tempitemreplace",String(true));
c->setChatInput();
logChat(c,time,true,false,text);
}
}
else if (room) {
MucRoomMember* m = id ? room->findMemberById(id) : 0;
if (!m)
return false;
DDebug(ClientDriver::self(),DebugAll,
"DefaultLogic sending MUC chat room=%s nick=%s",
room->uri().c_str(),m->m_name.c_str());
String text;
room->getChatInput(id,text);
bool ok = false;
if (room->ownMember(m))
ok = text && room->sendChat(text,String::empty(),"groupchat");
else
ok = text && room->sendChat(text,m->m_name);
if (ok) {
unsigned int time = Time::secNow();
NamedList* tmp = buildChatParams(text,"me",time);
room->setChatProperty(id,"history","_yate_tempitemreplace",String(false));
room->addChatHistory(id,"chat_out",tmp);
room->setChatProperty(id,"history","_yate_tempitemreplace",String(true));
room->setChatInput(id);
logChat(room,time,true,false,text,room->ownMember(m),m->m_name);
}
}
else
return false;
return true;
}
// Show contact chat
if (name == s_chat || name == s_chatContactList) {
ClientContact* c = selectedChatContact(*m_accounts,wnd);
if (!c)
return false;
if (!c->hasChat()) {
c->createChatWindow();
NamedList p("");
fillChatContact(p,*c,true,true);
ClientResource* res = c->status();
c->updateChatWindow(p,"Chat [" + c->m_name + "]",
resStatusImage(res ? res->m_status : ClientResource::Offline));
}
c->showChat(true,true);
return true;
}
// Call chat contact
if (name == s_chatCall) {
ClientContact* c = selectedChatContact(*m_accounts,wnd);
if (!c)
return false;
ClientResource* res = c->findAudioResource();
if (!res)
return false;
NamedList p("");
p.addParam("line",c->accountName(),false);
p.addParam("account",c->accountName(),false);
p.addParam("target",c->uri());
p.addParam("instance",res->toString());
if (c->account())
p.addParam("protocol",c->account()->protocol(),false);
return callStart(p);
}
// Show chat contact log
if (name == s_chatShowLog) {
ClientContact* c = selectedChatContact(*m_accounts,wnd);
return logShow(c);
}
// Edit chat contact
if (name == s_chatEdit) {
ClientContact* c = selectedChatContact(*m_accounts,wnd);
return c && showContactEdit(*m_accounts,c);
}
if (getPrefixedContact(name,s_chatEdit,id,m_accounts,&c,0) && c) {
bool ok = showContactEdit(*m_accounts,c);
if (ok && wnd) {
// Hide contact info window
Window* w = getContactInfoEditWnd(false,c);
if (wnd == w)
Client::self()->closeWindow(wnd->id());
}
return ok;
}
// Add chat contact
if (name == s_chatNew)
return showContactEdit(*m_accounts);
// Remove chat contact
if (name == s_chatDel)
return deleteSelectedItem(s_chatContactList + ":",wnd);
// Show chat contact info
if (name == s_chatInfo) {
ClientContact* c = selectedChatContact(*m_accounts,wnd);
return updateContactInfo(c,true,true);
}
// Subscription management
bool sub = (name == s_chatSub);
bool unsubd = !sub && (name == s_chatUnsubd);
if (sub || unsubd || name == s_chatUnsub) {
ClientContact* c = selectedChatContact(*m_accounts,wnd);
if (!c)
return false;
if (!unsubd)
Engine::enqueue(Client::buildSubscribe(true,sub,c->accountName(),c->uri()));
else
Engine::enqueue(Client::buildSubscribe(false,false,c->accountName(),c->uri()));
return true;
}
// Save contact
if (name == "contactedit_ok") {
if (!(Client::valid() && wnd))
return false;
String contact;
bool reqSub = false;
ClientAccount* a = 0;
if (wnd->context()) {
// Edit
ClientContact* c = m_accounts->findContact(wnd->context());
if (c) {
a = c->account();
contact = c->uri();
}
if (!a) {
// Try to retrieve from data
String account;
Client::self()->getText("chatcontact_account",account,false,wnd);
a = m_accounts->findAccount(account);
if (!a) {
showError(wnd,"Account does not exists");
return false;
}
Client::self()->getText("chatcontact_uri",contact,false,wnd);
}
}
else {
a = selectedAccount(*m_accounts,wnd,"chataccount");
if (!a) {
showError(wnd,"You must select an account");
return false;
}
String user, domain;
Client::self()->getText("username",user,false,wnd);
Client::self()->getText("domain",domain,false,wnd);
if (!(user && domain)) {
showError(wnd,"You must enter an username and domain");
return false;
}
contact << user << "@" << domain;
Client::self()->getCheck("request_subscribe",reqSub,wnd);
}
String name;
Client::self()->getText("name",name,false,wnd);
NamedList p("");
Client::self()->getOptions("groups",&p,wnd);
String cid;
ClientContact::buildContactId(cid,a->toString(),contact);
// Clear from postponed contacts
s_postponedContacts.clearSection(cid);
Message* m = 0;
NamedList* sect = 0;
if (a->resource().online()) {
m = Client::buildUserRoster(true,a->toString(),contact);
m->addParam("name",name,false);
}
else {
// Postpone
Debug(ClientDriver::self(),DebugAll,
"Postponing update account=%s contact=%s reqSub=%u",
a->toString().c_str(),contact.c_str(),reqSub);
sect = s_postponedContacts.createSection(cid);
if (sect) {
sect->addParam("account",a->toString());
sect->addParam("contact",contact);
sect->addParam("name",name);
sect->addParam("request_subscribe",String::boolText(reqSub));
}
}
unsigned int n = p.length();
for (unsigned int i = 0; i < n; i++) {
NamedString* ns = p.getParam(i);
if (!(ns && ns->name()))
continue;
NamedList pp("");
Client::self()->getTableRow("groups",ns->name(),&pp,wnd);
if (!pp.getBoolValue("check:group"))
continue;
if (m)
m->addParam("group",ns->name(),false);
else if (sect)
sect->addParam("group",ns->name(),false);
}
if (m) {
Engine::enqueue(m);
if (reqSub)
Engine::enqueue(Client::buildSubscribe(true,true,a->toString(),contact));
}
Client::self()->setVisible(wnd->id(),false);
s_postponedContacts.save();
return true;
}
// Add group in contact edit/add window
if (name == "contactedit_addgroup") {
if (!(Client::valid() && wnd))
return false;
String grp;
Client::self()->getText("editgroup",grp,false,wnd);
if (!grp)
return false;
NamedList upd("");
NamedList* p = new NamedList(grp);
p->addParam("group",grp);
p->addParam("check:group",String::boolText(true));
upd.addParam(new NamedPointer(grp,p,String::boolText(true)));
if (Client::self()->updateTableRows("groups",&upd,false,wnd))
Client::self()->setText("editgroup",String::empty(),false,wnd);
return true;
}
return false;
}
// Handle actions from MUCS window. Return true if handled
bool DefaultLogic::handleMucsAction(const String& name, Window* wnd, NamedList* params)
{
XDebug(ClientDriver::self(),DebugAll,"DefaultLogic::handleMucsAction(%s)",
name.c_str());
MucRoom* room = 0;
String id;
if (getPrefixedContact(name,s_mucMembers,id,m_accounts,0,&room) ||
getPrefixedContact(name,s_mucPrivChat,id,m_accounts,0,&room)) {
// Handle item pressed in members list or show private chat
MucRoomMember* member = room ? selectedRoomMember(*room) : 0;
if (member && !room->ownMember(member) && room->canChatPrivate())
createRoomChat(*room,member,true);
return member != 0;
}
if (getPrefixedContact(name,s_mucChgSubject,id,m_accounts,0,&room)) {
// Change room subject
if (room && room->ownMember(id) && room->canChangeSubject()) {
String text;
text << "Change room '" << room->uri() << "' subject";
showInput(wnd,s_mucChgSubject,text,room->toString(),"Change room subject");
}
return true;
}
if (getPrefixedContact(name,s_mucChgNick,id,m_accounts,0,&room)) {
// Change room nickname
if (room && room->ownMember(id)) {
String text;
text << "Change nickname in room '" << room->uri() << "'";
showInput(wnd,s_mucChgNick,text,room->toString(),"Change nickname");
}
return true;
}
if (getPrefixedContact(name,s_mucInvite,id,m_accounts,0,&room)) {
// Invite contacts to conference
if (!room)
return false;
showMucInvite(*room,m_accounts);
return true;
}
if (getPrefixedContact(name,s_mucRoomShowLog,id,m_accounts,0,&room)) {
// Show MUC room log
if (!room)
return false;
logShow(room,true);
return true;
}
if (getPrefixedContact(name,s_mucMemberShowLog,id,m_accounts,0,&room)) {
// Show MUC room member log
MucRoomMember* member = room ? selectedRoomMember(*room) : 0;
if (!member)
return false;
logShow(room,room->ownMember(member),member->m_name);
return true;
}
bool kick = getPrefixedContact(name,s_mucKick,id,m_accounts,0,&room);
if (kick || getPrefixedContact(name,s_mucBan,id,m_accounts,0,&room)) {
MucRoomMember* member = room ? selectedRoomMember(*room) : 0;
if (!member || room->ownMember(member))
return false;
if (kick) {
if (room->canKick(member)) {
// TODO: implement reason input from user
Message* m = room->buildMucRoom("kick");
m->addParam("nick",member->m_name);
Engine::enqueue(m);
}
}
else if (room->canBan(member) && member->m_uri) {
Message* m = room->buildMucRoom("ban");
m->addParam("contact",member->m_uri);
Engine::enqueue(m);
}
return true;
}
return false;
}
// Handle select from MUCS window. Return true if handled
bool DefaultLogic::handleMucsSelect(const String& name, const String& item, Window* wnd,
const String& text)
{
MucRoom* room = 0;
String id;
if (getPrefixedContact(name,s_mucMembers,id,m_accounts,0,&room)) {
// Handle selection changes in members list
MucRoomMember* member = (room && item) ? room->findMemberById(item) : 0;
if (!room)
return false;
// Enable/disable actions
NamedList p("");
enableMucActions(p,*room,member,false);
room->updateChatWindow(room->resource().toString(),p);
return true;
}
return false;
}
// Handle resource.notify messages from MUC rooms
// The account was already checked
bool DefaultLogic::handleMucResNotify(Message& msg, ClientAccount* acc, const String& contact,
const String& instance, const String& operation)
{
if (!acc)
return false;
MucRoom* room = acc->findRoomByUri(contact);
if (!room)
return false;
Debug(ClientDriver::self(),DebugAll,
"Logic(%s) handle MUC notify account=%s contact=%s instance=%s operation=%s",
name().c_str(),acc->toString().c_str(),contact.c_str(),instance.safe(),
operation.c_str());
MucRoomMember* member = instance ? room->findMember(instance) : 0;
if (operation == "error") {
if (instance && !room->ownMember(member))
return false;
if (!instance && room->resource().m_status == ClientResource::Connecting) {
// Connection refused
String text("Failed to join room");
text.append(msg.getValue("reason",msg.getValue("error")),": ");
addChatNotify(*room,text,msg.msgTime().sec());
room->resource().m_status = ClientResource::Offline;
updateMucRoomMember(*room,room->resource());
}
return true;
}
if (!instance)
return false;
bool online = (operation == "online");
if (!online && operation != "offline")
return false;
// Get user status notifications
ObjList* list = String(msg.getParam("muc.userstatus")).split(',');
bool newRoom = 0 != list->find("newroom");
bool ownUser = 0 != list->find("ownuser");
bool userKicked = !online && list->find("userkicked");
bool userBanned = !online && list->find("userbanned");
String nick;
if (!ownUser && list->find("nickchanged"))
nick = msg.getParam("muc.nick");
TelEngine::destruct(list);
// Retrieve the member
if (!member && online) {
if (ownUser) {
member = &(room->resource());
nick = instance;
}
else
member = static_cast<MucRoomMember*>(room->appendResource(instance));
}
if (!member)
return false;
// Update contact UI. Set some chat history messages
if (userKicked || userBanned) {
String tmp(member->m_name + " was ");
const char* by = 0;
const char* r = 0;
if (userKicked) {
tmp << "kicked";
by = msg.getValue("muc.userkicked.by");
r = msg.getValue("muc.userkicked.reason");
}
else {
tmp << "banned";
by = msg.getValue("muc.userbanned.by");
r = msg.getValue("muc.userbanned.reason");
}
if (!TelEngine::null(by))
tmp << " by " << by;
if (!TelEngine::null(r))
tmp << " (" << r << ")";
addChatNotify(*room,tmp,msg.msgTime().sec());
}
bool changed = false;
// Update role and affiliation
const String& roleStr = msg["muc.role"];
int role = lookup(roleStr,MucRoomMember::s_roleName);
if (role != MucRoomMember::RoleUnknown && role != member->m_role) {
Debug(ClientDriver::self(),DebugAll,
"Logic(%s) account=%s room=%s nick=%s role set to '%s'",
name().c_str(),acc->toString().c_str(),room->uri().c_str(),
member->m_name.c_str(),roleStr.c_str());
member->m_role = role;
changed = true;
if (role != MucRoomMember::RoleNone) {
// Notify role change
String text;
if (room->ownMember(member))
text << "You are now a ";
else
text << member->m_name + " is now a ";
addChatNotify(*room,text + roleStr + " in the room",msg.msgTime().sec());
}
}
int aff = msg.getIntValue("muc.affiliation",MucRoomMember::s_affName);
if (aff != MucRoomMember::AffUnknown && aff != member->m_affiliation) {
Debug(ClientDriver::self(),DebugAll,
"Logic(%s) account=%s room=%s nick=%s affiliation set to '%s'",
name().c_str(),acc->toString().c_str(),room->uri().c_str(),
member->m_name.c_str(),msg.getValue("muc.affiliation"));
member->m_affiliation = aff;
if (member->m_affiliation == MucRoomMember::Outcast) {
String text;
if (room->ownMember(member))
text << "You are";
else
text << member->m_name + " is";
text << " no longer a room member";
addChatNotify(*room,text,msg.msgTime().sec());
}
changed = true;
}
// Update status
if (online != member->online()) {
// Create the room by setting a default config if this a new one
if (online && room->ownMember(member) && newRoom &&
room->resource().m_status == ClientResource::Connecting &&
member->m_affiliation == MucRoomMember::Owner)
Engine::enqueue(room->buildMucRoom("setconfig"));
if (member->m_status < ClientResource::Online)
member->m_status = ClientResource::Online;
else
member->m_status = ClientResource::Offline;
if (!room->ownMember(member)) {
String text(member->m_name);
text << " is " << lookup(member->m_status,ClientResource::s_statusName);
addChatNotify(*room,text,msg.msgTime().sec());
}
changed = true;
}
// Update contact/instance
if (!room->ownMember(member)) {
String* tmp = msg.getParam("muc.contact");
if (tmp && *tmp != member->m_uri) {
member->m_uri = *tmp;
changed = true;
}
tmp = msg.getParam("muc.contactinstance");
if (tmp && *tmp != member->m_instance) {
member->m_instance = *tmp;
changed = true;
}
}
// Handle nick changes
if (nick) {
String text;
if (room->ownMember(member))
text << "You are";
else {
text << member->m_name << " is";
// Close old member's chat log
logCloseSession(room,false,member->m_name);
}
text << " now known as " << nick;
addChatNotify(*room,text,msg.msgTime().sec());
member->m_name = nick;
changed = true;
}
// Update
if (changed)
updateMucRoomMember(*room,*member,&msg);
return true;
}
// Show/hide the notification area (messages)
bool DefaultLogic::showNotificationArea(bool show, Window* wnd, NamedList* upd)
{
if (!Client::self())
return false;
if (upd) {
Client::self()->updateTableRows("messages",upd,false,wnd);
addTrayIcon("notification");
}
else if (!show)
removeTrayIcon("notification");
NamedList p("");
const char* ok = String::boolText(show);
p.addParam("check:messages_show",ok);
p.addParam("show:frame_messages",ok);
Client::self()->setParams(&p,wnd);
if (wnd)
Client::self()->setUrgent(wnd->id(),true,wnd);
return true;
}
// Handle actions from notification area. Return true if handled
bool DefaultLogic::handleNotificationAreaAction(const String& action, Window* wnd)
{
String id = action;
const TokenDict* act = s_notifPrefix;
for (; act && act->token; act++)
if (id.startSkip(act->token,false))
break;
if (!(act && act->token))
return false;
NamedList p("");
Client::self()->getTableRow("messages",id,&p,wnd);
const String& type = p["item_type"];
const String& account = p["account"];
if (!(type && account))
return false;
bool handled = true;
bool remove = true;
if (type == "subscription") {
const String& contact = p["contact"];
if (!contact)
return false;
if (act->value == PrivNotificationOk) {
Engine::enqueue(Client::buildSubscribe(false,true,account,contact));
Engine::enqueue(Client::buildSubscribe(true,true,account,contact));
}
else if (act->value == PrivNotificationReject)
Engine::enqueue(Client::buildSubscribe(false,false,account,contact));
else
handled = false;
}
else if (type == "loginfail") {
if (act->value == PrivNotificationLogin) {
ClientAccount* acc = m_accounts->findAccount(account);
remove = acc && ::loginAccount(this,acc->params(),true);
}
else if (act->value == PrivNotificationAccEdit)
remove = internalEditAccount(false,&account,0,wnd);
else if (act->value == PrivNotificationAccounts) {
Window* w = Client::self()->getWindow(s_wndAcountList);
if (w) {
Client::self()->setSelect(s_accountList,account,w);
remove = Client::self()->setVisible(s_wndAcountList,true,true);
}
}
else
handled = false;
}
else if (type == "mucinvite") {
const String& room = p["room"];
if (!room)
return false;
if (act->value == PrivNotificationOk) {
ClientAccount* acc = m_accounts->findAccount(account);
if (acc) {
NamedList params("");
params.addParam("room_account",acc->toString());
URI uri(room);
params.addParam("room_room",uri.getUser());
params.addParam("room_server",uri.getHost());
params.addParam("room_nick",acc->contact() ? acc->contact()->uri().getUser().c_str() : "");
params.addParam("room_password",p["password"]);
params.addParam("check:room_history",String::boolText(true));
s_tempWizards.append(new JoinMucWizard(m_accounts,&params));
}
else
remove = false;
}
else if (act->value == PrivNotificationReject) {
Message* m = buildMucRoom("decline",account,String::empty());
m->copyParams(p,"room,contact,contact_instance");
// TODO: implement reason
Engine::enqueue(m);
}
else
handled = false;
}
else if (type == "incomingfile") {
const String& chan = p["targetid"];
if (chan) {
if (act->value == PrivNotificationOk) {
const String& file = p["file_name"];
if (file)
remove = !chooseFileTransfer(false,s_fileOpenRecvPrefix + id,wnd,file);
}
else {
Message* m = Client::buildMessage("call.drop",String::empty());
m->addParam("id",chan);
m->addParam("reason","rejected");
Engine::enqueue(m);
remove = true;
}
}
}
if (type == "rosterreqfail") {
if (act->value == PrivNotification1)
remove = queryRoster(m_accounts->findAccount(account));
}
else
return false;
if (handled) {
if (remove)
Client::self()->delTableRow("messages",id,wnd);
}
else
Debug(ClientDriver::self(),DebugStub,"Unhandled notification area action='%s' type=%s",
act->token,type.c_str());
return handled;
}
/**
* DurationUpdate
*/
// Destructor
DurationUpdate::~DurationUpdate()
{
setLogic();
}
// Get a string representation of this object
const String& DurationUpdate::toString() const
{
return m_id;
}
// Build a duration string representation and add the parameter to a list
unsigned int DurationUpdate::buildTimeParam(NamedList& dest, unsigned int secNow,
bool force)
{
return buildTimeParam(dest,m_name,m_startTime,secNow,force);
}
// Build a duration string representation hh:mm:ss. The hours are added only if non 0
unsigned int DurationUpdate::buildTimeString(String& dest, unsigned int secNow,
bool force)
{
return buildTimeString(dest,m_startTime,secNow,force);
}
// Set the logic used to update this duration object. Remove from the old one
void DurationUpdate::setLogic(ClientLogic* logic, bool owner)
{
if (m_logic) {
m_logic->removeDurationUpdate(this,false);
m_logic = 0;
}
m_logic = logic;
if (m_logic)
m_logic->addDurationUpdate(this,owner);
}
// Update UI if duration is non 0
unsigned int DurationUpdate::update(unsigned int secNow, const String* table,
Window* wnd, Window* skip, bool force)
{
NamedList p("");
unsigned int duration = buildTimeParam(p,secNow,force);
if ((duration || force) && Client::self()) {
if (table)
Client::self()->setTableRow(*table,toString(),&p,wnd,skip);
else
Client::self()->setParams(&p,wnd,skip);
}
return duration;
}
// Build a duration string representation and add the parameter to a list
unsigned int DurationUpdate::buildTimeParam(NamedList& dest, const char* param,
unsigned int secStart, unsigned int secNow, bool force)
{
String tmp;
unsigned int duration = buildTimeString(tmp,secStart,secNow,force);
if (duration || force)
dest.addParam(param,tmp);
return duration;
}
// Build a duration string representation hh:mm:ss. The hours are added only if non 0
unsigned int DurationUpdate::buildTimeString(String& dest, unsigned int secStart,
unsigned int secNow, bool force)
{
if (secNow < secStart)
secNow = secStart;
unsigned int duration = secNow - secStart;
if (!(duration || force))
return 0;
unsigned int hrs = duration / 3600;
if (hrs)
dest << hrs << ":";
unsigned int rest = duration % 3600;
unsigned int mins = rest / 60;
unsigned int secs = rest % 60;
dest << ((hrs && mins < 10) ? "0" : "") << mins << ":" << (secs < 10 ? "0" : "") << secs;
return duration;
}
// Release memory. Remove from updater
void DurationUpdate::destroyed()
{
setLogic();
RefObject::destroyed();
}
/* vi: set ts=8 sw=4 sts=4 noet: */