/* Facebook plugin for Miranda Instant Messenger _____________________________________________ Copyright © 2009-11 Michal Zelinka, 2011-13 Robert Pösel 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, see . */ #include "common.h" FacebookProto::FacebookProto(const char* proto_name,const TCHAR* username) : PROTO(proto_name, username) { facy.parent = this; signon_lock_ = CreateMutex(NULL, FALSE, NULL); avatar_lock_ = CreateMutex(NULL, FALSE, NULL); log_lock_ = CreateMutex(NULL, FALSE, NULL); update_loop_lock_ = CreateEvent(NULL, FALSE, FALSE, NULL); facy.buddies_lock_ = CreateMutex(NULL, FALSE, NULL); facy.send_message_lock_ = CreateMutex(NULL, FALSE, NULL); facy.fcb_conn_lock_ = CreateMutex(NULL, FALSE, NULL); CreateProtoService(PS_CREATEACCMGRUI, &FacebookProto::SvcCreateAccMgrUI); CreateProtoService(PS_GETMYAWAYMSG, &FacebookProto::GetMyAwayMsg); CreateProtoService(PS_GETMYAVATART, &FacebookProto::GetMyAvatar); CreateProtoService(PS_GETAVATARINFOT, &FacebookProto::GetAvatarInfo); CreateProtoService(PS_GETAVATARCAPS, &FacebookProto::GetAvatarCaps); CreateProtoService(PS_JOINCHAT, &FacebookProto::OnJoinChat); CreateProtoService(PS_LEAVECHAT, &FacebookProto::OnLeaveChat); CreateProtoService("/Mind", &FacebookProto::OnMind); HookProtoEvent(ME_CLIST_PREBUILDSTATUSMENU, &FacebookProto::OnBuildStatusMenu); HookProtoEvent(ME_OPT_INITIALISE, &FacebookProto::OnOptionsInit); HookProtoEvent(ME_GC_EVENT, &FacebookProto::OnChatOutgoing); HookProtoEvent(ME_IDLE_CHANGED, &FacebookProto::OnIdleChanged); HookProtoEvent(ME_TTB_MODULELOADED, &FacebookProto::OnToolbarInit); InitHotkeys(); InitPopups(); InitSounds(); // Create standard network connection TCHAR descr[512]; char module[512]; NETLIBUSER nlu = {sizeof(nlu)}; nlu.flags = NUF_INCOMING | NUF_OUTGOING | NUF_HTTPCONNS | NUF_TCHAR; nlu.szSettingsModule = m_szModuleName; mir_snprintf(module, SIZEOF(module), "%s", m_szModuleName); nlu.szSettingsModule = module; mir_sntprintf(descr, SIZEOF(descr), TranslateT("%s server connection"), m_tszUserName); nlu.ptszDescriptiveName = descr; m_hNetlibUser = (HANDLE)CallService(MS_NETLIB_REGISTERUSER, 0, (LPARAM)&nlu); if (m_hNetlibUser == NULL) MessageBox(NULL, TranslateT("Unable to get Netlib connection for Facebook"), m_tszUserName, MB_OK); facy.set_handle(m_hNetlibUser); mir_sntprintf(descr, SIZEOF(descr), _T("%%miranda_avatarcache%%\\%s"), m_tszUserName); hAvatarFolder_ = FoldersRegisterCustomPathT(LPGEN("Avatars"), m_szModuleName, descr, m_tszUserName); // Set all contacts offline -- in case we crashed SetAllContactStatuses(ID_STATUS_OFFLINE, true); } FacebookProto::~FacebookProto() { // Uninit popup classes for (std::vector::size_type i = 0; i < popupClasses.size(); i++) Popup_UnregisterClass(popupClasses[i]); popupClasses.clear(); Netlib_CloseHandle(m_hNetlibUser); WaitForSingleObject(signon_lock_, IGNORE); WaitForSingleObject(avatar_lock_, IGNORE); WaitForSingleObject(log_lock_, IGNORE); WaitForSingleObject(facy.buddies_lock_, IGNORE); WaitForSingleObject(facy.send_message_lock_, IGNORE); CloseHandle(signon_lock_); CloseHandle(avatar_lock_); CloseHandle(log_lock_); CloseHandle(update_loop_lock_); CloseHandle(facy.buddies_lock_); CloseHandle(facy.send_message_lock_); CloseHandle(facy.fcb_conn_lock_); } ////////////////////////////////////////////////////////////////////////////// DWORD_PTR FacebookProto::GetCaps(int type, HANDLE hContact) { switch(type) { case PFLAGNUM_1: { DWORD_PTR flags = PF1_IM | PF1_CHAT | PF1_SERVERCLIST | PF1_AUTHREQ | /*PF1_ADDED |*/ PF1_BASICSEARCH | PF1_SEARCHBYEMAIL | PF1_SEARCHBYNAME | PF1_ADDSEARCHRES; // | PF1_VISLIST | PF1_INVISLIST; if (getByte(FACEBOOK_KEY_SET_MIRANDA_STATUS, 0)) return flags |= PF1_MODEMSG; else return flags |= PF1_MODEMSGRECV; } case PFLAGNUM_2: return PF2_ONLINE | PF2_INVISIBLE | PF2_ONTHEPHONE | PF2_IDLE; // | PF2_SHORTAWAY; case PFLAGNUM_3: if (getByte(FACEBOOK_KEY_SET_MIRANDA_STATUS, 0)) return PF2_ONLINE; // | PF2_SHORTAWAY; else return 0; case PFLAGNUM_4: return PF4_NOCUSTOMAUTH | PF4_FORCEADDED | PF4_IMSENDUTF | PF4_AVATARS | PF4_SUPPORTTYPING | PF4_NOAUTHDENYREASON | PF4_IMSENDOFFLINE; case PFLAGNUM_5: return PF2_ONTHEPHONE; case PFLAG_MAXLENOFMESSAGE: return FACEBOOK_MESSAGE_LIMIT; case PFLAG_UNIQUEIDTEXT: return (DWORD_PTR) "Facebook ID"; case PFLAG_UNIQUEIDSETTING: return (DWORD_PTR) FACEBOOK_KEY_ID; } return 0; } ////////////////////////////////////////////////////////////////////////////// int FacebookProto::SetStatus(int new_status) { LOG("===== Beginning SetStatus process"); // Routing statuses not supported by Facebook switch (new_status) { case ID_STATUS_INVISIBLE: case ID_STATUS_OFFLINE: m_iDesiredStatus = new_status; break; case ID_STATUS_IDLE: default: m_iDesiredStatus = ID_STATUS_INVISIBLE; if (getByte(FACEBOOK_KEY_MAP_STATUSES, DEFAULT_MAP_STATUSES)) break; case ID_STATUS_ONLINE: case ID_STATUS_FREECHAT: m_iDesiredStatus = ID_STATUS_ONLINE; break; } if (new_status != ID_STATUS_OFFLINE && m_iStatus == ID_STATUS_CONNECTING) { LOG("===== Status is already connecting, no change"); return 0; } if (m_iStatus == m_iDesiredStatus) { LOG("===== Statuses are same, no change"); return 0; } facy.invisible_ = (new_status == ID_STATUS_INVISIBLE); ForkThread(&FacebookProto::ChangeStatus, this); return 0; } int FacebookProto::SetAwayMsg(int status, const PROTOCHAR *msg) { if (!msg) { last_status_msg_.clear(); return 0; } char *narrow = mir_utf8encodeT(msg); if (last_status_msg_ != narrow) last_status_msg_ = narrow; mir_free(narrow); if (isOnline() && getByte(FACEBOOK_KEY_SET_MIRANDA_STATUS, DEFAULT_SET_MIRANDA_STATUS)) ForkThread(&FacebookProto::SetAwayMsgWorker, NULL); return 0; } void FacebookProto::SetAwayMsgWorker(void *p) { if (p != NULL) { status_data *data = static_cast(p); facy.post_status(data); delete data; } else if (!last_status_msg_.empty()) { status_data data; data.text = last_status_msg_; data.privacy = facy.get_privacy_type(); facy.post_status(&data); } } HANDLE FacebookProto::SearchBasic(const PROTOCHAR* id) { if (isOffline()) return 0; TCHAR *tid = mir_tstrdup(id); ForkThread(&FacebookProto::SearchIdAckThread, tid); return tid; } HANDLE FacebookProto::SearchByEmail(const PROTOCHAR* email) { if (isOffline()) return 0; TCHAR *temail = mir_tstrdup(email); ForkThread(&FacebookProto::SearchAckThread, temail); return temail; } HANDLE FacebookProto::SearchByName(const PROTOCHAR* nick, const PROTOCHAR* firstName, const PROTOCHAR* lastName) { TCHAR arg[200]; mir_sntprintf (arg, SIZEOF(arg), _T("%s %s %s"), nick, firstName, lastName); return SearchByEmail(arg); // Facebook is using one search method for everything (except IDs) } HANDLE FacebookProto::AddToList(int flags, PROTOSEARCHRESULT* psr) { ptrA id( mir_t2a_cp(psr->id, CP_UTF8)); ptrA name( mir_t2a_cp(psr->firstName, CP_UTF8)); ptrA surname( mir_t2a_cp(psr->lastName, CP_UTF8)); if (id == NULL) return NULL; facebook_user fbu; fbu.user_id = id; if (name != NULL) fbu.real_name = name; if (surname != NULL) { fbu.real_name += " "; fbu.real_name += surname; } if (fbu.user_id.find_first_not_of("0123456789") != std::string::npos) { MessageBox(0, TranslateT("Facebook ID must be numeric value."), m_tszUserName, MB_ICONERROR | MB_OK); return NULL; } HANDLE hContact = AddToContactList(&fbu, CONTACT_NONE); if (hContact) { if (flags & PALF_TEMPORARY) { db_set_b(hContact, "Clist", "Hidden", 1); db_set_b(hContact, "Clist", "NotOnList", 1); } else if (db_get_b(hContact, "CList", "NotOnList", 0)) { db_unset(hContact, "CList", "Hidden"); db_unset(hContact, "CList", "NotOnList"); } } return hContact; } int FacebookProto::AuthRequest(HANDLE hContact,const PROTOCHAR *message) { return RequestFriendship((WPARAM)hContact, NULL); } int FacebookProto::Authorize(HANDLE hDbEvent) { if (!hDbEvent || isOffline()) return 1; HANDLE hContact = HContactFromAuthEvent(hDbEvent); if (hContact == INVALID_HANDLE_VALUE) return 1; return ApproveFriendship((WPARAM)hContact, NULL); } int FacebookProto::AuthDeny(HANDLE hDbEvent, const PROTOCHAR *reason) { if (!hDbEvent || isOffline()) return 1; HANDLE hContact = HContactFromAuthEvent(hDbEvent); if (hContact == INVALID_HANDLE_VALUE) return 1; // TODO: hide from facebook requests list if (db_get_b(hContact, "CList", "NotOnList", 0)) CallService(MS_DB_CONTACT_DELETE, (WPARAM)hContact, 0); return 0; } ////////////////////////////////////////////////////////////////////////////// // SERVICES INT_PTR FacebookProto::GetMyAwayMsg(WPARAM wParam, LPARAM lParam) { DBVARIANT dbv = { DBVT_TCHAR }; if (!getTString("StatusMsg", &dbv) && lstrlen(dbv.ptszVal) != 0) { int res = (lParam & SGMA_UNICODE) ? (INT_PTR)mir_t2u(dbv.ptszVal) : (INT_PTR)mir_t2a(dbv.ptszVal); db_free(&dbv); return res; } else { return 0; } } int FacebookProto::OnIdleChanged(WPARAM wParam, LPARAM lParam) { if (m_iStatus == ID_STATUS_INVISIBLE || m_iStatus <= ID_STATUS_OFFLINE) return 0; bool bIdle = (lParam & IDF_ISIDLE) != 0; bool bPrivacy = (lParam & IDF_PRIVACY) != 0; if (facy.is_idle_ && !bIdle) { facy.is_idle_ = false; SetStatus(m_iDesiredStatus); } else if (!facy.is_idle_ && bIdle && !bPrivacy && m_iDesiredStatus != ID_STATUS_INVISIBLE) { facy.is_idle_ = true; SetStatus(ID_STATUS_IDLE); } return 0; } ////////////////////////////////////////////////////////////////////////////// int FacebookProto::OnEvent(PROTOEVENTTYPE event, WPARAM wParam, LPARAM lParam) { switch(event) { case EV_PROTO_ONLOAD: return OnModulesLoaded(wParam,lParam); case EV_PROTO_ONEXIT: return OnPreShutdown(wParam,lParam); case EV_PROTO_ONOPTIONS: return OnOptionsInit(wParam,lParam); case EV_PROTO_ONCONTACTDELETED: return OnContactDeleted(wParam,lParam); } return 1; } ////////////////////////////////////////////////////////////////////////////// // EVENTS INT_PTR FacebookProto::SvcCreateAccMgrUI(WPARAM wParam, LPARAM lParam) { return (int)CreateDialogParam(g_hInstance,MAKEINTRESOURCE(IDD_FACEBOOKACCOUNT), (HWND)lParam, FBAccountProc, (LPARAM)this); } int FacebookProto::OnModulesLoaded(WPARAM wParam, LPARAM lParam) { // Register group chat GCREGISTER gcr = {sizeof(gcr)}; gcr.dwFlags = 0; //GC_ACKMSG; gcr.pszModule = m_szModuleName; gcr.pszModuleDispName = m_szModuleName; gcr.iMaxText = FACEBOOK_MESSAGE_LIMIT; gcr.nColors = 0; gcr.pColors = NULL; CallService(MS_GC_REGISTER,0,reinterpret_cast(&gcr)); return 0; } int FacebookProto::OnPreShutdown(WPARAM wParam, LPARAM lParam) { SetStatus(ID_STATUS_OFFLINE); return 0; } int FacebookProto::OnOptionsInit(WPARAM wParam, LPARAM lParam) { OPTIONSDIALOGPAGE odp = {sizeof(odp)}; odp.hInstance = g_hInstance; odp.ptszTitle = m_tszUserName; odp.dwInitParam = LPARAM(this); odp.flags = ODPF_BOLDGROUPS | ODPF_TCHAR | ODPF_DONTTRANSLATE; odp.position = 271828; odp.ptszGroup = LPGENT("Network"); odp.ptszTab = LPGENT("Account"); odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPTIONS); odp.pfnDlgProc = FBOptionsProc; Options_AddPage(wParam, &odp); odp.position = 271829; odp.ptszTab = LPGENT("Events"); odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPTIONS_EVENTS); odp.pfnDlgProc = FBEventsProc; Options_AddPage(wParam, &odp); odp.position = 271830; odp.ptszTab = LPGENT("Advanced"); odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPTIONS_ADVANCED); odp.pfnDlgProc = FBOptionsAdvancedProc; Options_AddPage(wParam, &odp); return 0; } int FacebookProto::OnToolbarInit(WPARAM, LPARAM) { TTBButton ttb = { sizeof(ttb) }; ttb.dwFlags = TTBBF_SHOWTOOLTIP | TTBBF_VISIBLE; char service[100]; mir_snprintf(service, sizeof(service), "%s%s", m_szModuleName, "/Mind"); ttb.pszService = service; ttb.pszTooltipUp = ttb.name = LPGEN("Share status..."); ttb.hIconHandleUp = Skin_GetIconByHandle(GetIconHandle("mind")); TopToolbar_AddButton(&ttb); return 0; } INT_PTR FacebookProto::OnMind(WPARAM wParam, LPARAM lParam) { if (!isOnline()) return 1; HANDLE hContact = reinterpret_cast(wParam); // TODO: why isn't wParam == 0 when is status menu moved to main menu? if (wParam != 0 && !IsMyContact(hContact)) return 1; wall_data *wall = new wall_data(); wall->user_id = ptrA(getStringA(hContact, FACEBOOK_KEY_ID)); wall->isPage = false; if (wall->user_id == facy.self_.user_id) { wall->title = _tcsdup(TranslateT("Own wall")); } else wall->title = getTStringA(hContact, FACEBOOK_KEY_NAME); post_status_data *data = new post_status_data(this, wall); if (wall->user_id == facy.self_.user_id) { for (std::map::iterator iter = facy.pages.begin(); iter != facy.pages.end(); ++iter) { data->walls.push_back(new wall_data(iter->first, mir_utf8decodeT(iter->second.c_str()), true)); } } HWND hDlg = CreateDialogParam(g_hInstance, MAKEINTRESOURCE(IDD_MIND), (HWND)0, FBMindProc, reinterpret_cast(data)); ShowWindow(hDlg, SW_SHOW); return 0; } INT_PTR FacebookProto::CheckNewsfeeds(WPARAM, LPARAM) { if (!isOffline()) { facy.client_notify(TranslateT("Loading newsfeeds...")); facy.feeds(); } return 0; } INT_PTR FacebookProto::CheckFriendRequests(WPARAM, LPARAM) { if (!isOffline()) { facy.client_notify(TranslateT("Checking friend requests...")); ProcessFriendRequests(NULL); } return 0; } INT_PTR FacebookProto::RefreshBuddyList(WPARAM, LPARAM) { if (!isOffline()) { facy.client_notify(TranslateT("Refreshing buddy list...")); facy.buddy_list(); } return 0; } INT_PTR FacebookProto::VisitProfile(WPARAM wParam,LPARAM lParam) { HANDLE hContact = reinterpret_cast(wParam); // TODO: why isn't wParam == 0 when is status menu moved to main menu? if (wParam != 0 && !IsMyContact(hContact)) return 1; std::string url = FACEBOOK_URL_PROFILE; ptrA val(getStringA(hContact, "Homepage")); if (val != NULL) { // Homepage link already present, get it url = val; } else { // No homepage link, create and save it val = getStringA(hContact, FACEBOOK_KEY_ID); url += val; setString(hContact, "Homepage", url.c_str()); } OpenUrl(url); return 0; } INT_PTR FacebookProto::VisitFriendship(WPARAM wParam,LPARAM lParam) { HANDLE hContact = reinterpret_cast(wParam); if (wParam == 0 || !IsMyContact(hContact)) return 1; ptrA id(getStringA(hContact, FACEBOOK_KEY_ID)); std::string url = FACEBOOK_URL_PROFILE; url += facy.self_.user_id; url += "&and=" + std::string(id); OpenUrl(url); return 0; } INT_PTR FacebookProto::Poke(WPARAM wParam,LPARAM lParam) { if (wParam == NULL || isOffline()) return 1; HANDLE hContact = reinterpret_cast(wParam); ptrA id(getStringA(hContact, FACEBOOK_KEY_ID)); if (id == NULL) return 1; ForkThread(&FacebookProto::SendPokeWorker, new std::string(id)); return 0; } INT_PTR FacebookProto::CancelFriendship(WPARAM wParam,LPARAM lParam) { if (wParam == NULL || isOffline()) return 1; bool deleting = (lParam == 1); HANDLE hContact = reinterpret_cast(wParam); // Ignore groupchats and, if deleting, also not-friends if (isChatRoom(hContact) || (deleting && getByte(hContact, FACEBOOK_KEY_CONTACT_TYPE, 0) != CONTACT_FRIEND)) return 0; ptrT tname(getTStringA(hContact, FACEBOOK_KEY_NAME)); if (tname == NULL) tname = getTStringA(hContact, FACEBOOK_KEY_ID); TCHAR tstr[256]; mir_sntprintf(tstr,SIZEOF(tstr),TranslateT("Do you want to cancel your friendship with '%s'?"), tname); if (MessageBox(0, tstr, m_tszUserName, MB_ICONWARNING | MB_YESNO | MB_DEFBUTTON2) == IDYES) { ptrA id(getStringA(hContact, FACEBOOK_KEY_ID)); if (id == NULL) return 1; std::string *val = new std::string(id); if (deleting) { facebook_user *fbu = facy.buddies.find(*val); if (fbu != NULL) fbu->handle = NULL; } ForkThread(&FacebookProto::DeleteContactFromServer, val); } return 0; } INT_PTR FacebookProto::RequestFriendship(WPARAM wParam,LPARAM lParam) { if (wParam == NULL || isOffline()) return 1; HANDLE hContact = reinterpret_cast(wParam); ptrA id(getStringA(hContact, FACEBOOK_KEY_ID)); if (id == NULL) return 1; ForkThread(&FacebookProto::AddContactToServer, new std::string(id)); return 0; } INT_PTR FacebookProto::ApproveFriendship(WPARAM wParam,LPARAM lParam) { if (wParam == NULL || isOffline()) return 1; HANDLE *hContact = new HANDLE(reinterpret_cast(wParam)); ForkThread(&FacebookProto::ApproveContactToServer, hContact); return 0; } INT_PTR FacebookProto::OnCancelFriendshipRequest(WPARAM wParam,LPARAM lParam) { if (wParam == NULL || isOffline()) return 1; HANDLE *hContact = new HANDLE(reinterpret_cast(wParam)); ForkThread(&FacebookProto::CancelFriendsRequest, hContact); return 0; } HANDLE FacebookProto::HContactFromAuthEvent(HANDLE hEvent) { DWORD body[2]; DBEVENTINFO dbei = { sizeof(dbei) }; dbei.cbBlob = sizeof(DWORD)*2; dbei.pBlob = (PBYTE)&body; if (db_event_get(hEvent, &dbei)) return INVALID_HANDLE_VALUE; if (dbei.eventType != EVENTTYPE_AUTHREQUEST) return INVALID_HANDLE_VALUE; if (strcmp(dbei.szModule, m_szModuleName)) return INVALID_HANDLE_VALUE; return DbGetAuthEventContact(&dbei); } void FacebookProto::OpenUrl(std::string url) { std::string::size_type pos = url.find(FACEBOOK_SERVER_DOMAIN); bool isFacebookUrl = (pos != std::string::npos); bool isRelativeUrl = (url.substr(0, 4) != "http"); if (isFacebookUrl || isRelativeUrl) { // Make realtive url if (!isRelativeUrl) { url = url.substr(pos + strlen(FACEBOOK_SERVER_DOMAIN)); // Strip eventual port pos = url.find("/"); if (pos != std::string::npos && pos != 0) url = url.substr(pos); } // Make absolute url bool useHttps = getByte(FACEBOOK_KEY_FORCE_HTTPS, 1) > 0; url = (useHttps ? HTTP_PROTO_SECURE : HTTP_PROTO_REGULAR) + facy.get_server_type() + url; } ptrT data( mir_utf8decodeT(url.c_str())); CallService(MS_UTILS_OPENURL, (WPARAM)OUF_TCHAR, (LPARAM)data); } void FacebookProto::ReadNotificationWorker(void *p) { if (p == NULL) return; std::string *id = static_cast(p); std::string data = "seen=0&asyncSignal=&__dyn=&__req=a&alert_ids%5B0%5D=" + *id; data += "&fb_dtsg=" + (facy.dtsg_.length() ? facy.dtsg_ : "0"); data += "&__user=" + facy.self_.user_id; facy.flap(REQUEST_NOTIFICATIONS_READ, NULL, &data); delete p; } /** * Popup processing callback */ LRESULT CALLBACK PopupDlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { switch(message) { case WM_COMMAND: case WM_CONTEXTMENU: { // Get the plugin data (we need the Popup service to do it) popup_data *data = (popup_data *)PUGetPluginData(hwnd); if (data != NULL) { if (!data->notification_id.empty()) data->proto->ForkThread(&FacebookProto::ReadNotificationWorker, new std::string(data->notification_id)); if (message == WM_COMMAND && !data->url.empty()) data->proto->OpenUrl(data->url); } // After a click, destroy popup PUDeletePopup(hwnd); } break; case UM_FREEPLUGINDATA: { // After close, free popup_data *data = (popup_data *)PUGetPluginData(hwnd); if (data != NULL) delete data; } return FALSE; default: break; } return DefWindowProc(hwnd, message, wParam, lParam); }; /** * Popup classes initialization */ void FacebookProto::InitPopups() { POPUPCLASS ppc = { sizeof(ppc) }; ppc.flags = PCF_TCHAR; ppc.PluginWindowProc = (WNDPROC)PopupDlgProc; TCHAR desc[256]; char name[256]; // Client mir_sntprintf(desc, SIZEOF(desc), _T("%s/%s"), m_tszUserName, TranslateT("Client notifications")); mir_snprintf(name, SIZEOF(name), "%s_%s", m_szModuleName, "Client"); ppc.ptszDescription = desc; ppc.pszName = name; ppc.hIcon = Skin_GetIconByHandle(GetIconHandle("facebook")); ppc.colorBack = RGB(191, 0, 0); // red ppc.colorText = RGB(255, 255, 255); // white ppc.iSeconds = 0; popupClasses.push_back(Popup_RegisterClass(&ppc)); // Newsfeeds mir_sntprintf(desc, SIZEOF(desc), _T("%s/%s"), m_tszUserName, TranslateT("News feeds")); mir_snprintf(name, SIZEOF(name), "%s_%s", m_szModuleName, "Newsfeed"); ppc.ptszDescription = desc; ppc.pszName = name; ppc.hIcon = Skin_GetIconByHandle(GetIconHandle("newsfeed")); ppc.colorBack = RGB(255, 255, 255); // white ppc.colorText = RGB(0, 0, 0); // black ppc.iSeconds = 0; popupClasses.push_back(Popup_RegisterClass(&ppc)); // Notifications mir_sntprintf(desc, SIZEOF(desc), _T("%s/%s"), m_tszUserName, TranslateT("Notifications")); mir_snprintf(name, SIZEOF(name), "%s_%s", m_szModuleName, "Notification"); ppc.ptszDescription = desc; ppc.pszName = name; ppc.hIcon = Skin_GetIconByHandle(GetIconHandle("notification")); ppc.colorBack = RGB(59, 89, 152); // Facebook's blue ppc.colorText = RGB(255, 255, 255); // white ppc.iSeconds = 0; popupClasses.push_back(Popup_RegisterClass(&ppc)); // Others mir_sntprintf(desc, SIZEOF(desc), _T("%s/%s"), m_tszUserName, TranslateT("Other events")); mir_snprintf(name, SIZEOF(name), "%s_%s", m_szModuleName, "Other"); ppc.ptszDescription = desc; ppc.pszName = name; ppc.hIcon = Skin_GetIconByHandle(GetIconHandle("facebook")); ppc.colorBack = RGB(255, 255, 255); // white ppc.colorText = RGB(0, 0, 0); // black ppc.iSeconds = 0; popupClasses.push_back(Popup_RegisterClass(&ppc)); } /** * Hotkeys initialiation */ void FacebookProto::InitHotkeys() { char module[512]; mir_snprintf(module, sizeof(module), "%s/Mind", m_szModuleName); HOTKEYDESC hkd = { sizeof(hkd) }; hkd.dwFlags = HKD_TCHAR; hkd.ptszDescription = LPGENT("Show 'Share status' window"); hkd.pszName = "ShowMindWnd"; hkd.ptszSection = m_tszUserName; hkd.pszService = module; hkd.DefHotKey = HOTKEYCODE(HOTKEYF_ALT|HOTKEYF_EXT, 'F'); Hotkey_Register(&hkd); } /** * Sounds initialization */ void FacebookProto::InitSounds() { SkinAddNewSoundExT("Notification", m_tszUserName, LPGENT("Notification")); SkinAddNewSoundExT("NewsFeed", m_tszUserName, LPGENT("News Feed")); SkinAddNewSoundExT("OtherEvent", m_tszUserName, LPGENT("Other Event")); }