From fee31daf0c5470fdb8c1efe39bf934bc2e1463be Mon Sep 17 00:00:00 2001 From: pescuma Date: Sat, 26 Dec 2009 01:24:30 +0000 Subject: sip: initial commit git-svn-id: http://pescuma.googlecode.com/svn/trunk/Miranda@194 c086bb3d-8645-0410-b8da-73a8550f86e7 --- Protocols/SIP/SIPProto.cpp | 1108 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1108 insertions(+) create mode 100644 Protocols/SIP/SIPProto.cpp (limited to 'Protocols/SIP/SIPProto.cpp') 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); +} + + -- cgit v1.2.3