/* KeyboardNotify plugin v1.5 for Miranda IM _________________________________________ Copyright (C) 2002,2003 Martin Öberg Copyright (C) 2004 Std Copyright (C) 2005,2006 TioDuke (tioduke@yahoo.ca) 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. */ #include "Common.h" #define NCONVERS_BLINKID ((HANDLE)123456) //nconvers' random identifier used to flash an icon for "incoming message" on contact list #ifndef SPI_GETSCREENSAVERRUNNING #define SPI_GETSCREENSAVERRUNNING 114 #endif #ifndef WM_XBUTTONDBLCLK #define WM_XBUTTONDBLCLK 0x020D #endif #ifndef WM_NCXBUTTONDBLCLK #define WM_NCXBUTTONDBLCLK 0x00AD #endif HINSTANCE hInst; int hLangpack; DWORD IDThread = 0; HANDLE hThread = NULL; HANDLE hFlashEvent; HANDLE hExitEvent; HANDLE hModulesLoaded = NULL; HANDLE hMsgEventHook = NULL; HANDLE hOptionsInitialize = NULL; HANDLE hEnableService = NULL; HANDLE hDisableService = NULL; HANDLE hStartBlinkService = NULL; HANDLE hEventsOpenedService = NULL; HANDLE hFlashingEventService = NULL; HANDLE hNormalizeSequenceService = NULL; HHOOK hMirandaMouseHook = NULL; HHOOK hMirandaKeyBoardHook = NULL; HHOOK hMirandaWndProcHook = NULL; UINT hReminderTimer = 0; HHOOK hMouseHook = NULL; HHOOK hKeyBoardHook = NULL; BYTE bEmulateKeypresses = 0; DWORD dwLastInput = 0; POINT lastGlobalMousePos = {0, 0}; static BOOL (WINAPI * MyGetLastInputInfo)(PLASTINPUTINFO); BYTE bFlashOnMsg; BYTE bFlashOnURL; BYTE bFlashOnFile; BYTE bFlashOnOther; BYTE bFullScreenMode; BYTE bScreenSaverRunning; BYTE bWorkstationLocked; BYTE bProcessesAreRunning; BYTE bWorkstationActive; BYTE bFlashIfMsgOpen; BYTE bFlashIfMsgWinNotTop; BYTE bFlashIfMsgOlder; WORD wSecondsOlder; BYTE bFlashUntil; WORD wBlinksNumber; BYTE bMirandaOrWindows; WORD wStatusMap; WORD wReminderCheck; BYTE bFlashLed[3]; BYTE bFlashEffect; BYTE bSequenceOrder; WORD wCustomTheme; WORD wStartDelay; BYTE bFlashSpeed; BYTE bOverride; BYTE bTrillianLedsMsg; BYTE bTrillianLedsURL; BYTE bTrillianLedsFile; BYTE bTrillianLedsOther; PROTOCOL_LIST ProtoList = {0, NULL}; PROCESS_LIST ProcessList = {0, NULL}; double dWinVer; BOOL bWindowsNT; int nWaitDelay; unsigned int nExternCount = 0; BOOL bFlashingEnabled = TRUE; BOOL bReminderDisabled = FALSE; char *szMetaProto = NULL; BYTE bMetaProtoEnabled = 0; PLUGININFOEX pluginInfo={ sizeof(PLUGININFOEX), __PLUGIN_NAME, PLUGIN_MAKE_VERSION(__MAJOR_VERSION, __MINOR_VERSION, __RELEASE_NUM, __BUILD_NUM), __DESCRIPTION, __AUTHOR, __AUTHOREMAIL, __COPYRIGHT, __AUTHORWEB, UNICODE_AWARE, //{119D7288-2050-448D-9900-D86AC70426BF} {0x119d7288, 0x2050, 0x448d, {0x99, 0x00, 0xd8, 0x6a, 0xc7, 0x04, 0x26, 0xbf}} }; int InitializeOptions(WPARAM,LPARAM); void LoadSettings(void); int HookWindowsHooks(void); int UnhookWindowsHooks(void); static LRESULT CALLBACK MouseHookFunction(int, WPARAM, LPARAM); static LRESULT CALLBACK KeyBoardHookFunction(int, WPARAM, LPARAM); static LRESULT CALLBACK MirandaMouseHookFunction(int, WPARAM, LPARAM); static LRESULT CALLBACK MirandaKeyBoardHookFunction(int, WPARAM, LPARAM); static LRESULT CALLBACK MirandaWndProcHookFunction(int, WPARAM, LPARAM); BOOL CheckMsgWnd(HANDLE, BOOL *); BOOL isMetaContactsSubContact(HANDLE hMetaContact, HANDLE hContact) { char *szProto = GetContactProto(hMetaContact); if (szProto && !strcmp(szMetaProto, szProto)) { // Safety check int i = DBGetContactSettingDword(hContact, szMetaProto, "ContactNumber", -1); if (i >= 0 && hContact == (HANDLE)CallService(MS_MC_GETSUBCONTACT, (WPARAM)hMetaContact, i)) return TRUE; } return FALSE; } BOOL checkOpenWindow(HANDLE hContact) { BOOL found, focus; if (bFlashIfMsgOpen && !bFlashIfMsgWinNotTop) return TRUE; found = CheckMsgWnd(hContact, &focus); if (!found && szMetaProto && bMetaProtoEnabled) { HANDLE hMetaContact = (HANDLE)DBGetContactSettingDword(hContact, szMetaProto, "Handle", 0); if (hMetaContact && isMetaContactsSubContact(hMetaContact, hContact)) found = CheckMsgWnd(hMetaContact, &focus); } if (!found) return TRUE; if (bFlashIfMsgOpen && !focus) return TRUE; return FALSE; } BOOL IsSaverOnNT4() { HDESK hd = OpenDesktop(_T("screen-saver"), 0, FALSE, MAXIMUM_ALLOWED); if(hd == NULL) return GetLastError()==ERROR_ACCESS_DENIED; CloseDesktop(hd); return TRUE; } BOOL isScreenSaverRunning() { BOOL screenSaverIsRunning=FALSE; if (bWindowsNT && dWinVer < 5) return IsSaverOnNT4(); SystemParametersInfo(SPI_GETSCREENSAVERRUNNING, 0, &screenSaverIsRunning, FALSE); return screenSaverIsRunning; } /* this function is from the original idle module */ BOOL isWorkstationLocked() { HDESK hd; char buf[MAX_PATH]; if (!bWindowsNT) return FALSE; hd = OpenInputDesktop(0, FALSE, MAXIMUM_ALLOWED); /* if it fails then the workstation is prolly locked anyway */ if (hd == NULL) return TRUE; GetUserObjectInformation(hd, UOI_NAME, buf, sizeof(buf), NULL); /* if we got it (hmm,) get a name */ CloseDesktop(hd); return strcmp(buf, "Winlogon")==0; } BOOL isFullScreen() { int w = GetSystemMetrics(SM_CXSCREEN); int h = GetSystemMetrics(SM_CYSCREEN); HWND hWnd = 0; while (hWnd = FindWindowEx(NULL, hWnd, NULL, NULL)) { RECT WindowRect; if (!(GetWindowLongPtr(hWnd, GWL_EXSTYLE) & WS_EX_TOPMOST)) continue; GetWindowRect(hWnd, &WindowRect); if ((w != (WindowRect.right - WindowRect.left)) || (h != (WindowRect.bottom - WindowRect.top))) continue; return TRUE; } return FALSE; } BOOL checkNotifyOptions() { BOOL fullScreenMode, screenSaverIsRunning, workstationIsLocked, processesRunning; screenSaverIsRunning = isScreenSaverRunning(); if (screenSaverIsRunning && bScreenSaverRunning) return TRUE; workstationIsLocked = isWorkstationLocked(); if (workstationIsLocked && bWorkstationLocked) return TRUE; fullScreenMode = isFullScreen() && !screenSaverIsRunning; if (fullScreenMode && bFullScreenMode) return TRUE; processesRunning = areThereProcessesRunning(); if (processesRunning && bProcessesAreRunning) return TRUE; return (!fullScreenMode && !screenSaverIsRunning && !workstationIsLocked && !processesRunning && bWorkstationActive); } BOOL isStatusEnabled(int status) { switch (status) { case ID_STATUS_OFFLINE: return wStatusMap & MAP_OFFLINE; case ID_STATUS_ONLINE: return wStatusMap & MAP_ONLINE; case ID_STATUS_AWAY: return wStatusMap & MAP_AWAY; case ID_STATUS_NA: return wStatusMap & MAP_NA; case ID_STATUS_OCCUPIED: return wStatusMap & MAP_OCCUPIED; case ID_STATUS_DND: return wStatusMap & MAP_DND; case ID_STATUS_FREECHAT: return wStatusMap & MAP_FREECHAT; case ID_STATUS_INVISIBLE: return wStatusMap & MAP_INVISIBLE; case ID_STATUS_ONTHEPHONE: return wStatusMap & MAP_ONTHEPHONE; case ID_STATUS_OUTTOLUNCH: return wStatusMap & MAP_OUTTOLUNCH; default: return FALSE; } } BOOL checkGlobalStatus() { return isStatusEnabled(CallService(MS_CLIST_GETSTATUSMODE, 0, 0)); } BOOL checkGlobalXstatus() { int protosSupporting=0, status=0; for (int i=0; i < ProtoList.protoCount; i++) { if ( !ProtoList.protoInfo[i].enabled || !ProtoList.protoInfo[i].xstatus.count) continue; protosSupporting++; // Retrieve xstatus for protocol CUSTOM_STATUS xstatus = { sizeof(CUSTOM_STATUS) }; xstatus.flags = CSSF_MASK_STATUS; xstatus.status = &status; CallProtoService(ProtoList.protoInfo[i].szProto, PS_GETCUSTOMSTATUSEX, 0, (LPARAM)&xstatus); if (ProtoList.protoInfo[i].xstatus.enabled[status]) return TRUE; } return protosSupporting == 0; } DBEVENTINFO createMsgEventInfo(HANDLE hContact) { DBEVENTINFO einfo = {0}; einfo.cbSize = sizeof(einfo); einfo.eventType = EVENTTYPE_MESSAGE; einfo.szModule = GetContactProto(hContact); return einfo; } DBEVENTINFO readEventInfo(HANDLE hDbEvent, HANDLE hContact) { DBEVENTINFO einfo = {0}; if (hDbEvent == NCONVERS_BLINKID) // we need to handle nconvers' blink event return createMsgEventInfo(hContact); einfo.cbSize = sizeof(einfo); einfo.cbBlob = 0; einfo.pBlob = NULL; CallService(MS_DB_EVENT_GET, (WPARAM)hDbEvent, (LPARAM)&einfo); return einfo; } BOOL checkIgnore(HANDLE hContact, WORD eventType) { return !IsIgnored(hContact, eventType); } BOOL checkProtocol(char *szProto) { if (!szProto) return FALSE; for(int i=0; i < ProtoList.protoCount; i++) if (ProtoList.protoInfo[i].szProto && !strcmp(ProtoList.protoInfo[i].szProto, szProto)) return ProtoList.protoInfo[i].enabled; return FALSE; } BOOL metaCheckProtocol(char *szProto, HANDLE hContact, WORD eventType) { HANDLE hSubContact=NULL; if (szMetaProto && bMetaProtoEnabled && szProto && !strcmp(szMetaProto, szProto)) if (hSubContact = (HANDLE)CallService(MS_MC_GETMOSTONLINECONTACT, (WPARAM)hContact, 0)) szProto = GetContactProto(hSubContact); return checkProtocol(szProto) && checkIgnore(hSubContact?hSubContact:hContact, eventType); } BOOL checkUnopenEvents() { int nIndex; CLISTEVENT *pCLEvent; if (nExternCount && bFlashOnOther) return TRUE; for (nIndex = 0; pCLEvent = (CLISTEVENT*)CallService(MS_CLIST_GETEVENT, -1, nIndex); nIndex++) { DBEVENTINFO einfo = readEventInfo(pCLEvent->hDbEvent, pCLEvent->hContact); if ((einfo.eventType == EVENTTYPE_MESSAGE && bFlashOnMsg) || (einfo.eventType == EVENTTYPE_URL && bFlashOnURL) || (einfo.eventType == EVENTTYPE_FILE && bFlashOnFile) || (einfo.eventType != EVENTTYPE_MESSAGE && einfo.eventType != EVENTTYPE_URL && einfo.eventType != EVENTTYPE_FILE && bFlashOnOther)) if (metaCheckProtocol(einfo.szModule, pCLEvent->hContact, einfo.eventType)) return TRUE; } return FALSE; } static void FlashThreadFunction() { BOOL bEvent = FALSE; DWORD dwEventStarted, dwFlashStarted; BYTE data, unchangedLeds; while (TRUE) { unchangedLeds = (BYTE)(LedState(VK_PAUSE) * !bFlashLed[2] + ((LedState(VK_NUMLOCK) * !bFlashLed[0])<<1) + ((LedState(VK_CAPITAL) * !bFlashLed[1])<<2)); GetAsyncKeyState(VK_PAUSE); // empty Pause/Break's keystroke buffer // Start flashing while(bEvent && bFlashingEnabled) { // Let's give the user the opportunity of finishing flashing manually :) if (GetAsyncKeyState(VK_PAUSE) & 1) break; if ((bFlashUntil & UNTIL_NBLINKS) && GetTickCount() > (dwFlashStarted + wBlinksNumber * 1000)) break; if (bFlashUntil & UNTIL_REATTENDED) { if (bMirandaOrWindows == ACTIVE_WINDOWS && MyGetLastInputInfo && !bEmulateKeypresses) { LASTINPUTINFO lii; ZeroMemory(&lii, sizeof(lii)); lii.cbSize = sizeof(lii); MyGetLastInputInfo(&lii); dwLastInput = lii.dwTime; } if (dwLastInput > dwEventStarted) break; } if ((bFlashUntil & UNTIL_EVENTSOPEN) && !checkUnopenEvents()) break; if ((bFlashUntil & UNTIL_CONDITIONS) && (!checkNotifyOptions() || !checkGlobalStatus() || !checkGlobalXstatus())) break; data = getBlinkingLeds(); ToggleKeyboardLights((BYTE)(data|unchangedLeds)); // Wait for exit event if (WaitForSingleObject(hExitEvent, nWaitDelay) == WAIT_OBJECT_0) return; } RestoreLEDState(); setFlashingSequence(); bReminderDisabled = FALSE; // Wait for new event { DWORD dwEvent; HANDLE Objects[2]; Objects[0] = hFlashEvent; Objects[1] = hExitEvent; dwEvent = WaitForMultipleObjects(2, Objects, FALSE, INFINITE); if ((dwEvent - WAIT_OBJECT_0) == 1) return; } bEvent = TRUE; bReminderDisabled = TRUE; dwEventStarted = GetTickCount(); // Wait StartDelay seconds if (wStartDelay > 0) Sleep(wStartDelay * 1000); dwFlashStarted = GetTickCount(); } } BOOL checkMsgTimestamp(HANDLE hEventCurrent, DWORD timestampCurrent) { HANDLE hEvent; if (!bFlashIfMsgOlder) return TRUE; for (hEvent=(HANDLE)CallService(MS_DB_EVENT_FINDPREV, (WPARAM)hEventCurrent, 0); hEvent; hEvent=(HANDLE)CallService(MS_DB_EVENT_FINDPREV, (WPARAM)hEvent, 0)) { DBEVENTINFO einfo = {0}; einfo.cbSize = sizeof(einfo); einfo.cbBlob = 0; einfo.pBlob = NULL; CallService(MS_DB_EVENT_GET, (WPARAM)hEvent, (LPARAM)&einfo); if ((einfo.timestamp + wSecondsOlder) <= timestampCurrent) return TRUE; if (einfo.eventType == EVENTTYPE_MESSAGE) return FALSE; } return TRUE; } BOOL contactCheckProtocol(char *szProto, HANDLE hContact, WORD eventType) { if (szMetaProto && bMetaProtoEnabled && hContact) { HANDLE hMetaContact = (HANDLE)DBGetContactSettingDword(hContact, szMetaProto, "Handle", 0); if (hMetaContact && isMetaContactsSubContact(hMetaContact, hContact)) return FALSE; } return (metaCheckProtocol(szProto, hContact, eventType)); } BOOL checkStatus(char *szProto) { if (!szProto) return checkGlobalStatus(); return isStatusEnabled(CallProtoService(szProto, PS_GETSTATUS, 0, 0)); } BOOL checkXstatus(char *szProto) { int status=0; CUSTOM_STATUS xstatus={0}; if (!szProto) return checkGlobalXstatus(); for(int i=0; i < ProtoList.protoCount; i++) if (ProtoList.protoInfo[i].szProto && !strcmp(ProtoList.protoInfo[i].szProto, szProto)) { if (!ProtoList.protoInfo[i].xstatus.count) return TRUE; // Retrieve xstatus for protocol xstatus.cbSize = sizeof(CUSTOM_STATUS); xstatus.flags = CSSF_MASK_STATUS; xstatus.status = &status; CallProtoService(ProtoList.protoInfo[i].szProto, PS_GETCUSTOMSTATUSEX, 0, (LPARAM)&xstatus); return ProtoList.protoInfo[i].xstatus.enabled[status]; } return TRUE; } // 'Pings' the FlashThread to keep the LEDs flashing. static int PluginMessageEventHook(WPARAM wParam, LPARAM lParam) { DBEVENTINFO einfo = {0}; HANDLE hContact = (HANDLE)wParam; HANDLE hEvent = (HANDLE)lParam; //get DBEVENTINFO without pBlob einfo.cbSize = sizeof(einfo); einfo.cbBlob = 0; einfo.pBlob = NULL; CallService(MS_DB_EVENT_GET, (WPARAM)hEvent, (LPARAM)&einfo); if (!(einfo.flags & DBEF_SENT)) if ((einfo.eventType == EVENTTYPE_MESSAGE && bFlashOnMsg && checkOpenWindow(hContact) && checkMsgTimestamp(hEvent, einfo.timestamp)) || (einfo.eventType == EVENTTYPE_URL && bFlashOnURL) || (einfo.eventType == EVENTTYPE_FILE && bFlashOnFile) || (einfo.eventType != EVENTTYPE_MESSAGE && einfo.eventType != EVENTTYPE_URL && einfo.eventType != EVENTTYPE_FILE && bFlashOnOther)) { if (contactCheckProtocol(einfo.szModule, hContact, einfo.eventType) && checkNotifyOptions() && checkStatus(einfo.szModule) && checkXstatus(einfo.szModule)) SetEvent(hFlashEvent); } return 0; } // ** // ** Checks for pending events. If it finds any, it pings the FlashThread to keep the LEDs flashing. // ** static VOID CALLBACK ReminderTimer(HWND hwnd, UINT message, UINT_PTR idEvent, DWORD dwTime) { int nIndex; CLISTEVENT *pCLEvent; if (!bReminderDisabled && nExternCount && bFlashOnOther) { SetEvent(hFlashEvent); return; } for (nIndex = 0; !bReminderDisabled && (pCLEvent = (CLISTEVENT*)CallService(MS_CLIST_GETEVENT, -1, nIndex)); nIndex++) { DBEVENTINFO einfo = readEventInfo(pCLEvent->hDbEvent, pCLEvent->hContact); if ((einfo.eventType == EVENTTYPE_MESSAGE && bFlashOnMsg) || (einfo.eventType == EVENTTYPE_URL && bFlashOnURL) || (einfo.eventType == EVENTTYPE_FILE && bFlashOnFile) || (einfo.eventType != EVENTTYPE_MESSAGE && einfo.eventType != EVENTTYPE_URL && einfo.eventType != EVENTTYPE_FILE && bFlashOnOther)) if (metaCheckProtocol(einfo.szModule, pCLEvent->hContact, einfo.eventType) && checkNotifyOptions() && checkStatus(einfo.szModule) && checkXstatus(einfo.szModule)) { SetEvent(hFlashEvent); return; } } } // Support for third-party plugins and mBot's scripts static INT_PTR EnableService(WPARAM wParam, LPARAM lParam) { bFlashingEnabled = TRUE; return 0; } static INT_PTR DisableService(WPARAM wParam, LPARAM lParam) { bFlashingEnabled = FALSE; return 0; } static INT_PTR StartBlinkService(WPARAM wParam, LPARAM lParam) { nExternCount += (unsigned int)wParam; if (bFlashOnOther && checkNotifyOptions() && checkGlobalStatus() && checkGlobalXstatus()) { if (lParam) useExternSequence((TCHAR *)lParam); SetEvent(hFlashEvent); } return 0; } static INT_PTR EventsWereOpenedService(WPARAM wParam, LPARAM lParam) { if ((unsigned int)wParam > nExternCount) nExternCount = 0; else nExternCount -= (unsigned int)wParam; return 0; } static INT_PTR IsFlashingActiveService(WPARAM wParam, LPARAM lParam) { if (!bReminderDisabled) return 0; return (int)getCurrentSequenceString(); } INT_PTR NormalizeSequenceService(WPARAM wParam, LPARAM lParam) { TCHAR strAux[MAX_PATH+1], *strIn = (TCHAR *)lParam; _snwprintf(strAux, MAX_PATH, _T("%s"), strIn); _snwprintf(strIn, MAX_PATH, _T("%s"), normalizeCustomString(strAux)); return (int)strIn; } // Support for Trigger plugin static void ForceEventsWereOpenedThread(void *eventMaxSeconds) { Sleep(((WORD)eventMaxSeconds) * 1000); CallService(MS_KBDNOTIFY_EVENTSOPENED, 1, 0); } void StartBlinkAction(char *flashSequence, WORD eventMaxSeconds) { DWORD threadID = 0; if (eventMaxSeconds) CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ForceEventsWereOpenedThread, (void *)eventMaxSeconds, 0, &threadID); CallService(MS_KBDNOTIFY_STARTBLINK, 1, (LPARAM)flashSequence); } void createProcessList(void) { DBVARIANT dbv; unsigned int i, count; count = (unsigned int)DBGetContactSettingWord(NULL, KEYBDMODULE, "processcount", 0); ProcessList.count = 0; ProcessList.szFileName = (TCHAR **)malloc(count * sizeof(TCHAR *)); if (ProcessList.szFileName) { for(i=0; i < count; i++) if (DBGetContactSettingTString(NULL, KEYBDMODULE, fmtDBSettingName("process%d", i), &dbv)) ProcessList.szFileName[i] = NULL; else { ProcessList.szFileName[i] = (TCHAR *)malloc(wcslen(dbv.ptszVal) + 1); if (ProcessList.szFileName[i]) wcscpy(ProcessList.szFileName[i], dbv.ptszVal); DBFreeVariant(&dbv); } ProcessList.count = count; } } void destroyProcessList(void) { unsigned int i, count; count = ProcessList.count; ProcessList.count = 0; for(i=0; i < count; i++) if (ProcessList.szFileName[i]) free(ProcessList.szFileName[i]); if (ProcessList.szFileName) free(ProcessList.szFileName); ProcessList.szFileName = NULL; } void LoadSettings(void) { bFlashOnMsg = DBGetContactSettingByte(NULL, KEYBDMODULE, "onmsg", DEF_SETTING_ONMSG); bFlashOnURL = DBGetContactSettingByte(NULL, KEYBDMODULE, "onurl", DEF_SETTING_ONURL); bFlashOnFile = DBGetContactSettingByte(NULL, KEYBDMODULE, "onfile", DEF_SETTING_ONFILE); bFlashOnOther = DBGetContactSettingByte(NULL, KEYBDMODULE, "onother", DEF_SETTING_OTHER); bFullScreenMode = DBGetContactSettingByte(NULL, KEYBDMODULE, "fscreenmode", DEF_SETTING_FSCREEN); bScreenSaverRunning = DBGetContactSettingByte(NULL, KEYBDMODULE, "ssaverrunning", DEF_SETTING_SSAVER); bWorkstationLocked = (bWindowsNT ? DBGetContactSettingByte(NULL, KEYBDMODULE, "wstationlocked", DEF_SETTING_LOCKED):0); bProcessesAreRunning = DBGetContactSettingByte(NULL, KEYBDMODULE, "procsrunning", DEF_SETTING_PROCS); bWorkstationActive = DBGetContactSettingByte(NULL, KEYBDMODULE, "wstationactive", DEF_SETTING_ACTIVE); bFlashIfMsgOpen = DBGetContactSettingByte(NULL, KEYBDMODULE, "ifmsgopen", DEF_SETTING_IFMSGOPEN); bFlashIfMsgWinNotTop = DBGetContactSettingByte(NULL, KEYBDMODULE, "ifmsgnottop", DEF_SETTING_IFMSGNOTTOP); bFlashIfMsgOlder = DBGetContactSettingByte(NULL, KEYBDMODULE, "ifmsgolder", DEF_SETTING_IFMSGOLDER); wSecondsOlder = DBGetContactSettingWord(NULL, KEYBDMODULE, "secsolder", DEF_SETTING_SECSOLDER); bFlashUntil = DBGetContactSettingByte(NULL, KEYBDMODULE, "funtil", DEF_SETTING_FLASHUNTIL); wBlinksNumber = DBGetContactSettingWord(NULL, KEYBDMODULE, "nblinks", DEF_SETTING_NBLINKS); bMirandaOrWindows = DBGetContactSettingByte(NULL, KEYBDMODULE, "mirorwin", DEF_SETTING_MIRORWIN); wStatusMap = DBGetContactSettingWord(NULL, KEYBDMODULE, "status", DEF_SETTING_STATUS); wReminderCheck = DBGetContactSettingWord(NULL, KEYBDMODULE, "remcheck", DEF_SETTING_CHECKTIME); bFlashLed[0] = !!DBGetContactSettingByte(NULL, KEYBDMODULE, "fnum", DEF_SETTING_FLASHNUM); bFlashLed[1] = !!DBGetContactSettingByte(NULL, KEYBDMODULE, "fcaps", DEF_SETTING_FLASHCAPS); bFlashLed[2] = !!DBGetContactSettingByte(NULL, KEYBDMODULE, "fscroll", DEF_SETTING_FLASHSCROLL); bFlashEffect = DBGetContactSettingByte(NULL, KEYBDMODULE, "feffect", DEF_SETTING_FLASHEFFECT); bSequenceOrder = DBGetContactSettingByte(NULL, KEYBDMODULE, "order", DEF_SETTING_SEQORDER); wCustomTheme = DBGetContactSettingWord(NULL, KEYBDMODULE, "custom", DEF_SETTING_CUSTOMTHEME); bTrillianLedsMsg = DBGetContactSettingByte(NULL, KEYBDMODULE, "ledsmsg", DEF_SETTING_LEDSMSG); bTrillianLedsURL = DBGetContactSettingByte(NULL, KEYBDMODULE, "ledsurl", DEF_SETTING_LEDSURL); bTrillianLedsFile = DBGetContactSettingByte(NULL, KEYBDMODULE, "ledsfile", DEF_SETTING_LEDSFILE); bTrillianLedsOther = DBGetContactSettingByte(NULL, KEYBDMODULE, "ledsother", DEF_SETTING_LEDSOTHER); wStartDelay = DBGetContactSettingWord(NULL, KEYBDMODULE, "sdelay", DEF_SETTING_STARTDELAY); bFlashSpeed = DBGetContactSettingByte(NULL, KEYBDMODULE, "speed", DEF_SETTING_FLASHSPEED); switch (bFlashSpeed) { case 0: nWaitDelay = 1500; break; case 1: nWaitDelay = 0750; break; case 2: nWaitDelay = 0250; break; case 3: nWaitDelay = 0150; break; case 4: nWaitDelay = 0100; break; default: nWaitDelay = 0050; break; } setFlashingSequence(); bEmulateKeypresses = DBGetContactSettingByte(NULL, KEYBDMODULE, "keypresses", DEF_SETTING_KEYPRESSES); bOverride = DBGetContactSettingByte(NULL, KEYBDMODULE, "override", DEF_SETTING_OVERRIDE); // Create hidden settings (for test button) if needed if (DBGetContactSettingByte(NULL, KEYBDMODULE, "testnum", -1) == -1) DBWriteContactSettingByte(NULL, KEYBDMODULE, "testnum", DEF_SETTING_TESTNUM); if (DBGetContactSettingByte(NULL, KEYBDMODULE, "testsecs", -1) == -1) DBWriteContactSettingByte(NULL, KEYBDMODULE, "testsecs", DEF_SETTING_TESTSECS); for(int i=0; i < ProtoList.protoCount; i++) if (ProtoList.protoInfo[i].visible) { unsigned int j; ProtoList.protoInfo[i].enabled = DBGetContactSettingByte(NULL, KEYBDMODULE, ProtoList.protoInfo[i].szProto, DEF_SETTING_PROTOCOL); for(j=0; j < ProtoList.protoInfo[i].xstatus.count; j++) ProtoList.protoInfo[i].xstatus.enabled[j] = DBGetContactSettingByte(NULL, KEYBDMODULE, fmtDBSettingName("%sxstatus%d", ProtoList.protoInfo[i].szProto, j), DEF_SETTING_XSTATUS); } if (szMetaProto) bMetaProtoEnabled = DBGetContactSettingByte(NULL, szMetaProto, "Enabled", 1); destroyProcessList(); createProcessList(); UnhookWindowsHooks(); HookWindowsHooks(); } void GetWindowsVersion(void) { OSVERSIONINFOEX osvi; BOOL bOsVersionInfoEx; ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX)); osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); if (!(bOsVersionInfoEx = GetVersionEx((OSVERSIONINFO *) &osvi))) { osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); if (!GetVersionEx((OSVERSIONINFO *)&osvi)) osvi.dwPlatformId = VER_PLATFORM_WIN32_WINDOWS; } bWindowsNT = osvi.dwPlatformId==VER_PLATFORM_WIN32_NT; dWinVer = osvi.dwMajorVersion + osvi.dwMinorVersion / 10.0; } void updateXstatusProto(PROTOCOL_INFO *protoInfo) { unsigned int i; char szServiceName[MAXMODULELABELLENGTH]; CUSTOM_STATUS xstatus={0}; mir_snprintf(szServiceName, sizeof(szServiceName), "%s%s", protoInfo->szProto, PS_GETCUSTOMSTATUSEX); if (!ServiceExists(szServiceName)) return; // Retrieve xstatus.count xstatus.cbSize = sizeof(CUSTOM_STATUS); xstatus.flags = CSSF_STATUSES_COUNT; xstatus.wParam = &(protoInfo->xstatus.count); CallProtoService(protoInfo->szProto, PS_GETCUSTOMSTATUSEX, 0, (LPARAM)&xstatus); (protoInfo->xstatus.count)++; // Don't forget about xstatus=0 (None) // Alloc and initiailize xstatus.enabled array protoInfo->xstatus.enabled = (BOOL *)malloc(protoInfo->xstatus.count * sizeof(BOOL)); if (!protoInfo->xstatus.enabled) protoInfo->xstatus.count = 0; else for(i=0; i < protoInfo->xstatus.count; i++) protoInfo->xstatus.enabled[i] = FALSE; } void createProtocolList(void) { PROTOACCOUNT **proto; if (ServiceExists(MS_MC_GETPROTOCOLNAME)) szMetaProto = (char *)CallService(MS_MC_GETPROTOCOLNAME, 0, 0); ProtoEnumAccounts(&ProtoList.protoCount, &proto); ProtoList.protoInfo = (PROTOCOL_INFO *)malloc(ProtoList.protoCount * sizeof(PROTOCOL_INFO)); if (!ProtoList.protoInfo) { ProtoList.protoCount = 0; return; } for(int i=0; i < ProtoList.protoCount; i++) { ProtoList.protoInfo[i].xstatus.count = 0; ProtoList.protoInfo[i].xstatus.enabled = NULL; ProtoList.protoInfo[i].szProto = (char *)malloc(strlen(proto[i]->szModuleName) + 1); if (!ProtoList.protoInfo[i].szProto) { ProtoList.protoInfo[i].enabled = FALSE; ProtoList.protoInfo[i].visible = FALSE; } else { strcpy(ProtoList.protoInfo[i].szProto, proto[i]->szModuleName); ProtoList.protoInfo[i].enabled = FALSE; if (szMetaProto && !strcmp(proto[i]->szModuleName, szMetaProto)) ProtoList.protoInfo[i].visible = FALSE; else { ProtoList.protoInfo[i].visible = TRUE; updateXstatusProto(&(ProtoList.protoInfo[i])); } } } } // We use the profile name to create the first part of each event name // We do so to avoid problems between different instances of the plugin concurrently running void createEventPrefix(TCHAR *prefixName, size_t maxLen) { size_t len; TCHAR profileName[MAX_PATH+1], *str; getAbsoluteProfileName(profileName, MAX_PATH); while (str = _tcschr(profileName, _T('\\'))) *str = _T('/'); if ((len = _tcslen(profileName)) <= maxLen) _tcscpy(prefixName, profileName); else { str = profileName + len - maxLen / 2; mir_sntprintf(prefixName, maxLen / 2, _T("%s"), profileName); _tcscat(prefixName, str); } } // ** // ** Everything below is just Miranda init/uninit stuff // ** static int ModulesLoaded(WPARAM wParam, LPARAM lParam) { TCHAR eventPrefix[MAX_PATH+1], eventName[MAX_PATH+1]; LoadProcsLibrary(); if (bWindowsNT && dWinVer >= 5) MyGetLastInputInfo = (BOOL (WINAPI *)(PLASTINPUTINFO)) GetProcAddress(GetModuleHandle(_T("user32")), "GetLastInputInfo"); else MyGetLastInputInfo = NULL; createProtocolList(); LoadSettings(); // Create some synchronisation objects createEventPrefix(eventPrefix, MAX_PATH - 11); mir_sntprintf(eventName, SIZEOF(eventName), _T("%s/FlashEvent"), eventPrefix); hFlashEvent = CreateEvent(NULL, FALSE, FALSE, eventName); mir_sntprintf(eventName, SIZEOF(eventName), _T("%s/ExitEvent"), eventPrefix); hExitEvent = CreateEvent(NULL, FALSE, FALSE, eventName); hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)FlashThreadFunction, NULL, 0, &IDThread); hMsgEventHook = HookEvent(ME_DB_EVENT_ADDED, PluginMessageEventHook); hOptionsInitialize = HookEvent(ME_OPT_INITIALISE, InitializeOptions); hEnableService = CreateServiceFunction(MS_KBDNOTIFY_ENABLE, EnableService); hDisableService = CreateServiceFunction(MS_KBDNOTIFY_DISABLE, DisableService); hStartBlinkService = CreateServiceFunction(MS_KBDNOTIFY_STARTBLINK, StartBlinkService); hEventsOpenedService = CreateServiceFunction(MS_KBDNOTIFY_EVENTSOPENED, EventsWereOpenedService); hFlashingEventService = CreateServiceFunction(MS_KBDNOTIFY_FLASHINGACTIVE, IsFlashingActiveService); hNormalizeSequenceService = CreateServiceFunction(MS_KBDNOTIFY_NORMALSEQUENCE, NormalizeSequenceService); RegisterAction(); if (ServiceExists("DBEditorpp/RegisterSingleModule")) CallService("DBEditorpp/RegisterSingleModule", (WPARAM)KEYBDMODULE, 0); return 0; } BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) { hInst = hinstDLL; return TRUE; } extern "C" __declspec(dllexport) PLUGININFOEX* MirandaPluginInfoEx(DWORD mirandaVersion) { return &pluginInfo; } extern "C" __declspec(dllexport) int Load(void) { mir_getLP(&pluginInfo); GetWindowsVersion(); OpenKeyboardDevice(); hModulesLoaded = HookEvent(ME_SYSTEM_MODULESLOADED, ModulesLoaded); return 0; } void destroyProtocolList(void) { for(int i=0; i < ProtoList.protoCount; i++) { if (ProtoList.protoInfo[i].szProto) free(ProtoList.protoInfo[i].szProto); if (ProtoList.protoInfo[i].xstatus.enabled) free(ProtoList.protoInfo[i].xstatus.enabled); } ProtoList.protoCount = 0; if (ProtoList.protoInfo) free(ProtoList.protoInfo); } extern "C" __declspec(dllexport) int Unload(void) { UnhookWindowsHooks(); DeInitAction(); if (hModulesLoaded) UnhookEvent(hModulesLoaded); if (hMsgEventHook) UnhookEvent(hMsgEventHook); if (hOptionsInitialize) UnhookEvent(hOptionsInitialize); if (hEnableService) DestroyServiceFunction(hEnableService); if (hDisableService) DestroyServiceFunction(hDisableService); if (hStartBlinkService) DestroyServiceFunction(hStartBlinkService); if (hEventsOpenedService) DestroyServiceFunction(hEventsOpenedService); if (hFlashingEventService) DestroyServiceFunction(hFlashingEventService); if (hNormalizeSequenceService) DestroyServiceFunction(hNormalizeSequenceService); // Wait for thread to exit SetEvent(hExitEvent); WaitForSingleObject(hThread, INFINITE); RestoreLEDState(); CloseKeyboardDevice(); UnloadProcsLibrary(); destroyProcessList(); destroyProtocolList(); return 0; } // ========================== Windows hooks ========================== int HookWindowsHooks() { if (wReminderCheck) hReminderTimer = SetTimer(NULL,0, wReminderCheck * 60000, ReminderTimer); if (bFlashUntil & UNTIL_REATTENDED) switch (bMirandaOrWindows) { case ACTIVE_WINDOWS: if (!MyGetLastInputInfo || bEmulateKeypresses) { if (hMouseHook == NULL) hMouseHook = SetWindowsHookEx(WH_MOUSE, MouseHookFunction, hInst, 0); if (hKeyBoardHook == NULL) hKeyBoardHook = SetWindowsHookEx(WH_KEYBOARD, KeyBoardHookFunction, hInst, 0); } break; case ACTIVE_MIRANDA: if (hMirandaMouseHook == NULL) hMirandaMouseHook = SetWindowsHookEx(WH_MOUSE, MirandaMouseHookFunction, NULL, GetCurrentThreadId()); if (hMirandaKeyBoardHook == NULL) hMirandaKeyBoardHook = SetWindowsHookEx(WH_KEYBOARD, MirandaKeyBoardHookFunction, NULL, GetCurrentThreadId()); if (hMirandaWndProcHook == NULL) hMirandaWndProcHook = SetWindowsHookEx(WH_CALLWNDPROC, MirandaWndProcHookFunction, NULL, GetCurrentThreadId()); } return 0; } int UnhookWindowsHooks() { if (hReminderTimer) KillTimer(NULL, hReminderTimer); if (hMouseHook) UnhookWindowsHookEx(hMouseHook); if (hKeyBoardHook) UnhookWindowsHookEx(hKeyBoardHook); if (hMirandaMouseHook) UnhookWindowsHookEx(hMirandaMouseHook); if (hMirandaKeyBoardHook) UnhookWindowsHookEx(hMirandaKeyBoardHook); if (hMirandaWndProcHook) UnhookWindowsHookEx(hMirandaWndProcHook); hReminderTimer = 0; hMouseHook = hKeyBoardHook = hMirandaMouseHook = hMirandaKeyBoardHook = hMirandaWndProcHook = NULL; return 0; } static LRESULT CALLBACK MouseHookFunction(int code, WPARAM wParam, LPARAM lParam) { if (code >= 0) { /* This should handle all mouse buttons ... */ if ((wParam >= WM_NCLBUTTONDOWN && wParam <= WM_NCXBUTTONDBLCLK && wParam != 0x00AA) || (wParam >= WM_LBUTTONDOWN && wParam <= WM_XBUTTONDBLCLK)) dwLastInput = GetTickCount(); /* ... and here it is either mouse move, hover, leave or something unexpected */ else { PMOUSEHOOKSTRUCT mouseInfo = (PMOUSEHOOKSTRUCT)lParam; POINT pt = mouseInfo->pt; if (pt.x!=lastGlobalMousePos.x || pt.y!=lastGlobalMousePos.y) { lastGlobalMousePos = pt; dwLastInput = GetTickCount(); } } } return CallNextHookEx(hMouseHook, code, wParam, lParam); } static LRESULT CALLBACK KeyBoardHookFunction(int code, WPARAM wParam, LPARAM lParam) { if (code >= 0 && (!bEmulateKeypresses || (bEmulateKeypresses && wParam != VK_NUMLOCK && wParam != VK_CAPITAL && wParam != VK_SCROLL))) dwLastInput = GetTickCount(); return CallNextHookEx(hKeyBoardHook, code, wParam, lParam); } static LRESULT CALLBACK MirandaMouseHookFunction(int code, WPARAM wParam, LPARAM lParam) { static POINT lastMousePos = {0, 0}; if (code >= 0) { /* Movement mouse messages are for some reason incoming in inactive/background window too, that is not input */ DWORD pid; GetWindowThreadProcessId(GetForegroundWindow(), &pid); if(pid == GetCurrentProcessId()) { /* This should handle all mouse buttons ... */ if ((wParam >= WM_NCLBUTTONDOWN && wParam <= WM_NCXBUTTONDBLCLK && wParam != 0x00AA) || (wParam >= WM_LBUTTONDOWN && wParam <= WM_XBUTTONDBLCLK)) dwLastInput = GetTickCount(); /* ... and here it is either mouse move, hover, leave or something unexpected */ else { PMOUSEHOOKSTRUCT mouseInfo = (PMOUSEHOOKSTRUCT)lParam; POINT pt = mouseInfo->pt; if (pt.x!=lastMousePos.x || pt.y!=lastMousePos.y) { lastMousePos = pt; dwLastInput = GetTickCount(); } } } } return CallNextHookEx(hMirandaMouseHook, code, wParam, lParam); } static LRESULT CALLBACK MirandaKeyBoardHookFunction(int code, WPARAM wParam, LPARAM lParam) { if (code >= 0 && (!bEmulateKeypresses || (bEmulateKeypresses && wParam != VK_NUMLOCK && wParam != VK_CAPITAL && wParam != VK_SCROLL))) dwLastInput = GetTickCount(); return CallNextHookEx(hMirandaKeyBoardHook, code, wParam, lParam); } static LRESULT CALLBACK MirandaWndProcHookFunction(int code, WPARAM wParam, LPARAM lParam) { if (code >= 0) { /* WM_ACTIVATEAPP with nonzero wParam means someone brought miranda to foreground, that equals to input */ PCWPSTRUCT cwpInfo = (PCWPSTRUCT)lParam; if(cwpInfo->message == WM_ACTIVATEAPP && cwpInfo->wParam) dwLastInput = GetTickCount(); } return CallNextHookEx(hMirandaWndProcHook, code, wParam, lParam); } BOOL CheckMsgWnd(HANDLE hContact, BOOL *focus) { if (ServiceExists(MS_MSG_GETWINDOWDATA)) { // use the new message API MessageWindowData mwd; MessageWindowInputData mwid; mwid.cbSize = sizeof(MessageWindowInputData); mwid.hContact = hContact; mwid.uFlags = MSG_WINDOW_UFLAG_MSG_BOTH; mwd.cbSize = sizeof(MessageWindowData); mwd.hContact = hContact; if (!CallService(MS_MSG_GETWINDOWDATA, (WPARAM)&mwid, (LPARAM)&mwd) && mwd.hwndWindow) { *focus = mwd.uState & MSG_WINDOW_STATE_FOCUS; return TRUE; } } *focus = FALSE; return FALSE; } void countUnopenEvents(int *msgCount, int *fileCount, int *urlCount, int *otherCount) { int nIndex; CLISTEVENT *pCLEvent; for (nIndex = 0; pCLEvent = (CLISTEVENT*)CallService(MS_CLIST_GETEVENT, -1, nIndex); nIndex++) { DBEVENTINFO einfo = readEventInfo(pCLEvent->hDbEvent, pCLEvent->hContact); if (metaCheckProtocol(einfo.szModule, pCLEvent->hContact, einfo.eventType)) switch (einfo.eventType) { case EVENTTYPE_MESSAGE: if (bFlashOnMsg) (*msgCount)++; break; case EVENTTYPE_URL: if (bFlashOnURL) (*urlCount)++; break; case EVENTTYPE_FILE: if (bFlashOnFile) (*fileCount)++; break; default: if (bFlashOnOther) (*otherCount)++; } } if (bFlashOnOther) (*otherCount) += nExternCount; }