/* AdvancedAutoAway Plugin for Miranda-IM (www.miranda-im.org) Copyright 2003-2006 P. Boon 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 Some code is copied from Miranda's AutoAway module */ #include "../commonstatus.h" #include "advancedautoaway.h" #include "../resource.h" #include #ifdef _DEBUG #define SECS_PER_MINUTE 20 /* speedup */ #else #define SECS_PER_MINUTE 60 /* default I believe */ #endif ///////////////////////////////////////////////////////////////////////////////////////// int CompareSettings( const TAAAProtoSetting* p1, const TAAAProtoSetting* p2 ) { return lstrcmpA( p1->szName, p2->szName ); } OBJLIST *autoAwaySettings; TAAAProtoSetting::TAAAProtoSetting( PROTOACCOUNT* pa ) { cbSize = sizeof(PROTOCOLSETTINGEX); szName = pa->szModuleName; tszAccName = pa->tszAccountName; lastStatus = status = originalStatusMode = ID_STATUS_CURRENT; szMsg = NULL; curState = ACTIVE; mStatus = FALSE; } TAAAProtoSetting::~TAAAProtoSetting() { if ( szMsg ) free( szMsg ); } ///////////////////////////////////////////////////////////////////////////////////////// extern HANDLE hStateChangedEvent; static BOOL ignoreLockKeys = FALSE; static BOOL ignoreSysKeys = FALSE; static BOOL ignoreAltCombo = FALSE; static BOOL monitorMouse = TRUE; static BOOL monitorKeyboard = TRUE; static HWND confirmDialog; static int mouseStationaryTimer; HHOOK hMirandaMouseHook = NULL; HHOOK hMirandaKeyBoardHook = NULL; #pragma data_seg("Shared") DWORD lastInput = 0; POINT lastMousePos = {0}; HHOOK hMouseHook = NULL; HHOOK hKeyBoardHook = NULL; #pragma data_seg() #pragma comment(linker, "/section:Shared,rws") DWORD lastMirandaInput = 0; static UINT_PTR hAutoAwayTimer; // prototypes extern DWORD StatusModeToProtoFlag(int status); extern int InitCommonStatus(); void LoadOptions(TAAAProtoSetting** loadSettings, BOOL override); static int HookWindowsHooks(int hookMiranda, int hookAll); static int UnhookWindowsHooks(); static LRESULT CALLBACK MouseHookFunction(int code, WPARAM wParam, LPARAM lParam); static LRESULT CALLBACK KeyBoardHookFunction(int code, WPARAM wParam, LPARAM lParam); static LRESULT CALLBACK MirandaMouseHookFunction(int code, WPARAM wParam, LPARAM lParam); static LRESULT CALLBACK MirandaKeyBoardHookFunction(int code, WPARAM wParam, LPARAM lParam); BOOL WINAPI DllMain(HINSTANCE hinstDLL,DWORD fdwReason,LPVOID lpvReserved); static VOID CALLBACK AutoAwayTimer(HWND hwnd,UINT message,UINT_PTR idEvent,DWORD dwTime); extern int AutoAwayOptInitialise(WPARAM wParam,LPARAM lParam); extern int AutoAwayMsgOptInitialise(WPARAM wParam,LPARAM lParam); extern int SetStatus(WPARAM wParam, LPARAM lParam); extern int ShowConfirmDialog(WPARAM wParam, LPARAM lParam); extern char *StatusModeToDbSetting(int status,const char *suffix); ///////////////////////////////////////////////////////////////////////////////////////// // Load from DB void LoadOptions( OBJLIST& loadSettings, BOOL override) { // if override is enabled, samesettings will be ignored (for options loading) int monitorMiranda = FALSE; // use windows hooks? int monitorAll = FALSE; // use windows hooks? if (!override) UnhookWindowsHooks(); if (hAutoAwayTimer != 0) KillTimer(NULL, hAutoAwayTimer); ignoreLockKeys = db_get_b(NULL, MODULENAME, SETTING_IGNLOCK, FALSE); ignoreSysKeys = db_get_b(NULL, MODULENAME, SETTING_IGNSYSKEYS, FALSE); ignoreAltCombo = db_get_b(NULL, MODULENAME, SETTING_IGNALTCOMBO, FALSE); monitorMouse = db_get_b(NULL, MODULENAME, SETTING_MONITORMOUSE, TRUE); monitorKeyboard = db_get_b(NULL, MODULENAME, SETTING_MONITORKEYBOARD, TRUE); lastInput = lastMirandaInput = GetTickCount(); for ( int i=0; i < loadSettings.getCount(); i++ ) { char* protoName; if ((db_get_b(NULL, MODULENAME, SETTING_SAMESETTINGS, 0)) && !override) protoName = SETTING_ALL; else protoName = loadSettings[i].szName; LoadAutoAwaySetting( loadSettings[i], protoName); if (!override) { if (loadSettings[i].optionFlags & FLAG_MONITORMIRANDA) monitorMiranda = TRUE; else if (ignoreLockKeys || ignoreSysKeys || ignoreAltCombo || (monitorMouse != monitorKeyboard)) monitorAll = TRUE; } } if (db_get_b(NULL, "Idle", "AAEnable", 0)) return; HookWindowsHooks(monitorMiranda, monitorAll); hAutoAwayTimer = SetTimer(NULL,0, db_get_w(NULL, MODULENAME, SETTING_AWAYCHECKTIMEINSECS, 5)*1000,AutoAwayTimer); } int LoadAutoAwaySetting(TAAAProtoSetting& autoAwaySetting, char* protoName) { char setting[128]; mir_snprintf(setting, sizeof(setting), "%s_OptionFlags", protoName); autoAwaySetting.optionFlags = db_get_b(NULL,MODULENAME,setting,FLAG_LV2ONINACTIVE|FLAG_RESET); mir_snprintf(setting, sizeof(setting), "%s_AwayTime", protoName); autoAwaySetting.awayTime = db_get_w(NULL,MODULENAME,setting,SETTING_AWAYTIME_DEFAULT); mir_snprintf(setting, sizeof(setting), "%s_NATime", protoName); autoAwaySetting.naTime = db_get_w(NULL,MODULENAME,setting,SETTING_NATIME_DEFAULT); mir_snprintf(setting, sizeof(setting), "%s_StatusFlags", protoName); autoAwaySetting.statusFlags = db_get_w(NULL,MODULENAME,setting, StatusModeToProtoFlag(ID_STATUS_ONLINE)|StatusModeToProtoFlag(ID_STATUS_FREECHAT)); int flags; if (db_get_b(NULL, MODULENAME, SETTING_SAMESETTINGS, 0)) flags = 0xFFFFFF; else flags = CallProtoService(protoName, PS_GETCAPS,PFLAGNUM_2,0)&~CallProtoService(protoName, PS_GETCAPS, (WPARAM)PFLAGNUM_5, 0); mir_snprintf(setting, sizeof(setting), "%s_Lv1Status", protoName); autoAwaySetting.lv1Status = db_get_w(NULL, MODULENAME, setting, (flags&StatusModeToProtoFlag(ID_STATUS_AWAY))?ID_STATUS_AWAY:ID_STATUS_OFFLINE); mir_snprintf(setting, sizeof(setting), "%s_Lv2Status", protoName); autoAwaySetting.lv2Status = db_get_w(NULL, MODULENAME, setting, (flags&StatusModeToProtoFlag(ID_STATUS_NA))?ID_STATUS_NA:ID_STATUS_OFFLINE); return 0; } static int ProcessProtoAck(WPARAM wParam,LPARAM lParam) { ACKDATA *ack = ( ACKDATA* )lParam; if ( ack->type != ACKTYPE_STATUS ) return 0; if ( ack->result != ACKRESULT_SUCCESS ) return 0; log_debugA("ProcessProtoAck: ack->szModule: %s", ack->szModule); for ( int i=0; i < autoAwaySettings->getCount(); i++ ) { log_debugA("chk: %s", (*autoAwaySettings)[i].szName); if (!strcmp((*autoAwaySettings)[i].szName, ack->szModule)) { log_debugA("ack->szModule: %s (*autoAwaySettings)[i].statusChanged: %d", ack->szModule, (*autoAwaySettings)[i].statusChanged); if (!(*autoAwaySettings)[i].statusChanged) (*autoAwaySettings)[i].mStatus = TRUE; (*autoAwaySettings)[i].statusChanged = FALSE; } } return 0; } ///////////////////////////////////////////////////////////////////////////////////////// // Account control event int OnAccChanged(WPARAM wParam,LPARAM lParam) { PROTOACCOUNT* pa = (PROTOACCOUNT*)lParam; switch( wParam ) { case PRAC_ADDED: autoAwaySettings->insert( new TAAAProtoSetting(pa)); break; case PRAC_REMOVED: for (int i=0; i < autoAwaySettings->getCount(); i++) { if ( !lstrcmpA( (*autoAwaySettings)[i].szName, pa->szModuleName )) { autoAwaySettings->remove( i ); break; } } break; } return 0; } static char* status2descr( int status ) { switch( status ) { case ACTIVE: return "ACTIVE"; case STATUS1_SET: return "STATUS1_SET"; case STATUS2_SET: return "STATUS2_SET"; case SET_ORGSTATUS: return "SET_ORGSTATUS"; case HIDDEN_ACTIVE: return "HIDDEN_ACTIVE"; } return "ERROR"; } static int changeState(TAAAProtoSetting& setting, STATES newState) { if (setting.curState == newState) return 0; setting.oldState = setting.curState; setting.curState = newState; log_debugA("%s state change: %s -> %s", setting.szName, status2descr(setting.oldState), status2descr(setting.curState)); NotifyEventHooks(hStateChangedEvent, 0, (LPARAM)(AUTOAWAYSETTING*)&setting); if ( setting.curState != SET_ORGSTATUS && setting.curState != ACTIVE && setting.statusChanged ) { /* change the awaymessage */ if (setting.szMsg != NULL) { free(setting.szMsg); setting.szMsg = NULL; } if (db_get_b(NULL, MODULENAME, StatusModeToDbSetting(setting.status, SETTING_MSGCUSTOM), FALSE)) { DBVARIANT dbv; if (!db_get_ts(NULL, MODULENAME, StatusModeToDbSetting(setting.status, SETTING_STATUSMSG), &dbv)) { setting.szMsg = _tcsdup(dbv.ptszVal); db_free(&dbv); } } } else if (setting.szMsg != NULL) { free(setting.szMsg); setting.szMsg = NULL; } return 0; } static VOID CALLBACK AutoAwayTimer(HWND hwnd,UINT message,UINT_PTR idEvent,DWORD dwTime) { int statusChanged = FALSE; int confirm = FALSE; for (int i=0; i < autoAwaySettings->getCount(); i++) { TAAAProtoSetting& aas = (*autoAwaySettings)[i]; aas.status = ID_STATUS_DISABLED; BOOL screenSaver = FALSE, locked = FALSE; if ( aas.optionFlags & FLAG_MONITORMIRANDA ) mouseStationaryTimer = (GetTickCount() - lastMirandaInput)/1000; else { LASTINPUTINFO ii = { sizeof(ii) }; GetLastInputInfo(&ii); mouseStationaryTimer = (GetTickCount() - ii.dwTime)/1000; } int sts1Time = aas.awayTime * SECS_PER_MINUTE; int sts2Time = aas.naTime * SECS_PER_MINUTE; int sts1setTime = aas.sts1setTimer==0?0:(GetTickCount() - aas.sts1setTimer)/1000; int currentMode = CallProtoService(aas.szName,PS_GETSTATUS,0, 0); if (aas.optionFlags & FLAG_ONSAVER) screenSaver = IsScreenSaverRunning(); if (aas.optionFlags & FLAG_ONLOCK) locked = IsWorkstationLocked(); /* check states */ if (aas.curState == ACTIVE) { if ((( mouseStationaryTimer >= sts1Time && (aas.optionFlags & FLAG_ONMOUSE)) || screenSaver || locked ) && currentMode != aas.lv1Status && aas.statusFlags&StatusModeToProtoFlag(currentMode)) { /* from ACTIVE to STATUS1_SET */ aas.lastStatus = aas.originalStatusMode = CallProtoService(aas.szName,PS_GETSTATUS,0, 0); aas.status = aas.lv1Status; aas.sts1setTimer = GetTickCount(); sts1setTime = 0; aas.statusChanged = statusChanged = TRUE; changeState(aas, STATUS1_SET); } else if ( mouseStationaryTimer >= sts2Time && currentMode == aas.lv1Status && currentMode != aas.lv2Status && (aas.optionFlags & FLAG_SETNA) && (aas.statusFlags & StatusModeToProtoFlag(currentMode))) { /* from ACTIVE to STATUS2_SET */ aas.lastStatus = aas.originalStatusMode = CallProtoService(aas.szName,PS_GETSTATUS,0, 0); aas.status = aas.lv2Status; aas.statusChanged = statusChanged = TRUE; changeState(aas, STATUS2_SET); } } if (aas.curState == STATUS1_SET) { if (( mouseStationaryTimer < sts1Time && !screenSaver && !locked ) && !(aas.optionFlags & FLAG_RESET )) { /* from STATUS1_SET to HIDDEN_ACTIVE */ changeState(aas, HIDDEN_ACTIVE); aas.lastStatus = CallProtoService(aas.szName,PS_GETSTATUS,0, 0); } else if ( ((mouseStationaryTimer < sts1Time) && (!screenSaver) && (!locked)) && ((aas.optionFlags&FLAG_LV2ONINACTIVE) || (!(aas.optionFlags&FLAG_SETNA))) && (aas.optionFlags&FLAG_RESET)) { /* from STATUS1_SET to SET_ORGSTATUS */ changeState(aas, SET_ORGSTATUS); } else if (( aas.optionFlags & FLAG_SETNA) && sts1setTime >= sts2Time ) { /* when set STATUS2, currentMode doesn't have to be in the selected status list (statusFlags) */ /* from STATUS1_SET to STATUS2_SET */ aas.lastStatus = CallProtoService(aas.szName,PS_GETSTATUS,0, 0); aas.status = aas.lv2Status; aas.statusChanged = statusChanged = TRUE; changeState(aas, STATUS2_SET); } } if ( aas.curState == STATUS2_SET ) { if ( mouseStationaryTimer < sts2Time && !screenSaver && !locked && ( aas.optionFlags & FLAG_RESET )) { /* from STATUS2_SET to SET_ORGSTATUS */ changeState(aas, SET_ORGSTATUS); } else if ( mouseStationaryTimer < sts2Time && !screenSaver && !locked && !( aas.optionFlags & FLAG_RESET )) { /* from STATUS2_SET to HIDDEN_ACTIVE */ /* Remember: after status1 is set, and "only on inactive" is NOT set, it implies !reset. */ changeState(aas, HIDDEN_ACTIVE); aas.lastStatus = CallProtoService(aas.szName,PS_GETSTATUS,0, 0); } } if ( aas.curState == HIDDEN_ACTIVE ) { if ( aas.mStatus ) { /* HIDDEN_ACTIVE to ACTIVE */ //aas.statusChanged = FALSE; changeState(aas, ACTIVE); aas.sts1setTimer = 0; aas.mStatus = FALSE; } else if (( aas.optionFlags & FLAG_SETNA ) && currentMode == aas.lv1Status && currentMode != aas.lv2Status && (aas.statusFlags & StatusModeToProtoFlag(currentMode)) && (mouseStationaryTimer >= sts2Time || (sts1setTime >= sts2Time && !(aas.optionFlags & FLAG_LV2ONINACTIVE )))) { /* HIDDEN_ACTIVE to STATUS2_SET */ aas.lastStatus = aas.originalStatusMode = CallProtoService(aas.szName,PS_GETSTATUS,0, 0); aas.status = aas.lv2Status; aas.statusChanged = statusChanged = TRUE; changeState(aas, STATUS2_SET); } } if ( aas.curState == SET_ORGSTATUS ) { /* SET_ORGSTATUS to ACTIVE */ aas.lastStatus = CallProtoService(aas.szName,PS_GETSTATUS,0, 0); aas.status = aas.originalStatusMode; confirm = (aas.optionFlags&FLAG_CONFIRM)?TRUE:confirm; aas.statusChanged = statusChanged = TRUE; changeState(aas, ACTIVE); aas.sts1setTimer = 0; } (*autoAwaySettings)[i].mStatus = FALSE; } if ( confirm || statusChanged ) { OBJLIST ps = *autoAwaySettings; for (int i=0; i < ps.getCount(); i++) { if (ps[i].szMsg) ps[i].szMsg = _tcsdup(ps[i].szMsg); if (ps[i].status == ID_STATUS_DISABLED) ps[i].szName = ""; } if (confirm) confirmDialog = (HWND)CallService(MS_CS_SHOWCONFIRMDLGEX, (WPARAM)&ps, db_get_w(NULL, MODULENAME, SETTING_CONFIRMDELAY, 5)); else if (statusChanged) CallService(MS_CS_SETSTATUSEX, (WPARAM)&ps, 0); } } ///////////////////////////////////////////////////////////////////////////////////////// // Windows hooks static int HookWindowsHooks(int hookMiranda, int hookAll) { if (hookMiranda) { if (monitorKeyboard && hMirandaKeyBoardHook == NULL) hMirandaKeyBoardHook = SetWindowsHookEx(WH_KEYBOARD,MirandaKeyBoardHookFunction,NULL,GetCurrentThreadId()); if (monitorMouse && hMirandaMouseHook == NULL) hMirandaMouseHook = SetWindowsHookEx(WH_MOUSE,MirandaMouseHookFunction,NULL,GetCurrentThreadId()); } if (hookAll) { if (monitorKeyboard && hKeyBoardHook == NULL) hKeyBoardHook = SetWindowsHookEx(WH_KEYBOARD, KeyBoardHookFunction, 0, GetCurrentThreadId()); if (monitorMouse && hMouseHook == NULL) hMouseHook = SetWindowsHookEx(WH_MOUSE, MouseHookFunction, 0, GetCurrentThreadId()); } return 0; } static int UnhookWindowsHooks() { UnhookWindowsHookEx(hMouseHook); UnhookWindowsHookEx(hKeyBoardHook); UnhookWindowsHookEx(hMirandaMouseHook); UnhookWindowsHookEx(hMirandaKeyBoardHook); hMouseHook = hKeyBoardHook = hMirandaMouseHook = hMirandaKeyBoardHook = NULL; return 0; } static LRESULT CALLBACK MirandaMouseHookFunction(int code, WPARAM wParam, LPARAM lParam) { if (code >= 0) { PMOUSEHOOKSTRUCT mouseInfo = (PMOUSEHOOKSTRUCT)lParam; POINT pt = mouseInfo->pt; /* TioDuke's KeyBoardNotifyExt: only update if a Miranda window is focused */ DWORD pid; GetWindowThreadProcessId(GetForegroundWindow(), &pid); if (pid != GetCurrentProcessId()) return CallNextHookEx(hMirandaMouseHook, code, wParam, lParam); if (pt.x!=lastMousePos.x || pt.y!=lastMousePos.y) { lastMousePos = pt; lastMirandaInput = GetTickCount(); } } return CallNextHookEx(hMirandaMouseHook, code, wParam, lParam); } static LRESULT CALLBACK MirandaKeyBoardHookFunction(int code, WPARAM wParam, LPARAM lParam) { if (code >= 0) { if (ignoreAltCombo) { if (((GetKeyState(VK_MENU) < 0) || (wParam == VK_MENU)) || ((GetKeyState(VK_TAB) < 0) || (wParam == VK_TAB)) || ((GetKeyState(VK_SHIFT) < 0) || (wParam == VK_SHIFT)) || ((GetKeyState(VK_CONTROL) < 0) || (wParam == VK_CONTROL)) || ((GetKeyState(VK_ESCAPE) < 0) || (wParam == VK_ESCAPE)) || ((GetKeyState(VK_LWIN) < 0) || (wParam == VK_LWIN)) || ((GetKeyState(VK_RWIN) < 0) || (wParam == VK_RWIN))) { return CallNextHookEx(hMirandaKeyBoardHook, code, wParam, lParam); } } switch (wParam) { case VK_NUMLOCK: case VK_CAPITAL: case VK_SCROLL: if (!ignoreLockKeys) lastMirandaInput = GetTickCount(); break; case VK_TAB: case VK_SHIFT: case VK_CONTROL: case VK_MENU: case VK_ESCAPE: case VK_LWIN: case VK_RWIN: if (!ignoreSysKeys) lastMirandaInput = GetTickCount(); break; default: lastMirandaInput = GetTickCount(); break; } } return CallNextHookEx(hMirandaKeyBoardHook, code, wParam, lParam); } static LRESULT CALLBACK MouseHookFunction(int code, WPARAM wParam, LPARAM lParam) { if (code >= 0) { PMOUSEHOOKSTRUCT mouseInfo = (PMOUSEHOOKSTRUCT)lParam; POINT pt = mouseInfo->pt; /* TioDuke's KeyBoardNotifyExt: also grab clicks */ if ((wParam >= WM_NCLBUTTONDOWN && wParam <= WM_NCXBUTTONDBLCLK && wParam != 0x00AA) || (wParam >= WM_LBUTTONDOWN && wParam <= WM_XBUTTONDBLCLK)) lastInput = GetTickCount(); if (pt.x!=lastMousePos.x || pt.y!=lastMousePos.y) { lastMousePos = pt; lastInput = GetTickCount(); } } return CallNextHookEx(hMouseHook, code, wParam, lParam); } static LRESULT CALLBACK KeyBoardHookFunction(int code, WPARAM wParam, LPARAM lParam) { if (code >= 0) { if (ignoreAltCombo) { if ( ((GetKeyState(VK_MENU) < 0) || (wParam == VK_MENU)) || ((GetKeyState(VK_TAB) < 0) || (wParam == VK_TAB)) || ((GetKeyState(VK_SHIFT) < 0) || (wParam == VK_SHIFT)) || ((GetKeyState(VK_CONTROL) < 0) || (wParam == VK_CONTROL)) || ((GetKeyState(VK_ESCAPE) < 0) || (wParam == VK_ESCAPE)) || ((GetKeyState(VK_LWIN) < 0) || (wParam == VK_LWIN)) || ((GetKeyState(VK_RWIN) < 0) || (wParam == VK_RWIN))) { return CallNextHookEx(hKeyBoardHook, code, wParam, lParam); } } switch (wParam) { case VK_NUMLOCK: case VK_CAPITAL: case VK_SCROLL: if (!ignoreLockKeys) lastInput = GetTickCount(); break; case VK_TAB: case VK_SHIFT: case VK_CONTROL: case VK_MENU: case VK_ESCAPE: case VK_LWIN: case VK_RWIN: if (!ignoreSysKeys) lastInput = GetTickCount(); break; default: lastInput = GetTickCount(); break; } } return CallNextHookEx(hKeyBoardHook, code, wParam, lParam); } ///////////////////////////////////////////////////////////////////////////////////////// // Inits & stuff static int AutoAwayShutdown(WPARAM wParam,LPARAM lParam) { KillTimer(NULL, hAutoAwayTimer); #ifdef TRIGGERPLUGIN DeInitTrigger(); #endif UnhookWindowsHooks(); DestroyHookableEvent(hStateChangedEvent); autoAwaySettings->destroy(); return 0; } int CSModuleLoaded(WPARAM wParam, LPARAM lParam) { HookEvent(ME_PROTO_ACCLISTCHANGED, OnAccChanged); HookEvent(ME_OPT_INITIALISE, AutoAwayOptInitialise); HookEvent(ME_SYSTEM_PRESHUTDOWN, AutoAwayShutdown); HookEvent(ME_PROTO_ACK, ProcessProtoAck); mouseStationaryTimer = 0; lastInput = lastMirandaInput = GetTickCount(); //////////////////////////////////////////////////////////////////////////////////////// protoList = ( OBJLIST* )autoAwaySettings; int count; PROTOACCOUNT** protos; ProtoEnumAccounts(&count, &protos); for ( int i=0; i < count; i++ ) if ( IsSuitableProto( protos[i] )) autoAwaySettings->insert( new TAAAProtoSetting( protos[i] )); //////////////////////////////////////////////////////////////////////////////////////// LoadOptions(*autoAwaySettings, FALSE); return 0; }