///////////////////////////////////////////////////////////////////////////////////////// // Miranda NG: the free IM client for Microsoft* Windows* // // Copyright (C) 2012-23 Miranda NG team, // Copyright (c) 2000-09 Miranda ICQ/IM project, // all portions of this codebase are copyrighted to the people // listed in contributors.txt. // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // you should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // // part of tabSRMM messaging plugin for Miranda. // // (C) 2005-2010 by silvercircle _at_ gmail _dot_ com and contributors // // Plugin configuration variables and functions. Implemented as a class // though there will always be only a single instance. #include "stdafx.h" void Chat_ModulesLoaded(); void CB_InitCustomButtons(); CGlobals PluginConfig; static TContainerSettings _cnt_default = { 0, 0, 255, CInfoPanel::DEGRADE_THRESHOLD, 60, 60, L"%n (%s)", 1, 0 }; wchar_t* CGlobals::m_default_container_name = L"default"; extern HANDLE hHookButtonPressedEvt; EXCEPTION_RECORD CGlobals::m_exRecord = { 0 }; CONTEXT CGlobals::m_exCtx = { 0 }; LRESULT CGlobals::m_exLastResult = 0; char CGlobals::m_exSzFile[MAX_PATH] = "\0"; wchar_t CGlobals::m_exReason[256] = L"\0"; int CGlobals::m_exLine = 0; bool CGlobals::m_exAllowContinue = false; ///////////////////////////////////////////////////////////////////////////////////////// // reload system values. These are read ONCE and are not allowed to change // without a restart void CGlobals::reloadSystemStartup() { ::RegisterTabCtrlClass(); CTip::registerClass(); dwThreadID = GetCurrentThreadId(); PluginConfig.g_hMenuContext = LoadMenu(g_plugin.getInst(), MAKEINTRESOURCE(IDR_TABCONTEXT)); TranslateMenu(g_hMenuContext); g_plugin.addSound("RecvMsgActive", LPGENW("Instant messages"), LPGENW("Incoming (focused window)")); g_plugin.addSound("RecvMsgInactive", LPGENW("Instant messages"), LPGENW("Incoming (unfocused window)")); g_plugin.addSound("AlertMsg", LPGENW("Instant messages"), LPGENW("Incoming (new session)")); g_plugin.addSound("SendMsg", LPGENW("Instant messages"), LPGENW("Outgoing")); g_plugin.addSound("SendError", LPGENW("Instant messages"), LPGENW("Message send error")); hCurSplitNS = LoadCursor(nullptr, IDC_SIZENS); hCurSplitWE = LoadCursor(nullptr, IDC_SIZEWE); hCurSplitSW = LoadCursor(nullptr, IDC_SIZENESW); hCurSplitWSE = LoadCursor(nullptr, IDC_SIZENWSE); HDC hScrnDC = GetDC(nullptr); m_DPIscaleX = GetDeviceCaps(hScrnDC, LOGPIXELSX) / 96.0; m_DPIscaleY = GetDeviceCaps(hScrnDC, LOGPIXELSY) / 96.0; ReleaseDC(nullptr, hScrnDC); reloadSettings(false); reloadAdv(); hookSystemEvents(); } ///////////////////////////////////////////////////////////////////////////////////////// // this runs ONCE at startup when the Modules Loaded event is fired // by the core. all plugins are loaded and ready to use. // // any initialation for 3rd party plugins must go here. void CGlobals::reloadSystemModulesChanged() { // smiley add if (ServiceExists(MS_SMILEYADD_REPLACESMILEYS)) { PluginConfig.g_SmileyAddAvail = 1; HookEvent(ME_SMILEYADD_OPTIONSCHANGED, ::SmileyAddOptionsChanged); } m_hwndClist = g_clistApi.hwndContactList; CMenuItem mi(&g_plugin); SET_UID(mi, 0x58d8dc1, 0x1c25, 0x49c0, 0xb8, 0x7c, 0xa3, 0x22, 0x2b, 0x3d, 0xf1, 0xd8); mi.position = -2000090000; mi.flags = CMIF_DEFAULT; mi.hIcolibItem = Skin_GetIconHandle(SKINICON_EVENT_MESSAGE); mi.name.a = LPGEN("&Message"); mi.pszService = MS_MSG_SENDMESSAGE; PluginConfig.m_hMenuItem = Menu_AddContactMenuItem(&mi); m_useAeroPeek = M.GetByte("useAeroPeek", 1); } ///////////////////////////////////////////////////////////////////////////////////////// // reload plugin settings on startup and runtime.Most of these setttings can be // changed while plugin is running. void CGlobals::reloadSettings(bool fReloadSkins) { m_ncm.cbSize = sizeof(NONCLIENTMETRICS); SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS), &m_ncm, 0); m_iTabNameLimit = db_get_w(0, SRMSGMOD_T, "cut_at", 15); m_bCutContactNameOnTabs = M.GetBool("cuttitle", false); m_MsgTimeout = g_plugin.getDword(SRMSGSET_MSGTIMEOUT, SRMSGDEFSET_MSGTIMEOUT); if (m_MsgTimeout < SRMSGSET_MSGTIMEOUT_MIN) m_MsgTimeout = SRMSGSET_MSGTIMEOUT_MIN; m_EscapeCloses = M.GetByte("escmode", 0); m_bAlwaysFullToolbarWidth = M.GetBool("alwaysfulltoolbar", true); m_LimitStaticAvatarHeight = M.GetDword("avatarheight", 96); m_panelHeight = (uint32_t)M.GetDword("panelheight", CInfoPanel::DEGRADE_THRESHOLD); m_MUCpanelHeight = db_get_dw(0, CHAT_MODULE, "panelheight", CInfoPanel::DEGRADE_THRESHOLD); m_bIdleDetect = M.GetBool("dimIconsForIdleContacts", true); m_smcxicon = m_smcyicon = 16; m_LangPackCP = Langpack_GetDefaultCodePage(); m_visualMessageSizeIndicator = M.GetByte("msgsizebar", 0); m_FlashOnMTN = g_plugin.getByte(SRMSGSET_SHOWTYPINGWINFLASH, SRMSGDEFSET_SHOWTYPINGWINFLASH); if (m_MenuBar == nullptr) { m_MenuBar = ::LoadMenu(g_plugin.getInst(), MAKEINTRESOURCE(IDR_MENUBAR)); TranslateMenu(m_MenuBar); } m_ipBackgroundGradient = db_get_dw(0, FONTMODULE, "ipfieldsbg", 0x62caff); if (0 == m_ipBackgroundGradient) m_ipBackgroundGradient = 0x62caff; m_ipBackgroundGradientHigh = db_get_dw(0, FONTMODULE, "ipfieldsbgHigh", 0xf0f0f0); if (0 == m_ipBackgroundGradientHigh) m_ipBackgroundGradientHigh = 0xf0f0f0; m_tbBackgroundHigh = db_get_dw(0, FONTMODULE, "tbBgHigh", 0); m_tbBackgroundLow = db_get_dw(0, FONTMODULE, "tbBgLow", 0); m_fillColor = db_get_dw(0, FONTMODULE, "fillColor", 0); if (CSkin::m_BrushFill) { ::DeleteObject(CSkin::m_BrushFill); CSkin::m_BrushFill = nullptr; } m_genericTxtColor = db_get_dw(0, FONTMODULE, "genericTxtClr", GetSysColor(COLOR_BTNTEXT)); m_cRichBorders = db_get_dw(0, FONTMODULE, "cRichBorders", 0); TContainerFlags f; f.dw = 0; f.m_bDontReport = f.m_bDontReportUnfocused = f.m_bAlwaysReportInactive = f.m_bHideTabs = f.m_bNewContainerFlags = f.m_bNoMenuBar = f.m_bInfoPanel = true; ::memcpy(&globalContainerSettings, &_cnt_default, sizeof(TContainerSettings)); globalContainerSettings.flags = f; Utils::ReadContainerSettingsFromDB(0, &globalContainerSettings); globalContainerSettings.fPrivate = false; if (fReloadSkins) Skin->setupAeroSkins(); } ///////////////////////////////////////////////////////////////////////////////////////// // reload "advanced tweaks" that can be applied w / o a restart void CGlobals::reloadAdv() { if (m_TypingSoundAdded == false) { g_plugin.addSound("SoundOnTyping", LPGENW("Other"), LPGENW("TabSRMM: typing")); m_TypingSoundAdded = true; } } const HMENU CGlobals::getMenuBar() { if (m_MenuBar == nullptr) { m_MenuBar = ::LoadMenu(g_plugin.getInst(), MAKEINTRESOURCE(IDR_MENUBAR)); TranslateMenu(m_MenuBar); } return(m_MenuBar); } ///////////////////////////////////////////////////////////////////////////////////////// // hook core events.This runs in LoadModule() // only core events and services are guaranteed to exist at this time void CGlobals::hookSystemEvents() { HookEvent(ME_SYSTEM_MODULESLOADED, ModulesLoaded); HookEvent(ME_SKIN_ICONSCHANGED, ::IconsChanged); HookEvent(ME_SKIN_ICONSCHANGED, ::IcoLibIconsChanged); HookEvent(ME_PROTO_CONTACTISTYPING, CMimAPI::TypingMessage); HookEvent(ME_PROTO_ACK, CMimAPI::ProtoAck); HookEvent(ME_SYSTEM_PRESHUTDOWN, PreshutdownSendRecv); HookEvent(ME_SYSTEM_OKTOEXIT, OkToExit); HookEvent(ME_CLIST_PREBUILDCONTACTMENU, CMimAPI::PrebuildContactMenu); HookEvent(ME_AV_AVATARCHANGED, ::AvatarChanged); HookEvent(ME_AV_MYAVATARCHANGED, ::MyAvatarChanged); } ///////////////////////////////////////////////////////////////////////////////////////// // second part of the startup initialisation.All plugins are now fully loaded int CGlobals::ModulesLoaded(WPARAM, LPARAM) { M.configureCustomFolders(); Skin->Init(true); CSkin::initAeroEffect(); for (int i = 0; i < NR_BUTTONBARICONS; i++) PluginConfig.g_buttonBarIcons[i] = nullptr; ::LoadIconTheme(); ::CreateImageList(true); ::CB_InitCustomButtons(); PluginConfig.reloadSystemModulesChanged(); ::Chat_ModulesLoaded(); ::BuildContainerMenu(); ::ModPlus_Init(); if (M.GetByte("avatarmode", -1) == -1) db_set_b(0, SRMSGMOD_T, "avatarmode", 2); PluginConfig.g_hwndHotkeyHandler = CreateWindowEx(0, L"TSHK", L"", WS_POPUP, 0, 0, 40, 40, nullptr, nullptr, g_plugin.getInst(), nullptr); CMenuItem mi(&g_plugin); SET_UID(mi, 0x9f68b822, 0xff97, 0x477d, 0xb7, 0x6d, 0xa5, 0x59, 0x33, 0x1c, 0x54, 0x40); mi.position = -500050005; mi.hIcolibItem = PluginConfig.g_iconContainer; mi.name.a = LPGEN("&Messaging settings..."); mi.pszService = MS_TABMSG_SETUSERPREFS; PluginConfig.m_UserMenuItem = Menu_AddContactMenuItem(&mi); if (SendLater::Avail) { SET_UID(mi, 0x8f32b04e, 0x314e, 0x42eb, 0x89, 0xc6, 0x56, 0x21, 0xf5, 0x1a, 0x2f, 0x22); mi.position = -500050006; mi.hIcolibItem = nullptr; mi.name.a = LPGEN("&Send later job list..."); mi.pszService = MS_TABMSG_SLQMGR; PluginConfig.m_UserMenuItem = Menu_AddMainMenuItem(&mi); } RestoreUnreadMessageAlerts(); ::RegisterFontServiceFonts(); ::CacheLogFonts(); TN_ModuleInit(); HookEvent(ME_DB_CONTACT_SETTINGCHANGED, DBSettingChanged); HookEvent(ME_DB_CONTACT_DELETED, DBContactDeleted); HookEvent(ME_DB_EVENT_ADDED, CMimAPI::MessageEventAdded); HookEvent(ME_DB_EVENT_EDITED, CMimAPI::MessageEventAdded); HookEvent(ME_FONT_RELOAD, ::FontServiceFontsChanged); HookEvent(ME_MC_ENABLED, &CContactCache::cacheUpdateMetaChanged); HookEvent(ME_MC_DEFAULTTCHANGED, MetaContactEvent); HookEvent(ME_MC_SUBCONTACTSCHANGED, MetaContactEvent); return 0; } ///////////////////////////////////////////////////////////////////////////////////////// // watches various important database settings and reacts accordingly // needed to catch status, nickname and other changes in order to update open message // sessions. int CGlobals::DBSettingChanged(WPARAM hContact, LPARAM lParam) { DBCONTACTWRITESETTING *cws = (DBCONTACTWRITESETTING *)lParam; const char *setting = cws->szSetting; if (hContact == 0) { if (!strcmp("Nick", setting)) Srmm_Broadcast(DM_OWNNICKCHANGED, 0, (LPARAM)cws->szModule); return 0; } CContactCache *c = CContactCache::getContactCache(hContact); const char *szProto = c->getProto(); if (szProto == nullptr) return 0; if (!c->isValid()) c->resetMeta(); // restart constructor // neither clist nor contact's settings -> skip if (strcmp(cws->szModule, "CList") && strcmp(cws->szModule, szProto)) return 0; if (!strcmp(cws->szModule, META_PROTO)) if (!strcmp(setting, "Nick")) // filter out this setting to avoid infinite loops while trying to obtain the most online contact return 0; HWND hwnd = Srmm_FindWindow(hContact); bool fChanged = false, fExtendedStatusChange = false; if (!strcmp(cws->szSetting, "Status")) fChanged = c->updateStatus(cws->value.wVal); fChanged |= c->updateNick(); if (strlen(setting) > 6 && strlen(setting) < 9 && !strncmp(setting, "Status", 6)) { fChanged = true; c->updateMeta(); c->updateUIN(); } if (strlen(setting) > 6 && strstr("StatusMsg,XStatusMsg,XStatusName,XStatusId,ListeningTo", setting)) { c->updateStatusMsg(setting); fExtendedStatusChange = true; } if (!strcmp(setting, "display_uid")) { c->updateUIN(); if (hwnd) PostMessage(hwnd, DM_UPDATEUIN, 0, 0); } if (hwnd != nullptr) { CMsgDialog *dat = c->getDat(); if (!strcmp(setting, "MirVer")) PostMessage(hwnd, DM_CLIENTCHANGED, 0, 0); if (dat && !strcmp(setting, "NotOnList") && (cws->value.type == DBVT_DELETED || cws->value.bVal == 0)) ((CMsgDialog*)dat)->onClick_CancelAdd(0); if (dat && (fChanged || fExtendedStatusChange)) dat->UpdateTitle(); if (fExtendedStatusChange) PostMessage(hwnd, DM_UPDATESTATUSMSG, 0, 0); if (fChanged) { if (dat && c->getStatus() == ID_STATUS_OFFLINE) // clear typing notification in the status bar when contact goes offline dat->ClearTyping(); PostMessage(PluginConfig.g_hwndHotkeyHandler, DM_LOGSTATUSCHANGE, MAKELONG(c->getStatus(), c->getOldStatus()), (LPARAM)c); } } return 0; } ///////////////////////////////////////////////////////////////////////////////////////// // event fired when a contact has been deleted. Make sure to close its message session int CGlobals::DBContactDeleted(WPARAM hContact, LPARAM) { if (hContact) { CContactCache *c = CContactCache::getContactCache(hContact); c->deletedHandler(); } return 0; } ///////////////////////////////////////////////////////////////////////////////////////// // Handle events from metacontacts protocol.Basically, just update // our contact cache and, if a message window exists, tell it to update // relevant information. int CGlobals::MetaContactEvent(WPARAM hContact, LPARAM) { if (hContact) { CContactCache *c = CContactCache::getContactCache(hContact); c->updateMeta(); CMsgDialog *pDlg = c->getDat(); if (pDlg) { pDlg->UpdateTitle(); pDlg->GetClientIcon(); ::PostMessage(pDlg->GetHwnd(), DM_UPDATEPICLAYOUT, 0, 0); InvalidateRect(pDlg->GetHwnd(), nullptr, TRUE); // force redraw } } return 0; } int CGlobals::PreshutdownSendRecv(WPARAM, LPARAM) { ::TN_ModuleDeInit(); ::CloseAllContainers(); for (auto &hContact : Contacts()) db_set_dw(hContact, SRMSGMOD_T, "messagecount", 0); ::DestroyWindow(PluginConfig.g_hwndHotkeyHandler); ::UnregisterClass(L"TSStatusBarClass", g_plugin.getInst()); ::UnregisterClass(L"SideBarClass", g_plugin.getInst()); ::UnregisterClass(L"TSTabCtrlClass", g_plugin.getInst()); ::UnregisterClass(L"RichEditTipClass", g_plugin.getInst()); ::UnregisterClass(L"TSHK", g_plugin.getInst()); return 0; } int CGlobals::OkToExit(WPARAM, LPARAM) { CWarning::destroyAll(); CMimAPI::m_shutDown = true; PluginConfig.globalContainerSettings.fPrivate = false; Utils::WriteContainerSettingsToDB(0, &PluginConfig.globalContainerSettings); return 0; } ///////////////////////////////////////////////////////////////////////////////////////// // used on startup to restore flashing tray icon if one or more messages are still "unread" struct MSavedEvent { MSavedEvent(MCONTACT _hContact, MEVENT _hEvent) : hContact(_hContact), hEvent(_hEvent) {} MEVENT hEvent; MCONTACT hContact; }; void CGlobals::RestoreUnreadMessageAlerts(void) { OBJLIST arEvents(10, NumericKeySortT); for (auto &hContact : Contacts()) { if (db_get_dw(hContact, "SendLater", "count", 0)) SendLater::addContact(hContact); if (!Contact::IsGroupChat(hContact)) { for (MEVENT hDbEvent = db_event_firstUnread(hContact); hDbEvent; hDbEvent = db_event_next(hContact, hDbEvent)) { DBEVENTINFO dbei = {}; if (db_event_get(hDbEvent, &dbei)) continue; if (Proto_GetBaseAccountName(hContact) == nullptr) continue; if (!dbei.markedRead() && dbei.eventType == EVENTTYPE_MESSAGE) if (!Srmm_FindWindow(hContact)) arEvents.insert(new MSavedEvent(hContact, hDbEvent)); } } } for (auto &e : arEvents) Srmm_AddEvent(e->hContact, e->hEvent); } void CGlobals::logStatusChange(WPARAM wParam, const CContactCache *c) { if (c == nullptr) return; CMsgDialog *dat = c->getDat(); if (dat == nullptr || !c->isValid() || dat->m_iLogMode != WANT_BUILTIN_LOG) return; MCONTACT hContact = c->getContact(); if (!g_plugin.bLogStatusChanges && !M.GetByte(hContact, "logstatuschanges", 0)) return; // don't log them if WE are logging off if (Proto_GetStatus(c->getProto()) == ID_STATUS_OFFLINE || Contact::IsGroupChat(hContact, c->getProto())) return; uint16_t wStatus = LOWORD(wParam); uint16_t wOldStatus = HIWORD(wParam); if (wStatus == wOldStatus) return; wchar_t *szOldStatus = Clist_GetStatusModeDescription(wOldStatus, 0); wchar_t *szNewStatus = Clist_GetStatusModeDescription(wStatus, 0); if (szOldStatus == nullptr || szNewStatus == nullptr) return; CMStringW text; if (wStatus == ID_STATUS_OFFLINE) text = TranslateT("signed off."); else if (wOldStatus == ID_STATUS_OFFLINE) text.Format(TranslateT("signed on and is now %s."), szNewStatus); else text.Format(TranslateT("changed status from %s to %s."), szOldStatus, szNewStatus); DB::EventInfo dbei; dbei.pBlob = (uint8_t*)T2Utf(text).detach(); dbei.cbBlob = (int)mir_strlen((char*)dbei.pBlob); dbei.flags = DBEF_UTF | DBEF_READ; dbei.eventType = EVENTTYPE_STATUSCHANGE; dbei.timestamp = time(0); dbei.szModule = (char*)c->getProto(); dat->LogEvent(dbei); }