diff options
Diffstat (limited to 'plugins/VoiceService/src/hooks.cpp')
-rw-r--r-- | plugins/VoiceService/src/hooks.cpp | 747 |
1 files changed, 747 insertions, 0 deletions
diff --git a/plugins/VoiceService/src/hooks.cpp b/plugins/VoiceService/src/hooks.cpp new file mode 100644 index 0000000000..a0e9e85999 --- /dev/null +++ b/plugins/VoiceService/src/hooks.cpp @@ -0,0 +1,747 @@ +/* +Copyright (C) 2006 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 "stdafx.h" + +static IconItem mainIcons[] = { + { LPGEN("Main"), "main", IDI_MAIN }, + { LPGEN("Dialpad"), "dialpad", IDI_DIALPAD }, + { LPGEN("Secure"), "secure", IDI_SECURE }, +}; + +static IconItem stateIcons[] = { + { LPGEN("Talking"), "talking", IDI_TALKING }, + { LPGEN("Ringing"), "ringing", IDI_RINGING }, + { LPGEN("Calling"), "calling", IDI_CALLING }, + { LPGEN("On Hold"), "onhold", IDI_ON_HOLD }, + { LPGEN("Ended"), "ended", IDI_ENDED }, + { LPGEN("Busy"), "busy", IDI_BUSY }, +}; + +static IconItem actionIcons[] = { + { LPGEN("Make Voice Call"), "call", IDI_ACTION_CALL }, + { LPGEN("Answer Voice Call"), "answer", IDI_ACTION_ANSWER }, + { LPGEN("Hold Voice Call"), "hold", IDI_ACTION_HOLD }, + { LPGEN("Drop Voice Call"), "drop", IDI_ACTION_DROP }, +}; + +SoundDescr g_sounds[NUM_STATES] = { + { "voice_started", LPGENW("Started talking") }, + { "voice_ringing", LPGENW("Ringing") }, + { "voice_calling", LPGENW("Calling a contact") }, + { "voice_holded", LPGENW("Put a call on Hold") }, + { "voice_ended", LPGENW("End of call") }, + { "voice_busy", LPGENW("Busy signal") }, +}; + +///////////////////////////////////////////////////////////////////////////////////////// + +static vector<HGENMENU> hCMCalls; +static HGENMENU hCMCall = NULL; +static HGENMENU hCMAnswer = NULL; +static HGENMENU hCMDrop = NULL; +static HGENMENU hCMHold = NULL; + +OBJLIST<VoiceProvider> modules(1, PtrKeySortT); +OBJLIST<VoiceCall> calls(1, PtrKeySortT); + +HFONT fonts[NUM_STATES] = { 0 }; +COLORREF font_colors[NUM_STATES] = { 0 }; +int font_max_height; + +COLORREF bkg_color = { 0 }; +HBRUSH bk_brush = NULL; + +static INT_PTR CListDblClick(WPARAM wParam, LPARAM lParam); + +static INT_PTR Service_CanCall(WPARAM wParam, LPARAM lParam); +static INT_PTR Service_Call(WPARAM wParam, LPARAM lParam); +static INT_PTR CMAnswer(WPARAM wParam, LPARAM lParam); +static INT_PTR CMHold(WPARAM wParam, LPARAM lParam); +static INT_PTR CMDrop(WPARAM wParam, LPARAM lParam); + +static VOID CALLBACK ClearOldVoiceCalls(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime); + +class CallingMethod +{ +public: + VoiceProvider *provider; + MCONTACT hContact; + wchar_t number[128]; + + CallingMethod(VoiceProvider *provider, MCONTACT hContact, const wchar_t *number = NULL) + : provider(provider), hContact(hContact) + { + if (number == NULL) + this->number[0] = 0; + else + lstrcpyn(this->number, number, _countof(this->number)); + } + + void Call() + { + provider->Call(hContact, number); + } +}; + +static int sttCompareCallingMethods(const CallingMethod *p1, const CallingMethod *p2) +{ + if (p1->hContact != p2->hContact) + return (int)p2->hContact - (int)p1->hContact; + + BOOL noNum1 = (IsEmptyW(p1->number) ? 1 : 0); + BOOL noNum2 = (IsEmptyW(p2->number) ? 1 : 0); + if (noNum1 != noNum2) + return noNum2 - noNum1; + + if (!noNum1) { + int numDif = lstrcmp(p1->number, p2->number); + if (numDif != 0) + return numDif; + } + + BOOL isProto1 = Proto_IsProtoOnContact(p1->hContact, p1->provider->name); + BOOL isProto2 = Proto_IsProtoOnContact(p2->hContact, p2->provider->name); + if (isProto1 != isProto2) + return isProto2 - isProto1; + + return lstrcmp(p1->provider->description, p2->provider->description); +} + +static void AddMethodsFrom(OBJLIST<CallingMethod> *list, MCONTACT hContact) +{ + for (int i = 0; i < modules.getCount(); i++) { + VoiceProvider *provider = &modules[i]; + if (provider->CanCall(hContact)) + list->insert(new CallingMethod(provider, hContact)); + } +} + +static void AddMethodsFrom(OBJLIST<CallingMethod> *list, MCONTACT hContact, const wchar_t *number) +{ + for (int i = 0; i < modules.getCount(); i++) { + VoiceProvider *provider = &modules[i]; + if (provider->CanCall(number)) + list->insert(new CallingMethod(provider, hContact, number)); + } +} + +static void BuildCallingMethodsList(OBJLIST<CallingMethod> *list, MCONTACT hContact) +{ + AddMethodsFrom(list, hContact); + + // Fetch contact number + char *proto = Proto_GetBaseAccountName(hContact); + + CMStringW protoNumber(db_get_wsm(hContact, proto, "Number")); + if (!protoNumber.IsEmpty()) + AddMethodsFrom(list, hContact, protoNumber); + + for (int i = 0; ; i++) { + char tmp[128]; + mir_snprintf(tmp, _countof(tmp), "MyPhone%d", i); + + CMStringW number(db_get_wsm(hContact, "UserInfo", tmp)); + if (!number.IsEmpty()) + AddMethodsFrom(list, hContact, number); + + if (number.IsEmpty() && i >= 4) + break; + } +} + +// Functions //////////////////////////////////////////////////////////////////////////// + +static MCONTACT ConvertMetacontact(MCONTACT hContact) +{ + MCONTACT hTmp = db_mc_getMostOnline(hContact); + if (hTmp != NULL) + return hTmp; + + return hContact; +} + +static void AddAccount(PROTOACCOUNT *acc) +{ + if (!acc->IsEnabled()) + return; + if (IsEmptyA(acc->szModuleName)) + return; + if (!ProtoServiceExists(acc->szModuleName, PS_VOICE_CAPS)) + return; + + int flags = CallProtoService(acc->szModuleName, PS_VOICE_CAPS, 0, 0); + + if ((flags & VOICE_CAPS_VOICE) == 0) + return; + + VOICE_MODULE vm = { 0 }; + vm.cbSize = sizeof(vm); + vm.name = acc->szModuleName; + vm.description = acc->tszAccountName; + vm.flags = flags; + VoiceRegister((WPARAM)&vm, 0); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +static int AccListChanged(WPARAM wParam, LPARAM lParam) +{ + PROTOACCOUNT *acc = (PROTOACCOUNT *)lParam; + if (acc == NULL) + return 0; + + VoiceProvider *provider = FindModule(acc->szModuleName); + + switch (wParam) { + case PRAC_ADDED: + AddAccount(acc); + break; + + case PRAC_CHANGED: + if (provider != NULL) + lstrcpyn(provider->description, acc->tszAccountName, _countof(provider->description)); + break; + + case PRAC_CHECKED: + { + BOOL enabled = acc->IsEnabled(); + + if (!enabled) { + if (provider != NULL) + VoiceUnregister((WPARAM)acc->szModuleName, 0); + } + else { + if (provider == NULL) + AddAccount(acc); + } + break; + } + case PRAC_REMOVED: + if (provider != NULL) + VoiceUnregister((WPARAM)acc->szModuleName, 0); + break; + } + + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +static INT_PTR Service_CallItem(WPARAM wParam, LPARAM, LPARAM param) +{ + MCONTACT hContact = (MCONTACT)wParam; + int index = (int)param; + + if (hContact == NULL) + return -1; + + hContact = ConvertMetacontact(hContact); + + OBJLIST<CallingMethod> methods(10, sttCompareCallingMethods); + BuildCallingMethodsList(&methods, hContact); + + if (index < 0 || index >= methods.getCount()) + return -2; + + methods[index].Call(); + return 0; +} + +static int PreBuildContactMenu(WPARAM wParam, LPARAM) +{ + Menu_ShowItem(hCMCall, false); + Menu_ShowItem(hCMAnswer, false); + Menu_ShowItem(hCMHold, false); + Menu_ShowItem(hCMDrop, false); + for (unsigned int i = 0; i < hCMCalls.size(); ++i) + Menu_ShowItem(hCMCalls[i], false); + + MCONTACT hContact = (MCONTACT)wParam; + if (hContact == NULL) + return -1; + + hContact = ConvertMetacontact(hContact); + + // There is a current call already? + VoiceCall *call = FindVoiceCall(hContact); + if (call == nullptr) { + OBJLIST<CallingMethod> methods(10, sttCompareCallingMethods); + BuildCallingMethodsList(&methods, hContact); + + if (methods.getCount() == 1) { + CallingMethod *method = &methods[0]; + + wchar_t name[128]; + if (!IsEmptyW(method->number)) + mir_snwprintf(name, _countof(name), TranslateT("Call %s with %s"), + method->number, method->provider->description); + else + mir_snwprintf(name, _countof(name), TranslateT("Call with %s"), + method->provider->description); + + Menu_ModifyItem(hCMCall, name); + Menu_ShowItem(hCMCall, true); + } + else if (methods.getCount() > 1) { + Menu_ModifyItem(hCMCall, TranslateT("Call")); + Menu_ShowItem(hCMCall, true); + + for (int i = 0; i < methods.getCount(); ++i) { + CallingMethod *method = &methods[i]; + + HICON hIcon = method->provider->GetIcon(); + + wchar_t name[128]; + if (!IsEmptyW(method->number)) + mir_snwprintf(name, _countof(name), TranslateT("%s with %s"), + method->number, method->provider->description); + else + mir_snwprintf(name, _countof(name), TranslateT("with %s"), + method->provider->description); + + char service[128]; + mir_snprintf(service, _countof(service), "VoiceService/ContactMenu/Call_%d", i); + + if (i == hCMCalls.size()) { + CreateServiceFunctionParam(service, Service_CallItem, i); + + CMenuItem mi(&g_plugin); + mi.position = i; + mi.flags = CMIF_UNICODE; + mi.name.w = name; + mi.hIcon = hIcon; + mi.pszService = service; + mi.root = hCMCall; + hCMCalls.push_back(Menu_AddContactMenuItem(&mi)); + } + else Menu_ModifyItem(hCMCalls[i], name, hIcon); + + method->provider->ReleaseIcon(hIcon); + } + } + } + else { + switch (call->state) { + case VOICE_STATE_CALLING: + Menu_ShowItem(hCMDrop, true); + break; + + case VOICE_STATE_TALKING: + if (call->module->CanHold()) + Menu_ShowItem(hCMHold, true); + Menu_ShowItem(hCMDrop, true); + break; + + case VOICE_STATE_RINGING: + case VOICE_STATE_ON_HOLD: + Menu_ShowItem(hCMAnswer, true); + Menu_ShowItem(hCMDrop, true); + break; + } + } + + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +static int IconsChanged(WPARAM, LPARAM) +{ + if (hwnd_frame != NULL) + PostMessage(hwnd_frame, WMU_REFRESH, 0, 0); + + return 0; +} + +static int ReloadColor(WPARAM, LPARAM) +{ + ColourIDW ci = { 0 }; + lstrcpyn(ci.group, TranslateT("Voice Calls"), _countof(ci.group)); + lstrcpyn(ci.name, TranslateT("Background"), _countof(ci.name)); + + bkg_color = Colour_GetW(ci); + + if (bk_brush != NULL) + DeleteObject(bk_brush); + bk_brush = CreateSolidBrush(bkg_color); + + if (hwnd_frame != NULL) + InvalidateRect(hwnd_frame, NULL, TRUE); + + return 0; +} + +VoiceProvider *FindModule(const char *szModule) +{ + for (int i = 0; i < modules.getCount(); i++) + if (strcmp(modules[i].name, szModule) == 0) + return &modules[i]; + + return NULL; +} + +static bool IsCall(VoiceCall *call, const char *szModule, const char *id) +{ + return strcmp(call->module->name, szModule) == 0 + && call->id != NULL && strcmp(call->id, id) == 0; +} + +VoiceCall *FindVoiceCall(const char *szModule, const char *id, bool add) +{ + for (int i = 0; i < calls.getCount(); i++) { + if (IsCall(&calls[i], szModule, id)) { + return &calls[i]; + } + } + + if (add) { + VoiceProvider *module = FindModule(szModule); + if (module == NULL) + return NULL; + + VoiceCall *tmp = new VoiceCall(module, id); + calls.insert(tmp); + return tmp; + } + + return NULL; +} + +VoiceCall* FindVoiceCall(MCONTACT hContact) +{ + for (int i = 0; i < calls.getCount(); i++) { + if (calls[i].state != VOICE_STATE_ENDED && calls[i].hContact == hContact) { + return &calls[i]; + } + } + + return NULL; +} + +static VOID CALLBACK ClearOldVoiceCalls(HWND, UINT, UINT_PTR, DWORD) +{ + DWORD now = GetTickCount(); + BOOL refresh = FALSE; + for (int i = calls.getCount() - 1; i >= 0; --i) { + VoiceCall *call = &calls[i]; + + if (call->state == VOICE_STATE_ENDED && call->end_time + TIME_TO_SHOW_ENDED_CALL < now) { + calls.remove(i); + refresh = TRUE; + } + } + + if (refresh && hwnd_frame != NULL) + PostMessage(hwnd_frame, WMU_REFRESH, 0, 0); +} + +bool CanCall(MCONTACT hContact, BOOL now) +{ + for (int i = 0; i < modules.getCount(); i++) { + if (modules[i].CanCall(hContact, now)) + return true; + } + + return false; +} + +bool CanCall(const wchar_t *number) +{ + for (int i = 0; i < modules.getCount(); i++) { + if (modules[i].CanCall(number)) + return true; + } + + return false; +} + +bool CanCallNumber() +{ + for (int i = 0; i < modules.getCount(); i++) { + if (modules[i].flags & VOICE_CAPS_CALL_STRING) + return true; + } + + return false; +} + +bool IsFinalState(int state) +{ + return state == VOICE_STATE_ENDED || state == VOICE_STATE_BUSY; +} + +VoiceCall *GetTalkingCall() +{ + for (int i = 0; i < calls.getCount(); ++i) { + VoiceCall *call = &calls[i]; + + if (call->state == VOICE_STATE_TALKING) + return call; + } + + return NULL; +} + +void HoldOtherCalls(VoiceCall *call) +{ + for (int i = 0; i < calls.getCount(); ++i) { + VoiceCall *other = &calls[i]; + + if (other == call || other->state != VOICE_STATE_TALKING) + continue; + + if (other->CanHold()) + other->Hold(); + else + other->Drop(); + } +} + +void Answer(VoiceCall *call) +{ + if (!call->CanAnswer()) + return; + + HoldOtherCalls(call); + + // Now annswer it + call->Answer(); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +static int ReloadFont(WPARAM, LPARAM) +{ + FontID fi = { 0 }; + strncpy_s(fi.group, "Voice Calls", _TRUNCATE); + + font_max_height = 0; + for (int i = 0; i < NUM_STATES; i++) { + if (fonts[i] != 0) DeleteObject(fonts[i]); + + strncpy_s(fi.name, stateIcons[i].szName, _TRUNCATE); + + LOGFONTA log_font = { 0 }; + font_colors[i] = Font_Get(fi, &log_font); + fonts[i] = CreateFontIndirectA(&log_font); + + font_max_height = max(font_max_height, log_font.lfHeight); + } + + if (hwnd_frame != NULL) + PostMessage(hwnd_frame, WMU_REFRESH, 0, 0); + + return 0; +} + +static INT_PTR Service_CanCall(WPARAM wParam, LPARAM) +{ + MCONTACT hContact = (MCONTACT)wParam; + if (hContact == NULL) + return -1; + + hContact = ConvertMetacontact(hContact); + return CanCall(hContact) ? 1 : 0; +} + +static INT_PTR Service_Call(WPARAM wParam, LPARAM) +{ + MCONTACT hContact = (MCONTACT)wParam; + if (hContact == NULL) + return -1; + + hContact = ConvertMetacontact(hContact); + + OBJLIST<CallingMethod> methods(10, sttCompareCallingMethods); + BuildCallingMethodsList(&methods, hContact); + + if (methods.getCount() < 1) + return -2; + + CallingMethod *method = &methods[0]; + if (!IsEmptyW(method->number)) + return -2; + + method->Call(); + return 0; +} + +static INT_PTR CMAnswer(WPARAM wParam, LPARAM) +{ + MCONTACT hContact = (MCONTACT)wParam; + if (hContact == NULL) + return -1; + + hContact = ConvertMetacontact(hContact); + + VoiceCall *call = FindVoiceCall(hContact); + if (call != NULL) + Answer(call); + + return 0; +} + +static INT_PTR CMHold(WPARAM wParam, LPARAM) +{ + MCONTACT hContact = (MCONTACT)wParam; + if (hContact == NULL) + return -1; + + hContact = ConvertMetacontact(hContact); + + VoiceCall *call = FindVoiceCall(hContact); + if (call != NULL) + call->Hold(); + + return 0; +} + +static INT_PTR CMDrop(WPARAM wParam, LPARAM) +{ + MCONTACT hContact = (MCONTACT)wParam; + if (hContact == NULL) + return -1; + + hContact = ConvertMetacontact(hContact); + + VoiceCall *call = FindVoiceCall(hContact); + if (call != NULL) + call->Drop(); + + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// Called when all the modules are loaded + +int ModulesLoaded(WPARAM, LPARAM) +{ + // add our modules to the KnownModules list + CallService("DBEditorpp/RegisterSingleModule", (WPARAM)MODULE_NAME, 0); + + // Init icons + g_plugin.registerIcon(LPGEN("Voice Calls"), mainIcons, "vc"); + g_plugin.registerIcon(LPGEN("Voice Calls"), stateIcons, "vc"); + g_plugin.registerIcon(LPGEN("Voice Calls"), actionIcons, "vca"); + + HookEvent(ME_SKIN_ICONSCHANGED, IconsChanged); + + // Init fonts + { + FontID fi = {}; + strncpy_s(fi.group, LPGEN("Voice Calls"), _TRUNCATE); + strncpy_s(fi.dbSettingsGroup, MODULE_NAME, _TRUNCATE); + + for (int i = 0; i < _countof(stateIcons); i++) { + fi.order = i; + strncpy_s(fi.name, stateIcons[i].szName, _TRUNCATE); + g_plugin.addFont(&fi); + } + + ReloadFont(0, 0); + HookEvent(ME_FONT_RELOAD, ReloadFont); + } + + // Init bkg color + { + ColourID ci = { 0 }; + strncpy_s(ci.group, LPGEN("Voice Calls"), _TRUNCATE); + strncpy_s(ci.name, LPGEN("Background"), _TRUNCATE); + strncpy_s(ci.dbSettingsGroup, MODULE_NAME, _TRUNCATE); + strncpy_s(ci.setting, "BkgColor", _TRUNCATE); + ci.defcolour = GetSysColor(COLOR_BTNFACE); + g_plugin.addColor(&ci); + + ReloadColor(0, 0); + HookEvent(ME_COLOUR_RELOAD, ReloadColor); + } + + InitOptions(); + InitFrames(); + + // Add menu items + CMenuItem mi(&g_plugin); + mi.position = -2000020000; + + mi.name.a = actionIcons[0].szDescr; + mi.hIcolibItem = g_plugin.getIconHandle(IDI_ACTION_CALL); + mi.pszService = MS_VOICESERVICE_CM_CALL; + hCMCall = Menu_AddContactMenuItem(&mi); + CreateServiceFunction(mi.pszService, Service_Call); + + mi.position++; + mi.name.a = actionIcons[1].szDescr; + mi.hIcolibItem = g_plugin.getIconHandle(IDI_ACTION_ANSWER); + mi.pszService = MS_VOICESERVICE_CM_ANSWER; + hCMAnswer = Menu_AddContactMenuItem(&mi); + CreateServiceFunction(mi.pszService, CMAnswer); + + mi.position++; + mi.position++; + mi.name.a = actionIcons[2].szDescr; + mi.hIcolibItem = g_plugin.getIconHandle(IDI_ACTION_HOLD); + mi.pszService = MS_VOICESERVICE_CM_HOLD; + hCMHold = Menu_AddContactMenuItem(&mi); + CreateServiceFunction(mi.pszService, CMHold); + + mi.position++; + mi.name.a = actionIcons[3].szDescr; + mi.hIcolibItem = g_plugin.getIconHandle(IDI_ACTION_DROP); + mi.pszService = MS_VOICESERVICE_CM_DROP; + hCMDrop = Menu_AddContactMenuItem(&mi); + CreateServiceFunction(mi.pszService, CMDrop); + + HookEvent(ME_CLIST_PREBUILDCONTACTMENU, PreBuildContactMenu); + + // Util services + CreateServiceFunction(MS_VOICESERVICE_CALL, Service_Call); + CreateServiceFunction(MS_VOICESERVICE_CAN_CALL, Service_CanCall); + + // Sounds + for (auto &it : g_sounds) + g_plugin.addSound(it.szName, LPGENW("Voice Calls"), it.wszDescr); + g_plugin.addSound("voice_dialpad", LPGENW("Voice Calls"), LPGENW("Dialpad press")); + + SetTimer(NULL, 0, 1000, ClearOldVoiceCalls); + + // Accounts + for (auto *pa : Accounts()) + AddAccount(pa); + + HookEvent(ME_PROTO_ACCLISTCHANGED, AccListChanged); + return 0; +} + +int PreShutdown(WPARAM, LPARAM) +{ + DeInitFrames(); + DeInitOptions(); + return 0; +} + +int ProtoAck(WPARAM, LPARAM lParam) +{ + ACKDATA *ack = (ACKDATA *)lParam; + if (ack->type == ACKTYPE_STATUS) + if (hwnd_frame != NULL) + PostMessage(hwnd_frame, WMU_REFRESH, 0, 0); + + return 0; +} |