summaryrefslogtreecommitdiff
path: root/Protocols/SIP/SIPProto.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Protocols/SIP/SIPProto.cpp')
-rw-r--r--Protocols/SIP/SIPProto.cpp1108
1 files changed, 1108 insertions, 0 deletions
diff --git a/Protocols/SIP/SIPProto.cpp b/Protocols/SIP/SIPProto.cpp
new file mode 100644
index 0000000..0d591fd
--- /dev/null
+++ b/Protocols/SIP/SIPProto.cpp
@@ -0,0 +1,1108 @@
+/*
+Copyright (C) 2009 Ricardo Pescuma Domenecci
+
+This is free software; you can redistribute it and/or
+modify it under the terms of the GNU Library General Public
+License as published by the Free Software Foundation; either
+version 2 of the License, or (at your option) any later version.
+
+This 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
+Library General Public License for more details.
+
+You should have received a copy of the GNU Library General Public
+License along with this file; see the file license.txt. If
+not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+Boston, MA 02111-1307, USA.
+*/
+
+#include "commons.h"
+
+
+static INT_PTR CALLBACK DlgProcAccMgrUI(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam);
+static INT_PTR CALLBACK DlgOptions(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
+
+
+class SipToTchar
+{
+public:
+ SipToTchar(const pj_str_t &str) : tchar(NULL)
+ {
+ if (str.ptr == NULL)
+ return;
+
+ int size = MultiByteToWideChar(CP_UTF8, 0, str.ptr, str.slen, NULL, 0);
+ if (size < 0)
+ throw _T("Could not convert string to WCHAR");
+ size++;
+
+ WCHAR *tmp = (WCHAR *) mir_alloc(size * sizeof(WCHAR));
+ if (tmp == NULL)
+ throw _T("mir_alloc returned NULL");
+
+ MultiByteToWideChar(CP_UTF8, 0, str.ptr, str.slen, tmp, size);
+ tmp[size-1] = 0;
+
+#ifdef UNICODE
+
+ tchar = tmp;
+
+#else
+
+ size = WideCharToMultiByte(CP_ACP, 0, tmp, -1, NULL, 0, NULL, NULL);
+ if (size <= 0)
+ {
+ mir_free(tmp);
+ throw _T("Could not convert string to ACP");
+ }
+
+ tchar = (TCHAR *) mir_alloc(size * sizeof(char));
+ if (tchar == NULL)
+ {
+ mir_free(tmp);
+ throw _T("mir_alloc returned NULL");
+ }
+
+ WideCharToMultiByte(CP_ACP, 0, tmp, -1, tchar, size, NULL, NULL);
+
+ mir_free(tmp);
+
+#endif
+ }
+
+ ~SipToTchar()
+ {
+ if (tchar != NULL)
+ mir_free(tchar);
+ }
+
+ TCHAR *detach()
+ {
+ TCHAR *ret = tchar;
+ tchar = NULL;
+ return ret;
+ }
+
+ TCHAR * get() const
+ {
+ return tchar;
+ }
+
+ operator TCHAR *() const
+ {
+ return tchar;
+ }
+
+ TCHAR operator[](int pos) const
+ {
+ return tchar[pos];
+ }
+
+ TCHAR & operator[](int pos)
+ {
+ return tchar[pos];
+ }
+
+private:
+ TCHAR *tchar;
+};
+
+
+SIPProto::SIPProto(const char *aProtoName, const TCHAR *aUserName)
+{
+ //hasToDestroy = false;
+ transport_id = -1;
+ acc_id = -1;
+ hNetlibUser = 0;
+ hCallStateEvent = 0;
+ m_iDesiredStatus = m_iStatus = ID_STATUS_OFFLINE;
+
+ m_tszUserName = mir_tstrdup(aUserName);
+ m_szProtoName = mir_strdup(aProtoName);
+ m_szModuleName = mir_strdup(aProtoName);
+
+ static OptPageControl amCtrls[] = {
+ { NULL, CONTROL_TEXT, IDC_HOST, "Host", NULL, 0, 0, 256 },
+ { NULL, CONTROL_INT, IDC_PORT, "Port", 5060, 0, 1 },
+ { NULL, CONTROL_TEXT, IDC_USERNAME, "Username", NULL, 0, 0, 16 },
+ { NULL, CONTROL_PASSWORD, IDC_PASSWORD, "Password", NULL, IDC_SAVEPASSWORD, 0, 16 },
+ { NULL, CONTROL_CHECKBOX, IDC_SAVEPASSWORD, "SavePassword", (BYTE) TRUE },
+ };
+
+ memmove(accountManagerCtrls, amCtrls, sizeof(accountManagerCtrls));
+ accountManagerCtrls[0].var = &opts.host;
+ accountManagerCtrls[1].var = &opts.port;
+ accountManagerCtrls[2].var = &opts.username;
+ accountManagerCtrls[3].var = &opts.password;
+ accountManagerCtrls[4].var = &opts.savePassword;
+
+ static OptPageControl oCtrls[] = {
+ { NULL, CONTROL_TEXT, IDC_HOST, "Host", NULL, 0, 0, 256 },
+ { NULL, CONTROL_INT, IDC_PORT, "Port", 5060, 0, 1 },
+ { NULL, CONTROL_TEXT, IDC_USERNAME, "Username", NULL, 0, 0, 16 },
+ { NULL, CONTROL_PASSWORD, IDC_PASSWORD, "Password", NULL, IDC_SAVEPASSWORD, 0, 16 },
+ { NULL, CONTROL_CHECKBOX, IDC_SAVEPASSWORD, "SavePassword", (BYTE) TRUE },
+ { NULL, CONTROL_TEXT, IDC_STUN_HOST, "StunHost", NULL, 0, 0, 256 },
+ { NULL, CONTROL_INT, IDC_STUN_PORT, "StunPort", 0, 0, 1 },
+ };
+
+ memmove(optionsCtrls, oCtrls, sizeof(optionsCtrls));
+ optionsCtrls[0].var = &opts.host;
+ optionsCtrls[1].var = &opts.port;
+ optionsCtrls[2].var = &opts.username;
+ optionsCtrls[3].var = &opts.password;
+ optionsCtrls[4].var = &opts.savePassword;
+ optionsCtrls[5].var = &opts.stun.host;
+ optionsCtrls[6].var = &opts.stun.port;
+
+ LoadOpts(optionsCtrls, MAX_REGS(optionsCtrls), m_szModuleName);
+
+ CreateProtoService(PS_CREATEACCMGRUI, &SIPProto::CreateAccMgrUI);
+
+ CreateProtoService(PS_VOICE_CAPS, &SIPProto::VoiceCaps);
+ CreateProtoService(PS_VOICE_CALL, &SIPProto::VoiceCall);
+ CreateProtoService(PS_VOICE_ANSWERCALL, &SIPProto::VoiceAnswerCall);
+ CreateProtoService(PS_VOICE_DROPCALL, &SIPProto::VoiceDropCall);
+ CreateProtoService(PS_VOICE_HOLDCALL, &SIPProto::VoiceHoldCall);
+ CreateProtoService(PS_VOICE_SEND_DTMF, &SIPProto::VoiceSendDTMF);
+ CreateProtoService(PS_VOICE_CALL_STRING_VALID, &SIPProto::VoiceCallStringValid);
+
+ hCallStateEvent = CreateProtoEvent(PE_VOICE_CALL_STATE);
+
+ HookProtoEvent(ME_OPT_INITIALISE, &SIPProto::OnOptionsInit);
+}
+
+
+SIPProto::~SIPProto()
+{
+ Netlib_CloseHandle(hNetlibUser);
+
+ mir_free(m_tszUserName);
+ mir_free(m_szProtoName);
+ mir_free(m_szModuleName);
+}
+
+
+DWORD_PTR __cdecl SIPProto::GetCaps( int type, HANDLE hContact )
+{
+ switch(type)
+ {
+ case PFLAGNUM_1:
+ return 0;
+
+ case PFLAGNUM_2:
+ return PF2_ONLINE | PF2_SHORTAWAY | PF2_LONGAWAY | PF2_LIGHTDND | PF2_HEAVYDND
+ | PF2_FREECHAT | PF2_OUTTOLUNCH | PF2_ONTHEPHONE | PF2_INVISIBLE | PF2_IDLE;
+
+ case PFLAGNUM_3:
+ return 0;
+
+ case PFLAGNUM_4:
+ return PF4_NOCUSTOMAUTH;
+
+ case PFLAG_UNIQUEIDTEXT:
+ return (UINT_PTR) Translate("User");
+
+ case PFLAG_UNIQUEIDSETTING:
+ return (UINT_PTR) "Username";
+
+ case PFLAG_MAXLENOFMESSAGE:
+ return 100;
+ }
+
+ return 0;
+}
+
+
+int SIPProto::Connect()
+{
+ Trace(_T("Connecting..."));
+
+ BroadcastStatus(ID_STATUS_CONNECTING);
+/*
+ {
+ pj_status_t status = pjsua_create();
+ if (status != PJ_SUCCESS)
+ {
+ Error(status, _T("Error creating pjsua!"));
+ return -1;
+ }
+ hasToDestroy = true;
+ }
+
+ {
+ pjsua_config cfg;
+ pjsua_config_default(&cfg);
+ cfg.cb.on_incoming_call = &static_on_incoming_call;
+ cfg.cb.on_call_media_state = &static_on_call_media_state;
+ cfg.cb.on_call_state = &static_on_call_state;
+ cfg.cb.on_reg_state = &static_on_reg_state;
+
+ pjsua_logging_config log_cfg;
+ pjsua_logging_config_default(&log_cfg);
+ log_cfg.console_level = 4;
+ log_cfg.cb = &static_on_log;
+
+ pj_status_t status = pjsua_init(&cfg, &log_cfg, NULL);
+ if (status != PJ_SUCCESS)
+ {
+ Error(status, _T("Error initializing pjsua"));
+ Disconnect();
+ return 1;
+ }
+ }
+*/
+ {
+ pjsua_transport_config cfg;
+ pjsua_transport_config_default(&cfg);
+ pj_status_t status = pjsua_transport_create(PJSIP_TRANSPORT_UDP, &cfg, &transport_id);
+ if (status != PJ_SUCCESS)
+ {
+ Error(status, _T("Error creating transport"));
+ Disconnect();
+ return 2;
+ }
+ }
+/*
+ {
+ pj_status_t status = pjsua_start();
+ if (status != PJ_SUCCESS)
+ {
+ Error(status, _T("Error starting pjsua"));
+ Disconnect();
+ return 3;
+ }
+ }
+*/
+ {
+ TCHAR tmp[1024];
+
+ pjsua_acc_config cfg;
+ pjsua_acc_config_default(&cfg);
+ cfg.user_data = this;
+ cfg.transport_id = transport_id;
+
+ mir_sntprintf(tmp, MAX_REGS(tmp), _T("sip:%s@%s"), opts.username, opts.host);
+ TcharToUtf8 id(tmp);
+ cfg.id = pj_str(id);
+
+ mir_sntprintf(tmp, MAX_REGS(tmp), _T("sip:%s:%d"), opts.host, opts.port);
+ TcharToUtf8 reg_uri(tmp);
+ cfg.reg_uri = pj_str(reg_uri);
+
+ cfg.publish_enabled = PJ_TRUE;
+
+ cfg.cred_count = 1;
+ TcharToUtf8 host(opts.host);
+ cfg.cred_info[0].realm = pj_str(host);
+ cfg.cred_info[0].scheme = pj_str("digest");
+ TcharToUtf8 username(opts.username);
+ cfg.cred_info[0].username = pj_str(username);
+ cfg.cred_info[0].data_type = PJSIP_CRED_DATA_PLAIN_PASSWD; // TODO
+ cfg.cred_info[0].data = pj_str(opts.password);
+
+ pj_status_t status = pjsua_acc_add(&cfg, PJ_TRUE, &acc_id);
+ if (status != PJ_SUCCESS)
+ {
+ Error(status, _T("Error adding account"));
+ Disconnect();
+ return 4;
+ }
+ }
+
+ return 0;
+}
+
+
+int __cdecl SIPProto::SetStatus( int iNewStatus )
+{
+ if (m_iStatus == iNewStatus)
+ return 0;
+ m_iDesiredStatus = iNewStatus;
+
+ if (m_iDesiredStatus == ID_STATUS_OFFLINE)
+ {
+ Disconnect();
+ }
+ else if (m_iStatus == ID_STATUS_OFFLINE)
+ {
+ return Connect();
+ }
+ else if (m_iStatus > ID_STATUS_OFFLINE)
+ {
+ SendPresence(m_iDesiredStatus);
+ BroadcastStatus(m_iDesiredStatus);
+ }
+
+ return 0;
+}
+
+
+INT_PTR __cdecl SIPProto::CreateAccMgrUI(WPARAM wParam, LPARAM lParam)
+{
+ return (INT_PTR) CreateDialogParam(hInst, MAKEINTRESOURCE(IDD_ACCMGRUI),
+ (HWND) lParam, DlgProcAccMgrUI, (LPARAM)this);
+}
+
+
+void SIPProto::BroadcastStatus(int newStatus)
+{
+ Trace(_T("BroadcastStatus %d"), newStatus);
+
+ int oldStatus = m_iStatus;
+ m_iStatus = newStatus;
+ SendBroadcast(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)oldStatus, m_iStatus);
+}
+
+
+void SIPProto::CreateProtoService(const char* szService, SIPServiceFunc serviceProc)
+{
+ char str[MAXMODULELABELLENGTH];
+
+ mir_snprintf(str, sizeof(str), "%s%s", m_szModuleName, szService);
+ ::CreateServiceFunctionObj(str, (MIRANDASERVICEOBJ)*(void**)&serviceProc, this);
+}
+
+
+void SIPProto::CreateProtoService(const char* szService, SIPServiceFuncParam serviceProc, LPARAM lParam)
+{
+ char str[MAXMODULELABELLENGTH];
+ mir_snprintf(str, sizeof(str), "%s%s", m_szModuleName, szService);
+ ::CreateServiceFunctionObjParam(str, (MIRANDASERVICEOBJPARAM)*(void**)&serviceProc, this, lParam);
+}
+
+
+HANDLE SIPProto::CreateProtoEvent(const char* szService)
+{
+ char str[MAXMODULELABELLENGTH];
+ mir_snprintf(str, sizeof(str), "%s%s", m_szModuleName, szService);
+ return ::CreateHookableEvent(str);
+}
+
+
+void SIPProto::HookProtoEvent(const char* szEvent, SIPEventFunc pFunc)
+{
+ ::HookEventObj(szEvent, (MIRANDAHOOKOBJ)*(void**)&pFunc, this);
+}
+
+
+
+int SIPProto::SendBroadcast(HANDLE hContact, int type, int result, HANDLE hProcess, LPARAM lParam)
+{
+ ACKDATA ack = {0};
+ ack.cbSize = sizeof(ACKDATA);
+ ack.szModule = m_szModuleName;
+ ack.hContact = hContact;
+ ack.type = type;
+ ack.result = result;
+ ack.hProcess = hProcess;
+ ack.lParam = lParam;
+ return CallService(MS_PROTO_BROADCASTACK, 0, (LPARAM)&ack);
+}
+
+
+#define MESSAGE_TYPE_TRACE 0
+#define MESSAGE_TYPE_INFO 1
+#define MESSAGE_TYPE_ERROR 2
+
+
+void SIPProto::Trace(TCHAR *fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+
+ ShowMessage(MESSAGE_TYPE_TRACE, fmt, args);
+
+ va_end(args);
+}
+
+
+void SIPProto::Info(TCHAR *fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+
+ ShowMessage(MESSAGE_TYPE_INFO, TranslateTS(fmt), args);
+
+ va_end(args);
+}
+
+
+void SIPProto::Error(TCHAR *fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+
+ ShowMessage(MESSAGE_TYPE_ERROR, TranslateTS(fmt), args);
+
+ va_end(args);
+}
+
+
+void SIPProto::Error(pj_status_t status, TCHAR *fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+
+ TCHAR buff[1024];
+ mir_vsntprintf(buff, MAX_REGS(buff), TranslateTS(fmt), args);
+
+ va_end(args);
+
+ char errmsg[PJ_ERR_MSG_SIZE];
+ pj_strerror(status, errmsg, sizeof(errmsg));
+
+ Error(_T("%s: %s"), buff, Utf8ToTchar(errmsg));
+}
+
+
+void SIPProto::ShowMessage(int type, TCHAR *fmt, va_list args)
+{
+ TCHAR buff[1024];
+ if (type == MESSAGE_TYPE_TRACE)
+ lstrcpy(buff, _T("TRACE: "));
+ else if (type == MESSAGE_TYPE_INFO)
+ lstrcpy(buff, _T("INFO : "));
+ else
+ lstrcpy(buff, _T("ERROR: "));
+
+ mir_vsntprintf(&buff[7], MAX_REGS(buff)-7, fmt, args);
+
+ OutputDebugString(buff);
+ OutputDebugString(_T("\n"));
+ CallService(MS_NETLIB_LOG, (WPARAM) hNetlibUser, (LPARAM) TcharToChar(buff).get());
+}
+
+
+void SIPProto::Disconnect()
+{
+ Trace(_T("Disconnecting..."));
+
+ if (acc_id >= 0)
+ {
+ SendPresence(ID_STATUS_OFFLINE);
+ pjsua_acc_del(acc_id);
+ acc_id = -1;
+ }
+
+ if (transport_id >= 0)
+ {
+ pjsua_transport_close(transport_id, PJ_FALSE);
+ transport_id = -1;
+ }
+/*
+ if (hasToDestroy)
+ {
+ pjsua_destroy();
+ hasToDestroy = false;
+ }
+*/
+ BroadcastStatus(ID_STATUS_OFFLINE);
+}
+
+
+int __cdecl SIPProto::OnEvent( PROTOEVENTTYPE iEventType, WPARAM wParam, LPARAM lParam )
+{
+ switch(iEventType)
+ {
+ case EV_PROTO_ONLOAD:
+ return OnModulesLoaded(0, 0);
+
+ case EV_PROTO_ONEXIT:
+ return OnPreShutdown(0, 0);
+
+ case EV_PROTO_ONOPTIONS:
+ return OnOptionsInit(wParam, lParam);
+
+ case EV_PROTO_ONRENAME:
+ {
+ break;
+ }
+ }
+ return 1;
+}
+
+
+int __cdecl SIPProto::OnModulesLoaded(WPARAM wParam, LPARAM lParam)
+{
+ TCHAR buffer[MAX_PATH];
+ mir_sntprintf(buffer, MAX_REGS(buffer), TranslateT("%s plugin connections"), m_tszUserName);
+
+ NETLIBUSER nl_user = {0};
+ nl_user.cbSize = sizeof(nl_user);
+ nl_user.flags = /* NUF_INCOMING | NUF_OUTGOING | */ NUF_NOOPTIONS | NUF_TCHAR;
+ nl_user.szSettingsModule = m_szModuleName;
+ nl_user.ptszDescriptiveName = buffer;
+ hNetlibUser = (HANDLE) CallService(MS_NETLIB_REGISTERUSER, 0, (LPARAM)&nl_user);
+
+ if (!ServiceExists(MS_VOICESERVICE_REGISTER))
+ {
+ Error(_T("SIP needs Voice Service plugin to work!"));
+ return 1;
+ }
+
+ return 0;
+}
+
+
+int __cdecl SIPProto::OnOptionsInit(WPARAM wParam, LPARAM lParam)
+{
+ OPTIONSDIALOGPAGE odp = {0};
+ odp.cbSize = sizeof(odp);
+ odp.hInstance = hInst;
+ odp.ptszGroup = LPGENT("Network");
+ odp.ptszTitle = m_tszUserName;
+ odp.pfnDlgProc = DlgOptions;
+ odp.dwInitParam = (LPARAM) this;
+ odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPTIONS);
+ odp.flags = ODPF_BOLDGROUPS | ODPF_TCHAR;
+ CallService(MS_OPT_ADDPAGE, wParam, (LPARAM)&odp);
+
+ return 0;
+}
+
+
+int __cdecl SIPProto::OnPreShutdown(WPARAM wParam, LPARAM lParam)
+{
+ return 0;
+}
+
+
+void SIPProto::NotifyCall(pjsua_call_id call_id, int state, HANDLE hContact, TCHAR *name, TCHAR *number)
+{
+ Trace(_T("NotifyCall %d -> %d"), call_id, state);
+
+ char tmp[16];
+
+ VOICE_CALL vc = {0};
+ vc.cbSize = sizeof(vc);
+ vc.szModule = m_szModuleName;
+ vc.id = itoa((int) call_id, tmp, 10);
+ vc.flags = VOICE_TCHAR;
+ vc.hContact = hContact;
+ vc.ptszName = name;
+ vc.ptszNumber = number;
+ vc.state = state;
+
+ NotifyEventHooks(hCallStateEvent, (WPARAM) &vc, 0);
+}
+
+
+bool SIPProto::SendPresence(int proto_status)
+{
+ pjsua_acc_info info;
+ pj_status_t status = pjsua_acc_get_info(acc_id, &info);
+ if (status != PJ_SUCCESS)
+ {
+ Error(status, _T("Error obtaining call info"));
+ return false;
+ }
+
+ if (info.status != PJSIP_SC_OK)
+ return false;
+
+ pjrpid_element elem;
+ pj_bzero(&elem, sizeof(elem));
+ elem.type = PJRPID_ELEMENT_TYPE_PERSON;
+
+ switch(proto_status)
+ {
+ case ID_STATUS_AWAY:
+ elem.activity = PJRPID_ACTIVITY_AWAY;
+ elem.note = pj_str("Away");
+ break;
+ case ID_STATUS_NA:
+ elem.activity = PJRPID_ACTIVITY_AWAY;
+ elem.note = pj_str("Not avaible");
+ break;
+ case ID_STATUS_DND:
+ elem.activity = PJRPID_ACTIVITY_BUSY;
+ elem.note = pj_str("Do not disturb");
+ break;
+ case ID_STATUS_OCCUPIED:
+ elem.activity = PJRPID_ACTIVITY_BUSY;
+ elem.note = pj_str("Occupied");
+ break;
+ case ID_STATUS_FREECHAT:
+ elem.activity = PJRPID_ACTIVITY_UNKNOWN;
+ elem.note = pj_str("Free for chat");
+ break;
+ case ID_STATUS_ONTHEPHONE:
+ elem.activity = PJRPID_ACTIVITY_BUSY;
+ elem.note = pj_str("On the phone");
+ break;
+ case ID_STATUS_OUTTOLUNCH:
+ elem.activity = PJRPID_ACTIVITY_AWAY;
+ elem.note = pj_str("Out to lunch");
+ break;
+ default:
+ break;
+ }
+
+ pj_bool_t online = (proto_status == ID_STATUS_INVISIBLE ? PJ_FALSE : PJ_TRUE);
+
+ if (online == info.online_status && pj_strcmp(&info.online_status_text, &elem.note) == 0)
+ return false;
+
+ pjsua_acc_set_online_status2(acc_id, online, &elem);
+ return true;
+}
+
+
+void CALLBACK SIPProto::DisconnectProto(void *param)
+{
+ SIPProto *proto = (SIPProto *) param;
+ proto->Disconnect();
+}
+
+
+void SIPProto::on_reg_state()
+{
+ Trace(_T("on_reg_state"));
+
+ pjsua_acc_info info;
+ pj_status_t status = pjsua_acc_get_info(acc_id, &info);
+ if (status != PJ_SUCCESS)
+ {
+ Error(status, _T("Error obtaining call info"));
+ return;
+ }
+
+ if (info.status == PJSIP_SC_OK)
+ {
+ int status = (m_iDesiredStatus > ID_STATUS_OFFLINE ? m_iDesiredStatus : ID_STATUS_ONLINE);
+ SendPresence(status);
+ BroadcastStatus(status);
+ }
+ else
+ {
+ CallFunctionAsync(&SIPProto::DisconnectProto, this);
+ }
+}
+
+static void RemoveLtGt(TCHAR *str)
+{
+ int len = lstrlen(str);
+ if (str[0] == _T('<') && str[len-1] == _T('>'))
+ {
+ str[len-1] = 0;
+ memmove(str, &str[1], len * sizeof(TCHAR));
+ }
+}
+
+void SIPProto::on_incoming_call(pjsua_call_id call_id, pjsip_rx_data *rdata)
+{
+ Trace(_T("on_incoming_call: %d"), call_id);
+
+ pjsua_call_info info;
+ pj_status_t status = pjsua_call_get_info(call_id, &info);
+ if (status != PJ_SUCCESS)
+ {
+ Error(status, _T("Error obtaining call info"));
+ return;
+ }
+
+ TCHAR numberBuff[1024];
+ lstrcpyn(numberBuff, SipToTchar(info.remote_contact), MAX_REGS(numberBuff));
+ RemoveLtGt(numberBuff);
+
+ TCHAR name[256];
+ name[0] = 0;
+ SipToTchar remote_info(info.remote_info);
+ if (remote_info[0] == _T('"'))
+ {
+ TCHAR *other = _tcsstr(&remote_info[1], _T("\" <"));
+ if (other != NULL)
+ {
+ lstrcpyn(name, &remote_info[1], min(MAX_REGS(name), other - remote_info));
+ if (IsEmpty(numberBuff))
+ {
+ lstrcpyn(numberBuff, other+2, MAX_REGS(numberBuff));
+ RemoveLtGt(numberBuff);
+ }
+ }
+ }
+
+ if (IsEmpty(name) && IsEmpty(numberBuff))
+ lstrcpyn(numberBuff, remote_info, MAX_REGS(numberBuff));
+
+ TCHAR *number = numberBuff;
+
+ if (_tcsnicmp(_T("sip:"), number, 4) == 0)
+ {
+ number += 4;
+
+ TCHAR *host = _tcschr(number, _T('@'));
+ if (host != NULL && _tcsicmp(opts.host, host+1) == 0)
+ *host = 0;
+ }
+
+ NotifyCall(call_id, VOICE_STATE_RINGING, NULL, name, number);
+}
+
+
+void SIPProto::on_call_state(pjsua_call_id call_id, pjsip_event *e)
+{
+ Trace(_T("on_call_state: %d"), call_id);
+
+ pjsua_call_info info;
+ pj_status_t status = pjsua_call_get_info(call_id, &info);
+ if (status != PJ_SUCCESS)
+ {
+ Error(status, _T("Error obtaining call info"));
+ return;
+ }
+
+ Trace(_T("Call info: %d / last: %d %s"), info.state, info.last_status, info.last_status_text);
+
+ switch(info.state)
+ {
+ case PJSIP_INV_STATE_NULL:
+ case PJSIP_INV_STATE_DISCONNECTED:
+ {
+ NotifyCall(call_id, VOICE_STATE_ENDED);
+ break;
+ }
+ case PJSIP_INV_STATE_CONFIRMED:
+ {
+ NotifyCall(call_id, VOICE_STATE_TALKING);
+ break;
+ }
+ }
+
+}
+
+
+void SIPProto::on_call_media_state(pjsua_call_id call_id)
+{
+ Trace(_T("on_call_media_state: %d"), call_id);
+
+ pjsua_call_info info;
+ pj_status_t status = pjsua_call_get_info(call_id, &info);
+ if (status != PJ_SUCCESS)
+ {
+ Error(status, _T("Error obtaining call info"));
+ return;
+ }
+
+ // Connect ports appropriately when media status is ACTIVE or REMOTE HOLD,
+ // otherwise we should NOT connect the ports.
+ if (info.media_status == PJSUA_CALL_MEDIA_ACTIVE || info.media_status == PJSUA_CALL_MEDIA_REMOTE_HOLD)
+ {
+ ConfigureDevices();
+
+ pjsua_conf_connect(info.conf_slot, 0);
+ pjsua_conf_connect(0, info.conf_slot);
+ }
+
+ if (info.media_status == PJSUA_CALL_MEDIA_ACTIVE)
+ {
+ NotifyCall(call_id, VOICE_STATE_TALKING);
+ }
+ else if (info.media_status == PJSUA_CALL_MEDIA_LOCAL_HOLD)
+ {
+ NotifyCall(call_id, VOICE_STATE_ON_HOLD);
+ }
+}
+
+
+static int GetDevice(bool out, int defaultVal)
+{
+ DBVARIANT dbv;
+ if (DBGetContactSettingString(NULL, "VoiceService", out ? "Output" : "Input", &dbv))
+ return defaultVal;
+
+ int ret = defaultVal;
+
+ unsigned count = pjmedia_aud_dev_count();
+ for (unsigned i = 0; i < count; ++i)
+ {
+ pjmedia_aud_dev_info info;
+
+ pj_status_t status = pjmedia_aud_dev_get_info(i, &info);
+ if (status != PJ_SUCCESS)
+ continue;
+
+ if (!out && info.input_count < 1)
+ continue;
+ if (out && info.output_count < 1)
+ continue;
+
+ if (strcmp(dbv.pszVal, info.name) == 0)
+ {
+ ret = i;
+ break;
+ }
+ }
+
+ DBFreeVariant(&dbv);
+ return ret;
+}
+
+
+void SIPProto::ConfigureDevices()
+{
+ int input, output;
+ pjsua_get_snd_dev(&input, &output);
+
+ int expectedInput = GetDevice(false, input);
+ int expectedOutput = GetDevice(true, output);
+
+ if (input != expectedInput || output != expectedOutput)
+ pjsua_set_snd_dev(input, output);
+
+ if (DBGetContactSettingByte(NULL, "VoiceService", "EchoCancelation", TRUE))
+ pjsua_set_ec(PJSUA_DEFAULT_EC_TAIL_LEN, 0);
+ else
+ pjsua_set_ec(0, 0);
+}
+
+
+int __cdecl SIPProto::VoiceCaps(WPARAM wParam,LPARAM lParam)
+{
+ return VOICE_CAPS_VOICE | VOICE_CAPS_CALL_STRING;
+}
+
+
+void SIPProto::BuildURI(TCHAR *out, int outSize, const TCHAR *number)
+{
+ bool hasSip = (_tcsnicmp(_T("sip:"), number, 4) == 0);
+ bool hasHost = (_tcschr(number, _T('@')) != NULL);
+
+ if (!hasSip && !hasHost)
+ mir_sntprintf(out, outSize, _T("sip:%s@%s"), number, opts.host);
+ else if (!hasSip)
+ mir_sntprintf(out, outSize, _T("sip:%s"), number);
+ else
+ mir_sntprintf(out, outSize, _T("%s"), number);
+}
+
+
+int __cdecl SIPProto::VoiceCall(WPARAM wParam, LPARAM lParam)
+{
+ HANDLE hContact = (HANDLE) wParam;
+ TCHAR *number = (TCHAR *) lParam;
+
+ TCHAR uri[1024];
+
+ if (number != NULL)
+ {
+ if (!VoiceCallStringValid((WPARAM) number, 0))
+ return 1;
+
+ BuildURI(uri, MAX_REGS(uri), number);
+ }
+ else
+ {
+ // TODO
+ return 2;
+ }
+
+ pjsua_call_id call_id;
+ pj_status_t status = pjsua_call_make_call(acc_id, &pj_str(TcharToUtf8(uri)), 0, NULL, NULL, &call_id);
+ if (status != PJ_SUCCESS)
+ {
+ Error(status, _T("Error making call"));
+ return -1;
+ }
+
+ NotifyCall(call_id, VOICE_STATE_CALLING, hContact, number);
+
+ return 0;
+}
+
+
+int __cdecl SIPProto::VoiceAnswerCall(WPARAM wParam, LPARAM lParam)
+{
+ char *id = (char *) wParam;
+ if (id == NULL || id[0] == 0)
+ return 1;
+
+ pjsua_call_id call_id = (pjsua_call_id) atoi(id);
+ if (call_id < 0 || call_id >= (int) pjsua_call_get_max_count())
+ return 2;
+
+ pjsua_call_info info;
+ pj_status_t status = pjsua_call_get_info(call_id, &info);
+ if (status != PJ_SUCCESS)
+ {
+ Error(status, _T("Error obtaining call info"));
+ return -1;
+ }
+
+ if (info.state == PJSIP_INV_STATE_INCOMING)
+ {
+ pj_status_t status = pjsua_call_answer(call_id, 200, NULL, NULL);
+ if (status != PJ_SUCCESS)
+ {
+ Error(status, _T("Error answering call"));
+ return -1;
+ }
+ }
+ else if (info.media_status == PJSUA_CALL_MEDIA_LOCAL_HOLD)
+ {
+ pj_status_t status = pjsua_call_reinvite(call_id, PJ_TRUE, NULL);
+ if (status != PJ_SUCCESS)
+ {
+ Error(status, _T("Error un-holding call"));
+ return -1;
+ }
+ }
+ else
+ {
+ // Wrong state
+ return 3;
+ }
+
+ return 0;
+}
+
+
+int __cdecl SIPProto::VoiceDropCall(WPARAM wParam, LPARAM lParam)
+{
+ char *id = (char *) wParam;
+ if (id == NULL || id[0] == 0)
+ return 1;
+
+ pjsua_call_id call_id = (pjsua_call_id) atoi(id);
+ if (call_id < 0 || call_id >= (int) pjsua_call_get_max_count())
+ return 2;
+
+ pj_status_t status = pjsua_call_hangup(call_id, 0, NULL, NULL);
+ if (status != PJ_SUCCESS)
+ {
+ Error(status, _T("Error hanging up call"));
+ return -1;
+ }
+
+ return 0;
+}
+
+
+int __cdecl SIPProto::VoiceHoldCall(WPARAM wParam, LPARAM lParam)
+{
+ char *id = (char *) wParam;
+ if (id == NULL || id[0] == 0)
+ return 1;
+
+ pjsua_call_id call_id = (pjsua_call_id) atoi(id);
+ if (call_id < 0 || call_id >= (int) pjsua_call_get_max_count())
+ return 2;
+
+ pj_status_t status = pjsua_call_set_hold(call_id, NULL);
+ if (status != PJ_SUCCESS)
+ {
+ Error(status, _T("Error holding call"));
+ return -1;
+ }
+
+ // NotifyCall(0, VOICE_STATE_ON_HOLD);
+
+ return 0;
+}
+
+
+static bool IsValidDTMF(TCHAR c)
+{
+ if (c >= _T('A') && c <= _T('D'))
+ return true;
+ if (c >= _T('0') && c <= _T('9'))
+ return true;
+ if (c == _T('#') || c == _T('*'))
+ return true;
+
+ return false;
+}
+
+
+int __cdecl SIPProto::VoiceSendDTMF(WPARAM wParam, LPARAM lParam)
+{
+ char *id = (char *) wParam;
+ TCHAR c = (TCHAR) lParam;
+ if (id == NULL || id[0] == 0 || c == 0)
+ return 1;
+
+ pjsua_call_id call_id = (pjsua_call_id) atoi(id);
+ if (call_id < 0 || call_id >= (int) pjsua_call_get_max_count())
+ return 2;
+
+ if (c >= _T('a') && c <= _T('d'))
+ c += _T('A') - _T('a');
+
+ if (!IsValidDTMF(c))
+ return 3;
+
+ TCHAR tmp[2];
+ tmp[0] = c;
+ tmp[1] = 0;
+ pjsua_call_dial_dtmf(call_id, &pj_str(TcharToUtf8(tmp)));
+
+ return 0;
+}
+
+
+int __cdecl SIPProto::VoiceCallStringValid(WPARAM wParam, LPARAM lParam)
+{
+ TCHAR *number = (TCHAR *) wParam;
+
+ if (m_iStatus <= ID_STATUS_OFFLINE)
+ return 0;
+
+ if (number == NULL || number[0] == 0)
+ return 0;
+
+ TCHAR tmp[1024];
+ BuildURI(tmp, MAX_REGS(tmp), number);
+ return pjsua_verify_sip_url(TcharToUtf8(tmp)) == PJ_SUCCESS;
+}
+
+
+
+
+
+
+static INT_PTR CALLBACK DlgProcAccMgrUI(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ SIPProto *proto = (SIPProto *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
+
+ switch(msg)
+ {
+ case WM_INITDIALOG:
+ {
+ SetWindowLongPtr(hwnd, GWLP_USERDATA, lParam);
+ proto = (SIPProto *) lParam;
+ break;
+ }
+ }
+
+ if (proto == NULL)
+ return FALSE;
+
+ return SaveOptsDlgProc(proto->accountManagerCtrls, MAX_REGS(proto->accountManagerCtrls),
+ proto->m_szModuleName, hwnd, msg, wParam, lParam);
+}
+
+
+static INT_PTR CALLBACK DlgOptions(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ SIPProto *proto = (SIPProto *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
+
+ switch(msg)
+ {
+ case WM_INITDIALOG:
+ {
+ SetWindowLongPtr(hwnd, GWLP_USERDATA, lParam);
+ proto = (SIPProto *) lParam;
+ break;
+ }
+ }
+
+ if (proto == NULL)
+ return FALSE;
+
+ return SaveOptsDlgProc(proto->optionsCtrls, MAX_REGS(proto->optionsCtrls),
+ proto->m_szModuleName, hwnd, msg, wParam, lParam);
+}
+
+