/* FDDNotify plugin v0.0.2 for Miranda IM ______________________________________ Copyright (C) 2005 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. Description ----------- This plugin for Miranda-IM notifies user of specified events (as incoming messages, incoming files, incoming URLs or other events). This plugin is based on KeyboardNotify by me. It has many options allowing: a) To select on which events to react b) Under which conditions (eg: fullscreen mode, ScreenSaver running, workstation locked) c) To act only if the protocol receiving the event is under specified status d) For message events you can choose to be notified if the message window is open or not e) A notification feature allowing to be notified of pending events (unopened events) after specified period of time f) To select method for stopping the blinking (after x secs, if Miranda is re-attended, if Windows is re-attended, if all notified events are opened or when the notify conditions end) It was designed to be flexible and performing several different tasks. It also provides a service to allow third party plugins use its notifier abilities. Options ------- Options page Options->Plugins->Floppy Flash. Tabbed: Protocols, Rules (when) and Flashing (how). Thanks ------ - Pete for the numerous patches he sent, actively helping to improve the code and functionality - UnregistereD for great help in solving problem with Windows activity detection - Slacktarn, Sir_qwerty and Tweety for giving great help with ideas (most of the new features included in this plugin were suggested by them) and testing - The authors of AAA, PopUp+, KeyScrollNotify, original KeyboardNotify, Neweventnotify, IEView, NGEventNotify for part of their code used in this plugin. - Miranda IM developers for this amazing program - all other people from Miranda community History ------- 0.0.2.2: [!] Fixed problem while trying to detect if message window is in foreground [*] Improved ListView control handling [*] Changed the default values (for the sake of new users) [*] More code optimization. 0.0.2.1: [+] Support for Updater plugin. 0.0.2.0: [+] New 'notify when' option: while defined programs are running (just like gamerstatus) [+] Extended the API to add two new services to disable and re-enable keyboards notifications (for use by bosskey plugin). 0.0.1.2: [!] Fixed some compatibility issues with nconvers++ (thank you donatas for your help). 0.0.1.1: [!] Fixed problem with Windows' activity detection under Win9x when using other plugins that do the same [!] Fixed crash caused by incoming authorisation requests when metacontacts was enabled. 0.0.1.0: [+] Applied pete's patches (thank you very much for your great work) - Use of GetLastInputInfo when possible for detecting Windows' activity - Made Windows' mouse hooks also aware of mouse clicking - Made Miranda re-attended option react on windows restoring and ignoring mouse hovering an unfocused window - New option for message events to avoid blinking if message window is focused - Made the plugin handle metacontact's special issues [!] Use of the new message API for windows detection when possible [+] New message event option to check last message timestamp (requested by D46MD) [+] Possibility of choosing more than one flash until option at the same time [+] Possibility of selecting/unselecting protocols (requested by tweety, usuful to avoid flashing on some protocols as rss) [!] Changed behaviour of Preview button to make it independent of the rules' options. [!] Fixed Metacontacts recognition in checking and counting of pending events (thank you NirG for finding the problem) [!] Fixed problems with multiple instances of the plugin running (thank you tweety for reporting and testing). [+] New plugin API (thank you CriS for your ideas and great help) [!] Added Offline status to status check list (thank you Slaktarn for finding it). [!] Fixed problem with first message in Metacontacts recognition while checking for pending events (thank you again NirG) 0.0.0.3: [!] scriver's message window detection (thanks D46MD for your great help) [!] corrected 'flash until' checking accordingly to pete's patch (thank you for this) 0.0.0.2: [!] nconvers++'s message window detection [!] checked window detection for srmm, scriver, sramm and srmm_mod 0.0.0.1: Original version */ #define WIN32_LEAN_AND_MEAN #define _WIN32_WINNT 0x0500 #include "AggressiveOptimize.h" #include #include #include #include "fdd.h" #include "constants.h" #include "protolist.h" #include "EnumProc.h" #include "m_fddnotify.h" #include "../headers_c/newpluginapi.h" #include "../headers_c/m_database.h" #include "../headers_c/m_options.h" #include "../headers_c/m_clist.h" #include "../headers_c/m_skin.h" #include "../headers_c/m_system.h" #include "../headers_c/m_langpack.h" #include "../headers_c/m_protocols.h" #include "../headers_c/m_protosvc.h" #include "../headers_c/m_contacts.h" #include "../headers_c/m_message.h" #include "../headers_c/m_metacontacts.h" #include "../headers_c/m_utils.h" #include "../headers_c/m_updater.h" #pragma comment(lib, "advapi32.lib") #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; PLUGINLINK *pluginLink; 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; HHOOK hMirandaMouseHook = NULL; HHOOK hMirandaKeyBoardHook = NULL; HHOOK hMirandaWndProcHook = NULL; UINT hReminderTimer = 0; #pragma data_seg("Shared") HHOOK hMouseHook = NULL; HHOOK hKeyBoardHook = NULL; DWORD dwLastInput = 0; POINT lastGlobalMousePos = {0, 0}; #pragma data_seg() #pragma comment(linker, "/section:Shared,rws") 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 bFlashIfMsgOlder; WORD wSecondsOlder; BYTE bFlashUntil; WORD wBlinksNumber; BYTE bMirandaOrWindows; WORD wStatusMap; WORD wReminderCheck; WORD wStartDelay; WORD wBlinksNumber; BYTE bFlashSpeed; BYTE bFlashIfMsgWinNotTop; 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; PLUGININFO pluginInfo={ sizeof(PLUGININFO), "FDD Notify", PLUGIN_MAKE_VERSION(0,0,2,2), "Flashes your floppy LED when an event has arrived", "TioDuke", "tioduke@yahoo.ca", "© 2005 TioDuke", "http://www.miranda-im.org/download/", 0, //not transient 0 //doesn't replace anything built-in }; 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 *); char *NumberToDBSetting(const char *, int); // Restores the three LEDs to the state // that was previously saved with SaveLEDState() // If no state is currently saved, this function // will do nothing. static void RestoreLEDState() { ToggleFddLight(0, 0); } BOOL isMetaContactsSubContact(HANDLE hMetaContact, HANDLE hContact) { char *szProto = (char *)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)hMetaContact, 0); 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("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 (!(GetWindowLong(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)); } DBEVENTINFO createMsgEventInfo(HANDLE hContact) { DBEVENTINFO einfo = {0}; einfo.cbSize = sizeof(einfo); einfo.eventType = EVENTTYPE_MESSAGE; einfo.szModule = (char *)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)hContact, 0); 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 checkProtocol(char *szProto) { int i; if (!szProto) return FALSE; for(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) { HANDLE hSubContact; if (szMetaProto && bMetaProtoEnabled && szProto && !strcmp(szMetaProto, szProto)) if (hSubContact = (HANDLE)CallService(MS_MC_GETMOSTONLINECONTACT, (WPARAM)hContact, 0)) szProto = (char *)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)hSubContact, 0); return checkProtocol(szProto); } 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)) return TRUE; } return FALSE; } static void FlashThreadFunction() { BOOL bEvent = FALSE; DWORD dwEventStarted, dwFlashStarted; BYTE data=0; while (TRUE) { GetAsyncKeyState(VK_PAUSE); // empty Pause'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) { 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())) break; data = !data; ToggleFddLight(0, data); // Wait for exit event if (WaitForSingleObject(hExitEvent, nWaitDelay) == WAIT_OBJECT_0) return; } RestoreLEDState(); 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) { if (szMetaProto && bMetaProtoEnabled && hContact) { HANDLE hMetaContact = (HANDLE)DBGetContactSettingDword(hContact, szMetaProto, "Handle", 0); if (hMetaContact && isMetaContactsSubContact(hMetaContact, hContact)) return FALSE; } return metaCheckProtocol(szProto, hContact); } BOOL checkStatus(char *szProto) { if(!szProto) return checkGlobalStatus(); return isStatusEnabled(CallProtoService(szProto, PS_GETSTATUS, 0, 0)); } 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) && checkNotifyOptions() && checkStatus(einfo.szModule)) SetEvent(hFlashEvent); } return 0; } // ** // ** Checks for pending events. If it finds any, it pings the FlashThread to keep the LED flashing. // ** static VOID CALLBACK ReminderTimer(HWND hwnd, UINT message, UINT idEvent, DWORD dwTime) { int nIndex; CLISTEVENT *pCLEvent; 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) && checkNotifyOptions() && checkStatus(einfo.szModule)) { SetEvent(hFlashEvent); return; } } } // Support for third-party plugins and mBot's scripts static int EnableService(WPARAM wParam, LPARAM lParam) { bFlashingEnabled = TRUE; return 0; } static int DisableService(WPARAM wParam, LPARAM lParam) { bFlashingEnabled = FALSE; return 0; } static int StartBlinkService(WPARAM wParam, LPARAM lParam) { nExternCount += (unsigned int)wParam; if (bFlashOnOther && checkNotifyOptions() && checkGlobalStatus()) SetEvent(hFlashEvent); return 0; } static int EventsWereOpenedService(WPARAM wParam, LPARAM lParam) { if ((unsigned int)wParam > nExternCount) nExternCount = 0; else nExternCount -= (unsigned int)wParam; return 0; } static int IsFlashingActiveService(WPARAM wParam, LPARAM lParam) { return bReminderDisabled; } void createProcessList(void) { DBVARIANT dbv; unsigned int i, count; count = (unsigned int)DBGetContactSettingWord(NULL, FDDMODULE, "processcount", 0); ProcessList.count = 0; ProcessList.szFileName = (char **)malloc(count * sizeof(char *)); if (ProcessList.szFileName) { for(i=0; i < count; i++) if (DBGetContactSetting(NULL, FDDMODULE, NumberToDBSetting("process", i), &dbv)) ProcessList.szFileName[i] = NULL; else { ProcessList.szFileName[i] = (char *)malloc(strlen(dbv.pszVal) + 1); if (ProcessList.szFileName[i]) strcpy(ProcessList.szFileName[i], dbv.pszVal); 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) { int i; bFlashOnMsg = DBGetContactSettingByte(NULL, FDDMODULE, "onmsg", DEF_SETTING_ONMSG); bFlashOnURL = DBGetContactSettingByte(NULL, FDDMODULE, "onurl", DEF_SETTING_ONURL); bFlashOnFile = DBGetContactSettingByte(NULL, FDDMODULE, "onfile", DEF_SETTING_ONFILE); bFlashOnOther = DBGetContactSettingByte(NULL, FDDMODULE, "onother", DEF_SETTING_OTHER); bFullScreenMode = DBGetContactSettingByte(NULL, FDDMODULE, "fscreenmode", DEF_SETTING_FSCREEN); bScreenSaverRunning = DBGetContactSettingByte(NULL, FDDMODULE, "ssaverrunning", DEF_SETTING_SSAVER); bWorkstationLocked = (bWindowsNT ? DBGetContactSettingByte(NULL, FDDMODULE, "wstationlocked", DEF_SETTING_LOCKED):0); bProcessesAreRunning = DBGetContactSettingByte(NULL, FDDMODULE, "procsrunning", DEF_SETTING_PROCS); bWorkstationActive = DBGetContactSettingByte(NULL, FDDMODULE, "wstationactive", DEF_SETTING_ACTIVE); bFlashIfMsgOpen = DBGetContactSettingByte(NULL, FDDMODULE, "ifmsgopen", DEF_SETTING_IFMSGOPEN); bFlashIfMsgWinNotTop = DBGetContactSettingByte(NULL, FDDMODULE, "ifmsgnottop", DEF_SETTING_IFMSGNOTTOP); bFlashIfMsgOlder = DBGetContactSettingByte(NULL, FDDMODULE, "ifmsgolder", DEF_SETTING_IFMSGOLDER); wSecondsOlder = DBGetContactSettingWord(NULL, FDDMODULE, "secsolder", DEF_SETTING_SECSOLDER); bFlashUntil = DBGetContactSettingByte(NULL, FDDMODULE, "funtil", DEF_SETTING_FLASHUNTIL); wBlinksNumber = DBGetContactSettingWord(NULL, FDDMODULE, "nblinks", DEF_SETTING_NBLINKS); bMirandaOrWindows = DBGetContactSettingByte(NULL, FDDMODULE, "mirorwin", DEF_SETTING_MIRORWIN); wStatusMap = DBGetContactSettingWord(NULL, FDDMODULE, "status", DEF_SETTING_STATUS); wReminderCheck = DBGetContactSettingWord(NULL, FDDMODULE, "remcheck", DEF_SETTING_CHECKTIME); wStartDelay = DBGetContactSettingWord(NULL, FDDMODULE, "sdelay", DEF_SETTING_STARTDELAY); bFlashSpeed = DBGetContactSettingByte(NULL, FDDMODULE, "speed", DEF_SETTING_FLASHSPEED); switch (bFlashSpeed) { case 0: nWaitDelay = 1500; break; case 1: nWaitDelay = 1000; break; case 2: nWaitDelay = 0750; break; case 3: nWaitDelay = 0500; break; case 4: nWaitDelay = 0250; break; default: nWaitDelay = 0150; break; } for(i=0; i < ProtoList.protoCount; i++) if (ProtoList.protoInfo[i].visible) ProtoList.protoInfo[i].enabled = DBGetContactSettingByte(NULL, FDDMODULE, ProtoList.protoInfo[i].szProto, DEF_SETTING_PROTOCOL); 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 createProtocolList(void) { int i; PROTOCOLDESCRIPTOR **proto; if (ServiceExists(MS_MC_GETPROTOCOLNAME)) szMetaProto = (char *)CallService(MS_MC_GETPROTOCOLNAME, 0, 0); CallService(MS_PROTO_ENUMPROTOCOLS, (WPARAM)&ProtoList.protoCount, (LPARAM)&proto); ProtoList.protoInfo = (PROTOCOL_INFO *)malloc(ProtoList.protoCount * sizeof(PROTOCOL_INFO)); if (!ProtoList.protoInfo) ProtoList.protoCount = 0; else for(i=0; i < ProtoList.protoCount; i++) { ProtoList.protoInfo[i].szProto = (char *)malloc(strlen(proto[i]->szName) + 1); if (!ProtoList.protoInfo[i].szProto) { ProtoList.protoInfo[i].enabled = FALSE; ProtoList.protoInfo[i].visible = FALSE; } else { strcpy(ProtoList.protoInfo[i].szProto, proto[i]->szName); ProtoList.protoInfo[i].enabled = FALSE; if (proto[i]->type != PROTOTYPE_PROTOCOL) ProtoList.protoInfo[i].visible = FALSE; else if (szMetaProto && !strcmp(proto[i]->szName, szMetaProto)) ProtoList.protoInfo[i].visible = FALSE; else ProtoList.protoInfo[i].visible = TRUE; } } } char *getAbsoluteProfileName(char *absoluteProfileName, size_t maxLen) { char profilePath[MAX_PATH+1], profileName[MAX_PATH+1]; profilePath[0] = profileName[0] = '\0'; CallService(MS_DB_GETPROFILEPATH, MAX_PATH, (LPARAM)profilePath); CallService(MS_DB_GETPROFILENAME, MAX_PATH, (LPARAM)profileName); mir_snprintf(absoluteProfileName, maxLen, "%s\\%s", profilePath, profileName); return absoluteProfileName; } // 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(char *prefixName, size_t maxLen) { size_t len; char profileName[MAX_PATH+1], *str; getAbsoluteProfileName(profileName, MAX_PATH); while (str = strchr(profileName, '\\')) *str = '/'; if ((len = strlen(profileName)) <= maxLen) strcpy(prefixName, profileName); else { str = profileName + len - maxLen / 2; mir_snprintf(prefixName, maxLen / 2, "%s", profileName); strcat(prefixName, str); } } // ** // ** Everything below is just Miranda init/uninit stuff // ** static int ModulesLoaded(WPARAM wParam,LPARAM lParam) { char eventPrefix[MAX_PATH+1], eventName[MAX_PATH+1]; LoadProcsLibrary(); if (bWindowsNT && dWinVer >= 5) MyGetLastInputInfo = (BOOL (WINAPI *)(PLASTINPUTINFO)) GetProcAddress(GetModuleHandle("user32"), "GetLastInputInfo"); else MyGetLastInputInfo = NULL; createProtocolList(); LoadSettings(); // Create some synchronisation objects createEventPrefix(eventPrefix, MAX_PATH - 14); mir_snprintf(eventName, sizeof(eventName), "%s/FddFlashEvent", eventPrefix); hFlashEvent = CreateEvent(NULL, FALSE, FALSE, eventName); mir_snprintf(eventName, sizeof(eventName), "%s/FddExitEvent", 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_FDDNOTIFY_ENABLE, EnableService); hDisableService = CreateServiceFunction(MS_FDDNOTIFY_DISABLE, DisableService); hStartBlinkService = CreateServiceFunction(MS_FDDNOTIFY_STARTBLINK, StartBlinkService); hEventsOpenedService = CreateServiceFunction(MS_FDDNOTIFY_EVENTSOPENED, EventsWereOpenedService); hFlashingEventService = CreateServiceFunction(MS_FDDNOTIFY_FLASHINGACTIVE, IsFlashingActiveService); if (ServiceExists("DBEditorpp/RegisterSingleModule")) CallService("DBEditorpp/RegisterSingleModule", (WPARAM)FDDMODULE, 0); if (ServiceExists(MS_UPDATE_REGISTERFL)) CallService(MS_UPDATE_REGISTERFL, (WPARAM)2070, (LPARAM)&pluginInfo); return 0; } BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) { hInst = hinstDLL; return TRUE; } __declspec(dllexport) PLUGININFO* MirandaPluginInfo(DWORD mirandaVersion) { return &pluginInfo; } int __declspec(dllexport) Load(PLUGINLINK *link) { pluginLink = link; GetWindowsVersion(); if (!OpenFddDevice()) { MessageBox(NULL, Translate("Could not open PortTalk device. Check installation of the driver."), Translate("Error"), MB_OK | MB_ICONSTOP ); return 0; } hModulesLoaded = HookEvent(ME_SYSTEM_MODULESLOADED, ModulesLoaded); return 0; } void destroyProtocolList(void) { int i; for(i=0; i < ProtoList.protoCount; i++) if (ProtoList.protoInfo[i].szProto) free(ProtoList.protoInfo[i].szProto); ProtoList.protoCount = 0; if (ProtoList.protoInfo) free(ProtoList.protoInfo); } int __declspec(dllexport) Unload(void) { UnhookWindowsHooks(); 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); // Wait for thread to exit SetEvent(hExitEvent); WaitForSingleObject(hThread, INFINITE); RestoreLEDState(); CloseFddDevice(); 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) { 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) 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) 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); } //===================== Check Window Message function ===================== // Took this snippet of code from "EventNotify" by micron-x, thx *g* // and updated with NGEventNotify and pete's patch // checks if the message-dialog window is already opened and returns: // NULL - No window found // Handle to the parent window HWND findMessageWindow(HANDLE hContact) { HWND hwnd; char newtitle[256]; char *szProto, *contactName, *szStatus; CONTACTINFO ci = {0}; szProto = (char *)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)hContact, 0); contactName = (char *)CallService(MS_CLIST_GETCONTACTDISPLAYNAME, (WPARAM)hContact, 0); szStatus = (char *)CallService(MS_CLIST_GETSTATUSMODEDESCRIPTION, szProto==NULL?ID_STATUS_OFFLINE:DBGetContactSettingWord(hContact, szProto, "Status", ID_STATUS_OFFLINE), 0); mir_snprintf(newtitle, sizeof(newtitle), "%s (%s): %s", contactName, szStatus, Translate("Message Received")); if(hwnd = FindWindow(NULL, newtitle)) return hwnd; mir_snprintf(newtitle, sizeof(newtitle), "%s %s", contactName, szStatus); if(hwnd = FindWindow(NULL, newtitle)) return hwnd; mir_snprintf(newtitle, sizeof(newtitle), "%s (%s): %s", contactName, szStatus, Translate("Message Session")); if(hwnd = FindWindow(NULL, newtitle)) return hwnd; mir_snprintf(newtitle, sizeof(newtitle), "%s (%s): %s", contactName, szStatus, Translate("Message Session is typing...")); if(hwnd = FindWindow(NULL, newtitle)) return hwnd; // search for the nconvers++ message window that uses the UIN ci.cbSize = sizeof(CONTACTINFO); ci.dwFlag = CNF_UNIQUEID; ci.hContact = hContact; if (!CallService(MS_CONTACT_GETCONTACTINFO, 0, (LPARAM)&ci)) { switch(ci.type) { case CNFT_BYTE: mir_snprintf(newtitle, sizeof(newtitle), "%s (%d) %s", contactName, ci.bVal, szStatus); break; case CNFT_WORD: mir_snprintf(newtitle, sizeof(newtitle), "%s (%d) %s", contactName, ci.wVal, szStatus); break; case CNFT_DWORD: mir_snprintf(newtitle, sizeof(newtitle), "%s (%d) %s", contactName, ci.dVal, szStatus); break; case CNFT_ASCIIZ: mir_snprintf(newtitle, sizeof(newtitle), "%s (%s) %s", contactName, ci.pszVal, szStatus); break; } if(hwnd = FindWindow(NULL, newtitle)) return hwnd; } return NULL; } BOOL CheckMsgWnd(HANDLE hContact, BOOL *focus) { if (ServiceExists(MS_MSG_GETWINDOWDATA)) { // use the new Window 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; } } else { // old way: find it by using the window class & title HWND hwnd; if(hwnd = findMessageWindow(hContact)) { *focus = hwnd==GetForegroundWindow(); return TRUE; } } *focus = FALSE; return FALSE; }