From d6a86b851a082a11b03bc8dabc81714c25217ff4 Mon Sep 17 00:00:00 2001 From: George Hazan Date: Thu, 30 Jan 2020 18:20:11 +0300 Subject: . --- plugins/StatusManager/src/aaa_main.cpp | 541 ++++++++++++ plugins/StatusManager/src/advancedautoaway.cpp | 541 ------------ plugins/StatusManager/src/keepstatus.cpp | 1100 ------------------------ plugins/StatusManager/src/ks_main.cpp | 1100 ++++++++++++++++++++++++ plugins/StatusManager/src/ss_main.cpp | 455 ++++++++++ plugins/StatusManager/src/startupstatus.cpp | 455 ---------- 6 files changed, 2096 insertions(+), 2096 deletions(-) create mode 100644 plugins/StatusManager/src/aaa_main.cpp delete mode 100644 plugins/StatusManager/src/advancedautoaway.cpp delete mode 100644 plugins/StatusManager/src/keepstatus.cpp create mode 100644 plugins/StatusManager/src/ks_main.cpp create mode 100644 plugins/StatusManager/src/ss_main.cpp delete mode 100644 plugins/StatusManager/src/startupstatus.cpp (limited to 'plugins/StatusManager/src') diff --git a/plugins/StatusManager/src/aaa_main.cpp b/plugins/StatusManager/src/aaa_main.cpp new file mode 100644 index 0000000000..aa630ccb36 --- /dev/null +++ b/plugins/StatusManager/src/aaa_main.cpp @@ -0,0 +1,541 @@ +/* + 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 "stdafx.h" + +#ifdef _DEBUG +#define SECS_PER_MINUTE 20 /* speedup */ +#else +#define SECS_PER_MINUTE 60 /* default I believe */ +#endif + +///////////////////////////////////////////////////////////////////////////////////////// + +CFakePlugin AAAPlugin(AAAMODULENAME); + +static HANDLE hEvents[3]; + +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 = nullptr; +HHOOK hMirandaKeyBoardHook = nullptr; +#pragma data_seg("Shared") +DWORD lastInput = 0; +POINT lastMousePos = { 0 }; +HHOOK hMouseHook = nullptr; +HHOOK hKeyBoardHook = nullptr; +#pragma data_seg() +#pragma comment(linker, "/section:Shared,rws") +DWORD lastMirandaInput = 0; +static UINT_PTR hAutoAwayTimer; +// prototypes +extern DWORD StatusModeToProtoFlag(int status); + +int AutoAwayOptInitialise(WPARAM wParam, LPARAM lParam); + +static int ProcessProtoAck(WPARAM, LPARAM lParam) +{ + ACKDATA *ack = (ACKDATA*)lParam; + if (ack->type != ACKTYPE_STATUS || ack->result != ACKRESULT_SUCCESS) + return 0; + + log_debug(0, "ProcessProtoAck: ack->szModule: %s", ack->szModule); + for (auto &it : protoList) { + log_debug(0, "chk: %s", it->m_szName); + if (!mir_strcmp(it->m_szName, ack->szModule)) { + log_debug(0, "ack->szModule: %s p.statusChanged: %d", ack->szModule, it->bStatusChanged); + if (!it->bStatusChanged) + it->bManualStatus = true; + + it->bStatusChanged = false; + } + } + + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// Account control event + +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(SMProto &setting, int mode, STATES newState) +{ + if (setting.curState == newState) + return 0; + + setting.oldState = setting.curState; + setting.curState = newState; + + log_debug(0, "%s state change: %s -> %s", setting.m_szName, status2descr(setting.oldState), status2descr(setting.curState)); + + if (setting.curState != SET_ORGSTATUS && setting.curState != ACTIVE && setting.bStatusChanged) { + /* change the awaymessage */ + if (setting.m_szMsg != nullptr) { + mir_free(setting.m_szMsg); + setting.m_szMsg = nullptr; + } + + if (AAAPlugin.getByte(StatusModeToDbSetting(setting.aaaStatus, SETTING_MSGCUSTOM), FALSE)) + setting.m_szMsg = AAAPlugin.getWStringA(StatusModeToDbSetting(setting.aaaStatus, SETTING_STATUSMSG)); + } + else if (setting.m_szMsg != nullptr) { + mir_free(setting.m_szMsg); + setting.m_szMsg = nullptr; + } + + if (setting.optionFlags & FLAG_ENTERIDLE) { + if (newState == ACTIVE) // we're returning back + Idle_Enter(-1); + else + Idle_Enter(mode); + } + + return 0; +} + +static int getIdleMode(int options) +{ + if ((options & FLAG_ONSAVER) && IsScreenSaverRunning()) + return 1; + + if ((options & FLAG_ONLOCK) && IsWorkstationLocked()) + return 2; + + if ((options & FLAG_ONTS) && IsTerminalDisconnected()) + return 3; + + if ((options & FLAG_FULLSCREEN) && IsFullScreen()) + return 4; + + return 0; +} + +static VOID CALLBACK AutoAwayTimer(HWND, UINT, UINT_PTR, DWORD) +{ + int statusChanged = FALSE; + int confirm = FALSE; + + for (auto &it : protoList) { + it->aaaStatus = ID_STATUS_DISABLED; + + if (it->optionFlags & FLAG_MONITORMIRANDA) + mouseStationaryTimer = (GetTickCount() - lastMirandaInput) / 1000; + else { + LASTINPUTINFO ii = { sizeof(ii) }; + GetLastInputInfo(&ii); + mouseStationaryTimer = (GetTickCount() - ii.dwTime) / 1000; + } + + int sts1Time = it->awayTime * SECS_PER_MINUTE; + int sts2Time = it->naTime * SECS_PER_MINUTE; + int sts1setTime = it->sts1setTimer == 0 ? 0 : (GetTickCount() - it->sts1setTimer) / 1000; + int currentMode = Proto_GetStatus(it->m_szName); + + int mode = getIdleMode(it->optionFlags); + + /* check states */ + if (it->curState == ACTIVE) { + if (((mouseStationaryTimer >= sts1Time && (it->optionFlags & FLAG_ONMOUSE)) || mode) && currentMode != it->lv1Status && it->statusFlags & StatusModeToProtoFlag(currentMode)) { + if (it->optionFlags & FLAG_ONMOUSE) + mode = 5; + + /* from ACTIVE to STATUS1_SET */ + it->m_lastStatus = it->originalStatusMode = Proto_GetStatus(it->m_szName); + it->aaaStatus = it->lv1Status; + it->sts1setTimer = GetTickCount(); + sts1setTime = 0; + it->bStatusChanged = statusChanged = true; + changeState(*it, mode, STATUS1_SET); + } + else if (mouseStationaryTimer >= sts2Time && currentMode == it->lv1Status && currentMode != it->lv2Status && (it->optionFlags & FLAG_SETNA) && (it->statusFlags & StatusModeToProtoFlag(currentMode))) { + /* from ACTIVE to STATUS2_SET */ + it->m_lastStatus = it->originalStatusMode = Proto_GetStatus(it->m_szName); + it->aaaStatus = it->lv2Status; + it->bStatusChanged = statusChanged = true; + changeState(*it, mode, STATUS2_SET); + } + } + + if (it->curState == STATUS1_SET) { + if ((mouseStationaryTimer < sts1Time && !mode) && !(it->optionFlags & FLAG_RESET)) { + /* from STATUS1_SET to HIDDEN_ACTIVE */ + changeState(*it, mode, HIDDEN_ACTIVE); + it->m_lastStatus = Proto_GetStatus(it->m_szName); + } + else if (((mouseStationaryTimer < sts1Time) && !mode) && + ((it->optionFlags & FLAG_LV2ONINACTIVE) || (!(it->optionFlags & FLAG_SETNA))) && + (it->optionFlags & FLAG_RESET)) { + /* from STATUS1_SET to SET_ORGSTATUS */ + changeState(*it, mode, SET_ORGSTATUS); + } + else if ((it->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 */ + it->m_lastStatus = Proto_GetStatus(it->m_szName); + it->aaaStatus = it->lv2Status; + it->bStatusChanged = statusChanged = true; + changeState(*it, mode, STATUS2_SET); + } + } + + if (it->curState == STATUS2_SET) { + if (mouseStationaryTimer < sts2Time && !mode && (it->optionFlags & FLAG_RESET)) { + /* from STATUS2_SET to SET_ORGSTATUS */ + changeState(*it, mode, SET_ORGSTATUS); + } + else if (mouseStationaryTimer < sts2Time && !mode && !(it->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(*it, mode, HIDDEN_ACTIVE); + it->m_lastStatus = Proto_GetStatus(it->m_szName); + } + } + + if (it->curState == HIDDEN_ACTIVE) { + if (it->bManualStatus) { + /* HIDDEN_ACTIVE to ACTIVE */ + // it->bStatusChanged = false; + changeState(*it, mode, ACTIVE); + it->sts1setTimer = 0; + it->bManualStatus = false; + } + else if ((it->optionFlags & FLAG_SETNA) && currentMode == it->lv1Status && + currentMode != it->lv2Status && (it->statusFlags & StatusModeToProtoFlag(currentMode)) && + (mouseStationaryTimer >= sts2Time || (sts1setTime >= sts2Time && !(it->optionFlags & FLAG_LV2ONINACTIVE)))) { + /* HIDDEN_ACTIVE to STATUS2_SET */ + it->m_lastStatus = it->originalStatusMode = Proto_GetStatus(it->m_szName); + it->aaaStatus = it->lv2Status; + it->bStatusChanged = statusChanged = true; + changeState(*it, mode, STATUS2_SET); + } + } + if (it->curState == SET_ORGSTATUS) { + /* SET_ORGSTATUS to ACTIVE */ + it->m_lastStatus = Proto_GetStatus(it->m_szName); + it->aaaStatus = it->originalStatusMode; + confirm = (it->optionFlags & FLAG_CONFIRM) ? TRUE : confirm; + it->bStatusChanged = statusChanged = true; + changeState(*it, mode, ACTIVE); + it->sts1setTimer = 0; + } + it->bManualStatus = false; + } + + if (confirm || statusChanged) { + TProtoSettings ps(protoList); // make a copy of data not to pollute main array + for (auto &it : ps) + it->m_status = it->aaaStatus; + + if (confirm) + confirmDialog = ShowConfirmDialogEx(&ps, AAAPlugin.getWord(SETTING_CONFIRMDELAY, 5)); + else if (statusChanged) + SetStatusEx(ps); + } +} + +///////////////////////////////////////////////////////////////////////////////////////// +// Windows hooks + +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); +} + +static int HookWindowsHooks(int hookMiranda, int hookAll) +{ + if (hookMiranda) { + if (monitorKeyboard && hMirandaKeyBoardHook == nullptr) + hMirandaKeyBoardHook = SetWindowsHookEx(WH_KEYBOARD, MirandaKeyBoardHookFunction, nullptr, GetCurrentThreadId()); + if (monitorMouse && hMirandaMouseHook == nullptr) + hMirandaMouseHook = SetWindowsHookEx(WH_MOUSE, MirandaMouseHookFunction, nullptr, GetCurrentThreadId()); + } + if (hookAll) { + if (monitorKeyboard && hKeyBoardHook == nullptr) + hKeyBoardHook = SetWindowsHookEx(WH_KEYBOARD, KeyBoardHookFunction, nullptr, GetCurrentThreadId()); + if (monitorMouse && hMouseHook == nullptr) + hMouseHook = SetWindowsHookEx(WH_MOUSE, MouseHookFunction, nullptr, GetCurrentThreadId()); + } + + return 0; +} + +static int UnhookWindowsHooks() +{ + UnhookWindowsHookEx(hMouseHook); + UnhookWindowsHookEx(hKeyBoardHook); + UnhookWindowsHookEx(hMirandaMouseHook); + UnhookWindowsHookEx(hMirandaKeyBoardHook); + + hMouseHook = hKeyBoardHook = hMirandaMouseHook = hMirandaKeyBoardHook = nullptr; + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// Load from DB + +int LoadAutoAwaySetting(SMProto &autoAwaySetting, char *protoName) +{ + char setting[128]; + mir_snprintf(setting, "%s_OptionFlags", protoName); + autoAwaySetting.optionFlags = AAAPlugin.getWord(setting, FLAG_LV2ONINACTIVE | FLAG_RESET | FLAG_ENTERIDLE); + mir_snprintf(setting, "%s_AwayTime", protoName); + autoAwaySetting.awayTime = AAAPlugin.getWord(setting, SETTING_AWAYTIME_DEFAULT); + mir_snprintf(setting, "%s_NATime", protoName); + autoAwaySetting.naTime = AAAPlugin.getWord(setting, SETTING_NATIME_DEFAULT); + mir_snprintf(setting, "%s_StatusFlags", protoName); + autoAwaySetting.statusFlags = AAAPlugin.getWord(setting, StatusModeToProtoFlag(ID_STATUS_ONLINE) | StatusModeToProtoFlag(ID_STATUS_FREECHAT)); + + int flags; + if (g_bAAASettingSame) + flags = 0xFFFFFF; + else + flags = CallProtoService(protoName, PS_GETCAPS, PFLAGNUM_2, 0) & ~CallProtoService(protoName, PS_GETCAPS, (WPARAM)PFLAGNUM_5, 0); + mir_snprintf(setting, "%s_Lv1Status", protoName); + autoAwaySetting.lv1Status = AAAPlugin.getWord(setting, (flags & StatusModeToProtoFlag(ID_STATUS_AWAY)) ? ID_STATUS_AWAY : ID_STATUS_OFFLINE); + mir_snprintf(setting, "%s_Lv2Status", protoName); + autoAwaySetting.lv2Status = AAAPlugin.getWord(setting, (flags & StatusModeToProtoFlag(ID_STATUS_NA)) ? ID_STATUS_NA : ID_STATUS_OFFLINE); + + return 0; +} + +void AAAUnloadOptions() +{ + UnhookWindowsHooks(); + if (hAutoAwayTimer != 0) + KillTimer(nullptr, hAutoAwayTimer); +} + +void AAALoadOptions() +{ + // if bOverride is enabled, samesettings will be ignored (for options loading) + AAAUnloadOptions(); + + bool monitorMiranda = false, monitorAll = false; + + ignoreLockKeys = AAAPlugin.getByte(SETTING_IGNLOCK, FALSE); + ignoreSysKeys = AAAPlugin.getByte(SETTING_IGNSYSKEYS, FALSE); + ignoreAltCombo = AAAPlugin.getByte(SETTING_IGNALTCOMBO, FALSE); + monitorMouse = AAAPlugin.getByte(SETTING_MONITORMOUSE, TRUE) != 0; + monitorKeyboard = AAAPlugin.getByte(SETTING_MONITORKEYBOARD, TRUE) != 0; + lastInput = lastMirandaInput = GetTickCount(); + + for (auto &it : protoList) { + char *protoName; + if (g_bAAASettingSame) + protoName = SETTING_ALL; + else + protoName = it->m_szName; + LoadAutoAwaySetting(*it, protoName); + + if (it->optionFlags & FLAG_MONITORMIRANDA) + monitorMiranda = true; + else if (ignoreLockKeys || ignoreSysKeys || ignoreAltCombo || (monitorMouse != monitorKeyboard)) + monitorAll = true; + } + + HookWindowsHooks(monitorMiranda, monitorAll); + hAutoAwayTimer = SetTimer(nullptr, 0, AAAPlugin.getWord(SETTING_AWAYCHECKTIMEINSECS, 5) * 1000, AutoAwayTimer); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// Inits & stuff + +static int AutoAwayShutdown(WPARAM, LPARAM) +{ + AAAUnloadOptions(); + return 0; +} + +static int AAAModuleLoaded(WPARAM, LPARAM) +{ + hEvents[0] = HookEvent(ME_OPT_INITIALISE, AutoAwayOptInitialise); + hEvents[1] = HookEvent(ME_SYSTEM_PRESHUTDOWN, AutoAwayShutdown); + hEvents[2] = HookEvent(ME_PROTO_ACK, ProcessProtoAck); + + mouseStationaryTimer = 0; + lastInput = lastMirandaInput = GetTickCount(); + + AAALoadOptions(); + return 0; +} + +void AdvancedAutoAwayLoad() +{ + g_bAAASettingSame = AAAPlugin.getByte(SETTING_SAMESETTINGS); + + if (g_bMirandaLoaded) + AAAModuleLoaded(0, 0); + else + HookEvent(ME_SYSTEM_MODULESLOADED, AAAModuleLoaded); +} + +void AdvancedAutoAwayUnload() +{ + if (g_bMirandaLoaded) + AutoAwayShutdown(0, 0); + + KillModuleOptions(&AAAPlugin); + + for (auto &it : hEvents) { + UnhookEvent(it); + it = nullptr; + } + + protoList.destroy(); +} diff --git a/plugins/StatusManager/src/advancedautoaway.cpp b/plugins/StatusManager/src/advancedautoaway.cpp deleted file mode 100644 index aa630ccb36..0000000000 --- a/plugins/StatusManager/src/advancedautoaway.cpp +++ /dev/null @@ -1,541 +0,0 @@ -/* - 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 "stdafx.h" - -#ifdef _DEBUG -#define SECS_PER_MINUTE 20 /* speedup */ -#else -#define SECS_PER_MINUTE 60 /* default I believe */ -#endif - -///////////////////////////////////////////////////////////////////////////////////////// - -CFakePlugin AAAPlugin(AAAMODULENAME); - -static HANDLE hEvents[3]; - -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 = nullptr; -HHOOK hMirandaKeyBoardHook = nullptr; -#pragma data_seg("Shared") -DWORD lastInput = 0; -POINT lastMousePos = { 0 }; -HHOOK hMouseHook = nullptr; -HHOOK hKeyBoardHook = nullptr; -#pragma data_seg() -#pragma comment(linker, "/section:Shared,rws") -DWORD lastMirandaInput = 0; -static UINT_PTR hAutoAwayTimer; -// prototypes -extern DWORD StatusModeToProtoFlag(int status); - -int AutoAwayOptInitialise(WPARAM wParam, LPARAM lParam); - -static int ProcessProtoAck(WPARAM, LPARAM lParam) -{ - ACKDATA *ack = (ACKDATA*)lParam; - if (ack->type != ACKTYPE_STATUS || ack->result != ACKRESULT_SUCCESS) - return 0; - - log_debug(0, "ProcessProtoAck: ack->szModule: %s", ack->szModule); - for (auto &it : protoList) { - log_debug(0, "chk: %s", it->m_szName); - if (!mir_strcmp(it->m_szName, ack->szModule)) { - log_debug(0, "ack->szModule: %s p.statusChanged: %d", ack->szModule, it->bStatusChanged); - if (!it->bStatusChanged) - it->bManualStatus = true; - - it->bStatusChanged = false; - } - } - - return 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// -// Account control event - -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(SMProto &setting, int mode, STATES newState) -{ - if (setting.curState == newState) - return 0; - - setting.oldState = setting.curState; - setting.curState = newState; - - log_debug(0, "%s state change: %s -> %s", setting.m_szName, status2descr(setting.oldState), status2descr(setting.curState)); - - if (setting.curState != SET_ORGSTATUS && setting.curState != ACTIVE && setting.bStatusChanged) { - /* change the awaymessage */ - if (setting.m_szMsg != nullptr) { - mir_free(setting.m_szMsg); - setting.m_szMsg = nullptr; - } - - if (AAAPlugin.getByte(StatusModeToDbSetting(setting.aaaStatus, SETTING_MSGCUSTOM), FALSE)) - setting.m_szMsg = AAAPlugin.getWStringA(StatusModeToDbSetting(setting.aaaStatus, SETTING_STATUSMSG)); - } - else if (setting.m_szMsg != nullptr) { - mir_free(setting.m_szMsg); - setting.m_szMsg = nullptr; - } - - if (setting.optionFlags & FLAG_ENTERIDLE) { - if (newState == ACTIVE) // we're returning back - Idle_Enter(-1); - else - Idle_Enter(mode); - } - - return 0; -} - -static int getIdleMode(int options) -{ - if ((options & FLAG_ONSAVER) && IsScreenSaverRunning()) - return 1; - - if ((options & FLAG_ONLOCK) && IsWorkstationLocked()) - return 2; - - if ((options & FLAG_ONTS) && IsTerminalDisconnected()) - return 3; - - if ((options & FLAG_FULLSCREEN) && IsFullScreen()) - return 4; - - return 0; -} - -static VOID CALLBACK AutoAwayTimer(HWND, UINT, UINT_PTR, DWORD) -{ - int statusChanged = FALSE; - int confirm = FALSE; - - for (auto &it : protoList) { - it->aaaStatus = ID_STATUS_DISABLED; - - if (it->optionFlags & FLAG_MONITORMIRANDA) - mouseStationaryTimer = (GetTickCount() - lastMirandaInput) / 1000; - else { - LASTINPUTINFO ii = { sizeof(ii) }; - GetLastInputInfo(&ii); - mouseStationaryTimer = (GetTickCount() - ii.dwTime) / 1000; - } - - int sts1Time = it->awayTime * SECS_PER_MINUTE; - int sts2Time = it->naTime * SECS_PER_MINUTE; - int sts1setTime = it->sts1setTimer == 0 ? 0 : (GetTickCount() - it->sts1setTimer) / 1000; - int currentMode = Proto_GetStatus(it->m_szName); - - int mode = getIdleMode(it->optionFlags); - - /* check states */ - if (it->curState == ACTIVE) { - if (((mouseStationaryTimer >= sts1Time && (it->optionFlags & FLAG_ONMOUSE)) || mode) && currentMode != it->lv1Status && it->statusFlags & StatusModeToProtoFlag(currentMode)) { - if (it->optionFlags & FLAG_ONMOUSE) - mode = 5; - - /* from ACTIVE to STATUS1_SET */ - it->m_lastStatus = it->originalStatusMode = Proto_GetStatus(it->m_szName); - it->aaaStatus = it->lv1Status; - it->sts1setTimer = GetTickCount(); - sts1setTime = 0; - it->bStatusChanged = statusChanged = true; - changeState(*it, mode, STATUS1_SET); - } - else if (mouseStationaryTimer >= sts2Time && currentMode == it->lv1Status && currentMode != it->lv2Status && (it->optionFlags & FLAG_SETNA) && (it->statusFlags & StatusModeToProtoFlag(currentMode))) { - /* from ACTIVE to STATUS2_SET */ - it->m_lastStatus = it->originalStatusMode = Proto_GetStatus(it->m_szName); - it->aaaStatus = it->lv2Status; - it->bStatusChanged = statusChanged = true; - changeState(*it, mode, STATUS2_SET); - } - } - - if (it->curState == STATUS1_SET) { - if ((mouseStationaryTimer < sts1Time && !mode) && !(it->optionFlags & FLAG_RESET)) { - /* from STATUS1_SET to HIDDEN_ACTIVE */ - changeState(*it, mode, HIDDEN_ACTIVE); - it->m_lastStatus = Proto_GetStatus(it->m_szName); - } - else if (((mouseStationaryTimer < sts1Time) && !mode) && - ((it->optionFlags & FLAG_LV2ONINACTIVE) || (!(it->optionFlags & FLAG_SETNA))) && - (it->optionFlags & FLAG_RESET)) { - /* from STATUS1_SET to SET_ORGSTATUS */ - changeState(*it, mode, SET_ORGSTATUS); - } - else if ((it->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 */ - it->m_lastStatus = Proto_GetStatus(it->m_szName); - it->aaaStatus = it->lv2Status; - it->bStatusChanged = statusChanged = true; - changeState(*it, mode, STATUS2_SET); - } - } - - if (it->curState == STATUS2_SET) { - if (mouseStationaryTimer < sts2Time && !mode && (it->optionFlags & FLAG_RESET)) { - /* from STATUS2_SET to SET_ORGSTATUS */ - changeState(*it, mode, SET_ORGSTATUS); - } - else if (mouseStationaryTimer < sts2Time && !mode && !(it->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(*it, mode, HIDDEN_ACTIVE); - it->m_lastStatus = Proto_GetStatus(it->m_szName); - } - } - - if (it->curState == HIDDEN_ACTIVE) { - if (it->bManualStatus) { - /* HIDDEN_ACTIVE to ACTIVE */ - // it->bStatusChanged = false; - changeState(*it, mode, ACTIVE); - it->sts1setTimer = 0; - it->bManualStatus = false; - } - else if ((it->optionFlags & FLAG_SETNA) && currentMode == it->lv1Status && - currentMode != it->lv2Status && (it->statusFlags & StatusModeToProtoFlag(currentMode)) && - (mouseStationaryTimer >= sts2Time || (sts1setTime >= sts2Time && !(it->optionFlags & FLAG_LV2ONINACTIVE)))) { - /* HIDDEN_ACTIVE to STATUS2_SET */ - it->m_lastStatus = it->originalStatusMode = Proto_GetStatus(it->m_szName); - it->aaaStatus = it->lv2Status; - it->bStatusChanged = statusChanged = true; - changeState(*it, mode, STATUS2_SET); - } - } - if (it->curState == SET_ORGSTATUS) { - /* SET_ORGSTATUS to ACTIVE */ - it->m_lastStatus = Proto_GetStatus(it->m_szName); - it->aaaStatus = it->originalStatusMode; - confirm = (it->optionFlags & FLAG_CONFIRM) ? TRUE : confirm; - it->bStatusChanged = statusChanged = true; - changeState(*it, mode, ACTIVE); - it->sts1setTimer = 0; - } - it->bManualStatus = false; - } - - if (confirm || statusChanged) { - TProtoSettings ps(protoList); // make a copy of data not to pollute main array - for (auto &it : ps) - it->m_status = it->aaaStatus; - - if (confirm) - confirmDialog = ShowConfirmDialogEx(&ps, AAAPlugin.getWord(SETTING_CONFIRMDELAY, 5)); - else if (statusChanged) - SetStatusEx(ps); - } -} - -///////////////////////////////////////////////////////////////////////////////////////// -// Windows hooks - -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); -} - -static int HookWindowsHooks(int hookMiranda, int hookAll) -{ - if (hookMiranda) { - if (monitorKeyboard && hMirandaKeyBoardHook == nullptr) - hMirandaKeyBoardHook = SetWindowsHookEx(WH_KEYBOARD, MirandaKeyBoardHookFunction, nullptr, GetCurrentThreadId()); - if (monitorMouse && hMirandaMouseHook == nullptr) - hMirandaMouseHook = SetWindowsHookEx(WH_MOUSE, MirandaMouseHookFunction, nullptr, GetCurrentThreadId()); - } - if (hookAll) { - if (monitorKeyboard && hKeyBoardHook == nullptr) - hKeyBoardHook = SetWindowsHookEx(WH_KEYBOARD, KeyBoardHookFunction, nullptr, GetCurrentThreadId()); - if (monitorMouse && hMouseHook == nullptr) - hMouseHook = SetWindowsHookEx(WH_MOUSE, MouseHookFunction, nullptr, GetCurrentThreadId()); - } - - return 0; -} - -static int UnhookWindowsHooks() -{ - UnhookWindowsHookEx(hMouseHook); - UnhookWindowsHookEx(hKeyBoardHook); - UnhookWindowsHookEx(hMirandaMouseHook); - UnhookWindowsHookEx(hMirandaKeyBoardHook); - - hMouseHook = hKeyBoardHook = hMirandaMouseHook = hMirandaKeyBoardHook = nullptr; - return 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// -// Load from DB - -int LoadAutoAwaySetting(SMProto &autoAwaySetting, char *protoName) -{ - char setting[128]; - mir_snprintf(setting, "%s_OptionFlags", protoName); - autoAwaySetting.optionFlags = AAAPlugin.getWord(setting, FLAG_LV2ONINACTIVE | FLAG_RESET | FLAG_ENTERIDLE); - mir_snprintf(setting, "%s_AwayTime", protoName); - autoAwaySetting.awayTime = AAAPlugin.getWord(setting, SETTING_AWAYTIME_DEFAULT); - mir_snprintf(setting, "%s_NATime", protoName); - autoAwaySetting.naTime = AAAPlugin.getWord(setting, SETTING_NATIME_DEFAULT); - mir_snprintf(setting, "%s_StatusFlags", protoName); - autoAwaySetting.statusFlags = AAAPlugin.getWord(setting, StatusModeToProtoFlag(ID_STATUS_ONLINE) | StatusModeToProtoFlag(ID_STATUS_FREECHAT)); - - int flags; - if (g_bAAASettingSame) - flags = 0xFFFFFF; - else - flags = CallProtoService(protoName, PS_GETCAPS, PFLAGNUM_2, 0) & ~CallProtoService(protoName, PS_GETCAPS, (WPARAM)PFLAGNUM_5, 0); - mir_snprintf(setting, "%s_Lv1Status", protoName); - autoAwaySetting.lv1Status = AAAPlugin.getWord(setting, (flags & StatusModeToProtoFlag(ID_STATUS_AWAY)) ? ID_STATUS_AWAY : ID_STATUS_OFFLINE); - mir_snprintf(setting, "%s_Lv2Status", protoName); - autoAwaySetting.lv2Status = AAAPlugin.getWord(setting, (flags & StatusModeToProtoFlag(ID_STATUS_NA)) ? ID_STATUS_NA : ID_STATUS_OFFLINE); - - return 0; -} - -void AAAUnloadOptions() -{ - UnhookWindowsHooks(); - if (hAutoAwayTimer != 0) - KillTimer(nullptr, hAutoAwayTimer); -} - -void AAALoadOptions() -{ - // if bOverride is enabled, samesettings will be ignored (for options loading) - AAAUnloadOptions(); - - bool monitorMiranda = false, monitorAll = false; - - ignoreLockKeys = AAAPlugin.getByte(SETTING_IGNLOCK, FALSE); - ignoreSysKeys = AAAPlugin.getByte(SETTING_IGNSYSKEYS, FALSE); - ignoreAltCombo = AAAPlugin.getByte(SETTING_IGNALTCOMBO, FALSE); - monitorMouse = AAAPlugin.getByte(SETTING_MONITORMOUSE, TRUE) != 0; - monitorKeyboard = AAAPlugin.getByte(SETTING_MONITORKEYBOARD, TRUE) != 0; - lastInput = lastMirandaInput = GetTickCount(); - - for (auto &it : protoList) { - char *protoName; - if (g_bAAASettingSame) - protoName = SETTING_ALL; - else - protoName = it->m_szName; - LoadAutoAwaySetting(*it, protoName); - - if (it->optionFlags & FLAG_MONITORMIRANDA) - monitorMiranda = true; - else if (ignoreLockKeys || ignoreSysKeys || ignoreAltCombo || (monitorMouse != monitorKeyboard)) - monitorAll = true; - } - - HookWindowsHooks(monitorMiranda, monitorAll); - hAutoAwayTimer = SetTimer(nullptr, 0, AAAPlugin.getWord(SETTING_AWAYCHECKTIMEINSECS, 5) * 1000, AutoAwayTimer); -} - -///////////////////////////////////////////////////////////////////////////////////////// -// Inits & stuff - -static int AutoAwayShutdown(WPARAM, LPARAM) -{ - AAAUnloadOptions(); - return 0; -} - -static int AAAModuleLoaded(WPARAM, LPARAM) -{ - hEvents[0] = HookEvent(ME_OPT_INITIALISE, AutoAwayOptInitialise); - hEvents[1] = HookEvent(ME_SYSTEM_PRESHUTDOWN, AutoAwayShutdown); - hEvents[2] = HookEvent(ME_PROTO_ACK, ProcessProtoAck); - - mouseStationaryTimer = 0; - lastInput = lastMirandaInput = GetTickCount(); - - AAALoadOptions(); - return 0; -} - -void AdvancedAutoAwayLoad() -{ - g_bAAASettingSame = AAAPlugin.getByte(SETTING_SAMESETTINGS); - - if (g_bMirandaLoaded) - AAAModuleLoaded(0, 0); - else - HookEvent(ME_SYSTEM_MODULESLOADED, AAAModuleLoaded); -} - -void AdvancedAutoAwayUnload() -{ - if (g_bMirandaLoaded) - AutoAwayShutdown(0, 0); - - KillModuleOptions(&AAAPlugin); - - for (auto &it : hEvents) { - UnhookEvent(it); - it = nullptr; - } - - protoList.destroy(); -} diff --git a/plugins/StatusManager/src/keepstatus.cpp b/plugins/StatusManager/src/keepstatus.cpp deleted file mode 100644 index 758b2d090a..0000000000 --- a/plugins/StatusManager/src/keepstatus.cpp +++ /dev/null @@ -1,1100 +0,0 @@ -/* - KeepStatus 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 - */ - -#include "stdafx.h" - -CFakePlugin KSPlugin(KSMODULENAME); - -static HANDLE hConnectionEvent = nullptr; -static HANDLE hServices[4], hEvents[2]; - -static mir_cs GenTimerCS, GenStatusCS, CheckContinueslyCS; - -static HANDLE hProtoAckHook = nullptr; -static HANDLE hStatusChangeHook = nullptr; -static HANDLE hCSStatusChangeHook = nullptr; -static HANDLE hCSStatusChangeExHook = nullptr; - -static HWND hMessageWindow = nullptr; - -static UINT_PTR checkConnectionTimerId = 0; -static UINT_PTR afterCheckTimerId = 0; -static UINT_PTR processAckTimerId = 0; -static UINT_PTR checkContinTimerId = 0; -static UINT_PTR checkConnectingTimerId = 0; -static int retryCount = 0; -static BOOL bLastPingResult = TRUE; -// variables (options) -static int maxRetries = 0; -static int initDelay = 0; -static int currentDelay = 0; -static int maxDelay = 0; -static int ackDelay = 500; -static int increaseExponential = 0; -static int showConnectionPopups = 0; -// prototypes -static int StartTimer(int timer, int timeout, BOOL restart); -static int StopTimer(int timer); -static int ProcessProtoAck(WPARAM wParam, LPARAM lParam); -static VOID CALLBACK CheckConnectingTimer(HWND hwnd, UINT message, UINT_PTR idEvent, DWORD dwTime); -static VOID CALLBACK CheckAckStatusTimer(HWND hwnd, UINT message, UINT_PTR idEvent, DWORD dwTime); -static int StatusChange(WPARAM wParam, LPARAM lParam); -static int CSStatusChange(WPARAM wParam, LPARAM lParam); -static int CSStatusChangeEx(WPARAM wParam, LPARAM lParam); -static VOID CALLBACK CheckConnectionTimer(HWND hwnd, UINT message, UINT_PTR idEvent, DWORD dwTime); -static int StopChecking(); -static VOID CALLBACK AfterCheckTimer(HWND hwnd, UINT message, UINT_PTR idEvent, DWORD dwTime); -static VOID CALLBACK CheckContinueslyTimer(HWND hwnd, UINT message, UINT_PTR idEvent, DWORD dwTime); -INT_PTR IsProtocolEnabledService(WPARAM wParam, LPARAM lParam); - -static int ProcessPopup(int reason, LPARAM lParam); -LRESULT CALLBACK KSPopupDlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); -static DWORD CALLBACK MessageWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); - -// options.c -extern int KeepStatusOptionsInit(WPARAM wparam, LPARAM); - -///////////////////////////////////////////////////////////////////////////////////////// - -void KSUnloadOptions() -{ - UnhookEvent(hProtoAckHook); - UnhookEvent(hStatusChangeHook); - UnhookEvent(hCSStatusChangeHook); - UnhookEvent(hCSStatusChangeExHook); - hProtoAckHook = hStatusChangeHook = hCSStatusChangeHook = hCSStatusChangeExHook = nullptr; - - if (IsWindow(hMessageWindow)) - DestroyWindow(hMessageWindow); - - if (StartTimer(IDT_CHECKCONTIN, -1, FALSE)) - WSACleanup(); - - StopTimer(IDT_CHECKCONN | IDT_PROCESSACK | IDT_AFTERCHECK | IDT_CHECKCONTIN | IDT_CHECKCONNECTING); -} - -int KSLoadOptions() -{ - KSUnloadOptions(); - - if (KSPlugin.getByte(SETTING_CHECKCONNECTION, FALSE)) { - if (KSPlugin.getByte(SETTING_CONTCHECK, FALSE)) { - if (KSPlugin.getByte(SETTING_BYPING, FALSE)) { - WSADATA wsaData; - WSAStartup(MAKEWORD(2, 2), &wsaData); - } - StartTimer(IDT_CHECKCONTIN, 0, FALSE); - } - increaseExponential = KSPlugin.getByte(SETTING_INCREASEEXPONENTIAL, FALSE); - currentDelay = initDelay = 1000 * KSPlugin.getDword(SETTING_INITDELAY, DEFAULT_INITDELAY); - maxDelay = 1000 * KSPlugin.getDword(SETTING_MAXDELAY, DEFAULT_MAXDELAY); - maxRetries = KSPlugin.getByte(SETTING_MAXRETRIES, 0); - if (maxRetries == 0) - maxRetries = -1; - hProtoAckHook = HookEvent(ME_PROTO_ACK, ProcessProtoAck); - hStatusChangeHook = HookEvent(ME_CLIST_STATUSMODECHANGE, StatusChange); - if (ServiceExists(ME_CS_STATUSCHANGE)) - hCSStatusChangeHook = HookEvent(ME_CS_STATUSCHANGE, CSStatusChange); - hCSStatusChangeExHook = HookEvent(ME_CS_STATUSCHANGEEX, CSStatusChangeEx); - if (KSPlugin.getByte(SETTING_CHECKAPMRESUME, 0)) { - if (!IsWindow(hMessageWindow)) { - hMessageWindow = CreateWindowEx(0, L"STATIC", nullptr, 0, 0, 0, 0, 0, nullptr, nullptr, nullptr, nullptr); - SetWindowLongPtr(hMessageWindow, GWLP_WNDPROC, (LONG_PTR)MessageWndProc); - } - } - retryCount = 0; - } - - return 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -int SMProto::AssignStatus(int iStatus, int iLastStatus, wchar_t *pwszMsg) -{ - if (iStatus < MIN_STATUS || iStatus > MAX_STATUS) - return -1; - if (iStatus != ID_STATUS_OFFLINE && m_status == ID_STATUS_DISABLED) - return -2; - if (!KSPlugin.getByte(SETTING_NOLOCKED, 0) && Proto_GetAccount(m_szName)->IsLocked()) - return -3; - - mir_cslock lck(GenStatusCS); - - char dbSetting[128]; - mir_snprintf(dbSetting, "%s_enabled", m_szName); - m_lastStatus = (iLastStatus == 0) ? m_status : iLastStatus; - if (!KSPlugin.getByte(dbSetting, 1)) - m_status = ID_STATUS_DISABLED; - else if (iStatus == ID_STATUS_LAST) - m_status = m_lastStatus; - else - m_status = iStatus; - - log_info(0, "KeepStatus: assigning status %d (%d, %d) to %s", m_status, m_lastStatus, iLastStatus, m_szName); - - if (pwszMsg != nullptr && mir_wstrcmp(pwszMsg, m_szMsg)) { - if (m_szMsg != nullptr) - mir_free(m_szMsg); - - m_szMsg = mir_wstrdup(pwszMsg); - } - else if (pwszMsg != m_szMsg) { - if (m_szMsg != nullptr) - mir_free(m_szMsg); - - m_szMsg = nullptr; - } - return 0; -} - -int SMProto::GetStatus() const -{ - switch (m_status) { - case ID_STATUS_CURRENT: - return Proto_GetStatus(m_szName); - case ID_STATUS_LAST: - return m_lastStatus; - default: - return m_status; - } -} - -static int SetCurrentStatus() -{ - TProtoSettings ps(protoList); - for (auto &p : ps) { - int realStatus = Proto_GetStatus(p->m_szName); - int curStatus = p->GetStatus(); - if (curStatus == ID_STATUS_DISABLED) - continue; - if (curStatus == realStatus) { - p->m_status = ID_STATUS_DISABLED; - continue; - } - - log_info(0, "KeepStatus: status for %s differs: stored = %d, real = %d", p->m_szName, curStatus, realStatus); - - // force offline before reconnecting? - if (realStatus != ID_STATUS_OFFLINE && KSPlugin.getByte(SETTING_FIRSTOFFLINE, FALSE)) { - log_info(0, "KeepStatus: Setting %s offline before making a new connection attempt", p->m_szName); - CallProtoService(p->m_szName, PS_SETSTATUS, (WPARAM)ID_STATUS_OFFLINE, 0); - } - } - ProcessPopup(KS_CONN_STATE_RETRY, (LPARAM)ps.getArray()); - return SetStatusEx(ps); -} - -static int StatusChange(WPARAM wParam, LPARAM lParam) -{ - char *szProto = (char *)lParam; - if (szProto == nullptr) { // global status change - for (auto &it : protoList) - it->AssignStatus(wParam, 0, it->m_szMsg); - } - else { - for (auto &it : protoList) - if (!mir_strcmp(it->m_szName, szProto)) - it->AssignStatus(wParam, 0, it->m_szMsg); - } - - return 0; -} - -static int CSStatusChange(WPARAM wParam, LPARAM) -{ - // the status was changed by commonstatus (old) - if (wParam != 0) { - PROTOCOLSETTING **protoSettings = *(PROTOCOLSETTING***)wParam; - if (protoSettings == nullptr) - return -1; - - for (int i = 0; i < protoList.getCount(); i++) { - auto psi = protoSettings[i]; - if (psi->szName == nullptr) - continue; - - for (auto &it : protoList) { - if (it->m_szName == nullptr) - continue; - - if (!mir_strcmp(psi->szName, it->m_szName)) - it->AssignStatus(psi->status, psi->lastStatus, it->m_szMsg); - } - } - } - - return 0; -} - -static int CSStatusChangeEx(WPARAM wParam, LPARAM pCount) -{ - // the status was changed by commonstatus (new) - if (wParam != 0) { - PROTOCOLSETTINGEX** protoSettings = *(PROTOCOLSETTINGEX***)wParam; - if (protoSettings == nullptr) - return -1; - - for (int i = 0; i < pCount; i++) { - auto psi = protoSettings[i]; - if (psi->m_szName == nullptr) - continue; - - for (auto &it : protoList) { - if (it->m_szName == nullptr) - continue; - - if (!mir_strcmp(psi->m_szName, it->m_szName)) - it->AssignStatus(psi->m_status, psi->m_lastStatus, psi->m_szMsg); - } - } - } - - return 0; -} - -struct TimerInfo -{ - int timer; - int timeout; - BOOL restart; -}; - -static INT_PTR CALLBACK StartTimerApcProc(void *param) -{ - TimerInfo *ti = (TimerInfo *)param; - int res = 0; - - mir_cslock lck(GenTimerCS); - log_debug(0, "StartTimer: %d, %d, %d", ti->timer, ti->timeout, ti->restart); - log_debug(0, "ack: %u, chk: %u, aft: %u, cnt: %u, con: %u", processAckTimerId, checkConnectionTimerId, afterCheckTimerId, checkContinTimerId, checkConnectingTimerId); - if (ti->timer & IDT_PROCESSACK) { - res = (processAckTimerId == 0) ? 0 : 1; - if (((processAckTimerId == 0) && (checkConnectionTimerId == 0)) || (ti->restart)) { - if (ti->timeout != -1) { - if (ti->restart) - KillTimer(nullptr, processAckTimerId); - if (ti->timeout == 0) - ti->timeout = ackDelay; - processAckTimerId = SetTimer(nullptr, 0, ti->timeout, CheckAckStatusTimer); - } - } - } - - if (ti->timer & IDT_CHECKCONN) { - res = (checkConnectionTimerId == 0 ? 0 : 1) || res; - if ((checkConnectionTimerId == 0) || (ti->restart)) { - if (ti->timeout != -1) { - if (ti->restart) - KillTimer(nullptr, checkConnectionTimerId); - - if (ti->timeout == 0) - ti->timeout = initDelay; - checkConnectionTimerId = SetTimer(nullptr, 0, ti->timeout, CheckConnectionTimer); - } - } - } - - if (ti->timer & IDT_AFTERCHECK) { - res = (afterCheckTimerId == 0 ? 0 : 1) || res; - if ((afterCheckTimerId == 0) || (ti->restart)) { - if (ti->timeout != -1) { - if (ti->restart) - KillTimer(nullptr, afterCheckTimerId); - - if (ti->timeout == 0) - ti->timeout = initDelay / 2; - afterCheckTimerId = SetTimer(nullptr, 0, ti->timeout, AfterCheckTimer); - } - } - } - - if (ti->timer & IDT_CHECKCONTIN) { - res = (checkContinTimerId == 0 ? 0 : 1) || res; - if ((checkContinTimerId == 0) || (ti->restart)) { - if (ti->timeout != -1) { - if (ti->restart) - KillTimer(nullptr, checkContinTimerId); - - if (ti->timeout == 0) - ti->timeout = 1000 * KSPlugin.getDword(SETTING_CNTDELAY, CHECKCONTIN_DELAY); - checkContinTimerId = SetTimer(nullptr, 0, ti->timeout, CheckContinueslyTimer); - } - } - } - - if (ti->timer & IDT_CHECKCONNECTING) { - res = (checkConnectingTimerId == 0 ? 0 : 1) || res; - if ((checkConnectingTimerId == 0) || (ti->restart)) { - if (ti->timeout != -1) { - if (ti->restart) - KillTimer(nullptr, checkConnectingTimerId); - if (ti->timeout == 0) - ti->timeout = initDelay / 2; - checkConnectingTimerId = SetTimer(nullptr, 0, ti->timeout, CheckConnectingTimer); - } - } - } - - log_debug(0, "ack: %u, chk: %u, aft: %u, cnt: %u, con: %u", processAckTimerId, checkConnectionTimerId, afterCheckTimerId, checkContinTimerId, checkConnectingTimerId); - log_debug(0, "StartTimer done %d", res); - return res; -} - -static int StartTimer(int timer, int timeout, BOOL restart) -{ - TimerInfo ti = { timer, timeout, restart }; - return CallFunctionSync(&StartTimerApcProc, &ti); -} - -static int StopTimer(int timer) -{ - int res = 0; - - mir_cslock lck(GenTimerCS); - log_debug(0, "StopTimer %d", timer); - log_debug(0, "ack: %u, chk: %u, aft: %u, cnt: %u, con: %u", processAckTimerId, checkConnectionTimerId, afterCheckTimerId, checkContinTimerId, checkConnectingTimerId); - - if (timer & IDT_PROCESSACK) { - if (processAckTimerId == 0) - res = 0; - else { - KillTimer(nullptr, processAckTimerId); - processAckTimerId = 0; - res = 1; - } - } - - if (timer & IDT_CHECKCONN) { - if (checkConnectionTimerId == 0) - res = 0 || res; - else { - KillTimer(nullptr, checkConnectionTimerId); - checkConnectionTimerId = 0; - res = 1; - } - } - - if (timer & IDT_AFTERCHECK) { - if (afterCheckTimerId == 0) - res = 0 || res; - else { - KillTimer(nullptr, afterCheckTimerId); - afterCheckTimerId = 0; - res = 1; - } - } - - if (timer & IDT_CHECKCONTIN) { - if (checkContinTimerId == 0) - res = 0 || res; - else { - KillTimer(nullptr, checkContinTimerId); - checkContinTimerId = 0; - res = 1; - } - } - - if (timer & IDT_CHECKCONNECTING) { - if (checkConnectingTimerId == 0) - res = 0 || res; - else { - KillTimer(nullptr, checkConnectingTimerId); - checkConnectingTimerId = 0; - res = 1; - } - } - - log_debug(0, "ack: %u, chk: %u, aft: %u, cnt: %u, con: %u", processAckTimerId, checkConnectionTimerId, afterCheckTimerId, checkContinTimerId, checkConnectingTimerId); - log_debug(0, "StopTimer done %d", res); - return res; -} - -static int ProcessProtoAck(WPARAM, LPARAM lParam) -{ - ACKDATA *ack = (ACKDATA*)lParam; - if (ack->type != ACKTYPE_STATUS && ack->type != ACKTYPE_LOGIN) - return 0; - - char dbSetting[128]; - mir_snprintf(dbSetting, "%s_enabled", ack->szModule); - if (!KSPlugin.getByte(dbSetting, 1)) - return 0; - - if (ack->type == ACKTYPE_STATUS && ack->result == ACKRESULT_SUCCESS) { - for (auto &it : protoList) - if (!mir_strcmp(it->m_szName, ack->szModule)) - it->lastStatusAckTime = GetTickCount(); - - StartTimer(IDT_PROCESSACK, 0, FALSE); - return 0; - } - - if (ack->type == ACKTYPE_LOGIN) { - if (ack->lParam == LOGINERR_OTHERLOCATION) { - for (auto &it : protoList) { - if (!mir_strcmp(ack->szModule, it->m_szName)) { - it->AssignStatus(ID_STATUS_OFFLINE); - if (KSPlugin.getByte(SETTING_CNCOTHERLOC, 0)) { - StopTimer(IDT_PROCESSACK); - for (auto &jt : protoList) - jt->AssignStatus(ID_STATUS_OFFLINE); - } - - NotifyEventHooks(hConnectionEvent, (WPARAM)KS_CONN_STATE_OTHERLOCATION, (LPARAM)it->m_szName); - ProcessPopup(KS_CONN_STATE_OTHERLOCATION, (LPARAM)ack->szModule); - } - } - } - else if (ack->result == ACKRESULT_FAILED) { - // login failed - NotifyEventHooks(hConnectionEvent, (WPARAM)KS_CONN_STATE_LOGINERROR, (LPARAM)ack->szModule); - - switch (KSPlugin.getByte(SETTING_LOGINERR, LOGINERR_NOTHING)) { - case LOGINERR_CANCEL: - log_info(0, "KeepStatus: cancel on login error (%s)", ack->szModule); - for (auto &it : protoList) - if (!mir_strcmp(ack->szModule, it->m_szName)) - it->AssignStatus(ID_STATUS_OFFLINE); - - ProcessPopup(KS_CONN_STATE_LOGINERROR, (LPARAM)ack->szModule); - StopChecking(); - break; - - case LOGINERR_SETDELAY: - { - int newDelay = 1000 * KSPlugin.getDword(SETTING_LOGINERR_DELAY, DEFAULT_MAXDELAY); - log_info(0, "KeepStatus: set delay to %d ms on login error (%s)", newDelay, ack->szModule); - StartTimer(IDT_CHECKCONN, newDelay, TRUE); - } - ProcessPopup(KS_CONN_STATE_LOGINERROR, (LPARAM)ack->szModule); - break; - - default: - case LOGINERR_NOTHING: - StartTimer(IDT_PROCESSACK, 0, FALSE); - break; - } - } - } - - return 0; -} - -static VOID CALLBACK CheckConnectingTimer(HWND, UINT, UINT_PTR, DWORD) -{ - StopTimer(IDT_CHECKCONNECTING); - - for (auto &it : protoList) { - int curStatus = it->GetStatus(); - if (IsStatusConnecting(curStatus)) { // connecting - int maxConnectingTime = KSPlugin.getDword(SETTING_MAXCONNECTINGTIME, 0); - if (maxConnectingTime > 0) { - if ((unsigned int)maxConnectingTime <= ((GetTickCount() - it->lastStatusAckTime) / 1000)) { - // set offline - log_info(0, "KeepStatus: %s is too long connecting; setting offline", it->m_szName); - CallProtoService(it->m_szName, PS_SETSTATUS, (WPARAM)ID_STATUS_OFFLINE, 0); - } - } - } - } -} - -static VOID CALLBACK CheckAckStatusTimer(HWND, UINT, UINT_PTR, DWORD) -{ - bool needChecking = false; - - StopTimer(IDT_PROCESSACK); - for (auto &it : protoList) { - int curStatus = it->GetStatus(); - int newStatus = Proto_GetStatus(it->m_szName); - // ok, np - if (curStatus == ID_STATUS_CURRENT || curStatus == ID_STATUS_DISABLED || curStatus == newStatus || newStatus > MAX_STATUS) - continue; - - if (IsStatusConnecting(newStatus)) { // connecting - int maxConnectingTime = KSPlugin.getDword(SETTING_MAXCONNECTINGTIME, 0); - if (maxConnectingTime > 0) - StartTimer(IDT_CHECKCONNECTING, (maxConnectingTime * 1000 - (GetTickCount() - it->lastStatusAckTime)), FALSE); - } - // keepstatus' administration was wrong! - else if (newStatus != ID_STATUS_OFFLINE) - it->AssignStatus(newStatus); - - // connection lost - else if (newStatus == ID_STATUS_OFFLINE) {// start checking connection - if (!StartTimer(IDT_CHECKCONN, -1, FALSE)) { /* check if not already checking */ - needChecking = true; - log_info(0, "KeepStatus: connection lost! (%s)", it->m_szName); - NotifyEventHooks(hConnectionEvent, (WPARAM)KS_CONN_STATE_LOST, (LPARAM)it->m_szName); - ProcessPopup(KS_CONN_STATE_LOST, (LPARAM)it->m_szName); - } - } - } - - if (needChecking) - StartTimer(IDT_CHECKCONN, initDelay, FALSE); -} - -static VOID CALLBACK CheckConnectionTimer(HWND, UINT, UINT_PTR, DWORD) -{ - log_debug(0, "CheckConnectionTimer"); - bool setStatus = false; - - for (auto &it : protoList) { - int realStatus = Proto_GetStatus(it->m_szName); - int shouldBeStatus = it->GetStatus(); - if (shouldBeStatus == ID_STATUS_LAST) - shouldBeStatus = it->m_lastStatus; - if (shouldBeStatus == ID_STATUS_DISABLED) - continue; - if ((shouldBeStatus != realStatus) && (realStatus == ID_STATUS_OFFLINE) || (realStatus < MIN_STATUS)) { - setStatus = true; - break; - } - } - - // one of the status was wrong - if (setStatus && (maxRetries == -1 || retryCount < maxRetries)) { - if (increaseExponential) - currentDelay = min(2 * currentDelay, maxDelay); - - if (((KSPlugin.getByte(SETTING_CHKINET, 0)) && (!InternetGetConnectedState(nullptr, 0))) || ((KSPlugin.getByte(SETTING_BYPING, FALSE)) && (!bLastPingResult))) { - // no network - NotifyEventHooks(hConnectionEvent, (WPARAM)KS_CONN_STATE_RETRYNOCONN, (LPARAM)retryCount + 1); - ProcessPopup(KS_CONN_STATE_RETRYNOCONN, 0); - } - else { - NotifyEventHooks(hConnectionEvent, (WPARAM)KS_CONN_STATE_RETRY, (LPARAM)retryCount + 1); - SetCurrentStatus(); // set the status - } - retryCount += 1; - StartTimer(IDT_AFTERCHECK, min(currentDelay, AFTERCHECK_DELAY) / 2, FALSE); - StartTimer(IDT_CHECKCONN, currentDelay, TRUE); // restart this timer - } - else // all status set ok already, or stop checking - StopChecking(); - - log_debug(0, "CheckConnectionTimer done"); -} - -static int StopChecking() -{ - StopTimer(IDT_CHECKCONN | IDT_PROCESSACK | IDT_AFTERCHECK | IDT_CHECKCONNECTING); - - BOOL isOk = TRUE; - for (auto &it : protoList) { - int curStatus = it->GetStatus(); - int newStatus = Proto_GetStatus(it->m_szName); - if (newStatus != curStatus) { - it->AssignStatus(newStatus); - isOk = FALSE; - break; - } - } - - NotifyEventHooks(hConnectionEvent, (WPARAM)KS_CONN_STATE_STOPPEDCHECKING, (LPARAM)isOk); - ProcessPopup(KS_CONN_STATE_STOPPEDCHECKING, (LPARAM)isOk); - log_info(0, "KeepStatus: stop checking (%s)", isOk ? "success" : "failure"); - retryCount = 0; - currentDelay = initDelay; - - return 0; -} - -static VOID CALLBACK AfterCheckTimer(HWND, UINT, UINT_PTR, DWORD) -{ - // after each connection check, this function is called to see if connection was recovered - StopTimer(IDT_AFTERCHECK); - - bool setStatus = false; - - for (auto &it : protoList) { - int realStatus = Proto_GetStatus(it->m_szName); - int shouldBeStatus = it->GetStatus(); - if (shouldBeStatus == ID_STATUS_LAST) // this should never happen - shouldBeStatus = it->m_lastStatus; - if (shouldBeStatus == ID_STATUS_DISABLED) // (on ignoring proto) - continue; - if ((shouldBeStatus != realStatus) && (realStatus == ID_STATUS_OFFLINE) || (realStatus < MIN_STATUS)) - setStatus = true; - } - - if (!setStatus || retryCount == maxRetries) - StopChecking(); -} - -static void CheckContinuouslyFunction(void *) -{ - Thread_SetName("KeepStatus: CheckContinuouslyFunction"); - - static int pingFailures = 0; - - // one at the time is enough, do it the 'easy' way - mir_cslock lck(CheckContinueslyCS); - - // do a ping, even if reconnecting - bool doPing = false; - for (auto &it : protoList) { - int shouldBeStatus = it->GetStatus(); - if (shouldBeStatus == ID_STATUS_LAST) - shouldBeStatus = it->m_lastStatus; - - if (shouldBeStatus == ID_STATUS_DISABLED) - continue; - - if (shouldBeStatus != ID_STATUS_OFFLINE) { - log_debug(0, "CheckContinuouslyFunction: %s should be %d", it->m_szName, shouldBeStatus); - doPing = true; - } - } - - if (!doPing) { - log_debug(0, "CheckContinuouslyFunction: All protocols should be offline, no need to check connection"); - return; - } - - BOOL ping = KSPlugin.getByte(SETTING_BYPING, FALSE); - if (ping) { - DBVARIANT dbv; - if (db_get_s(0, KSMODULENAME, SETTING_PINGHOST, &dbv)) - ping = FALSE; - else { - char *start, *end; - char host[MAX_PATH]; - DWORD *addr; - struct hostent *hostent; - char reply[sizeof(ICMP_ECHO_REPLY) + 8]; - - bLastPingResult = FALSE; - HANDLE hICMPFile = (HANDLE)IcmpCreateFile(); - if (hICMPFile == INVALID_HANDLE_VALUE) { - bLastPingResult = TRUE; - log_info(0, "KeepStatus: icmp.dll error (2)"); - } - if (bLastPingResult == FALSE) { - start = dbv.pszVal; - while ((*start != '\0') && (!bLastPingResult)) { - end = start; - while ((*end != ' ') && (*end != '\0')) - end++; - memset(host, '\0', sizeof(host)); - strncpy(host, start, end - start); - hostent = gethostbyname(host); - if (hostent != nullptr) { - addr = (DWORD *)(*hostent->h_addr_list); - bLastPingResult = (IcmpSendEcho(hICMPFile, *addr, nullptr, 0, nullptr, reply, sizeof(ICMP_ECHO_REPLY) + 8, 5000) != 0); - - if (bLastPingResult) - pingFailures = 0; - else - pingFailures++; - - log_debug(0, "CheckContinuouslyFunction: pinging %s (result: %d/%d)", host, bLastPingResult, pingFailures); - } - else log_debug(0, "CheckContinuouslyFunction: unable to resolve %s", host); - - start = end; - while (*start == ' ') - start++; - } - } - IcmpCloseHandle(hICMPFile); - } - db_free(&dbv); - } - - if (StartTimer(IDT_CHECKCONN, -1, FALSE)) // already connecting, leave - return; - - if (((!ping) && (!InternetGetConnectedState(nullptr, 0))) || ((ping) && (!bLastPingResult) && (pingFailures >= db_get_w(0, KSMODULENAME, SETTING_PINGCOUNT, DEFAULT_PINGCOUNT)))) { - pingFailures = 0; - - for (auto &pa : Accounts()) { - if (!IsSuitableProto(pa)) - continue; - - if (IsStatusConnecting(pa->iRealStatus)) { - log_debug(0, "CheckContinuouslyFunction: %s is connecting", pa->szModuleName); - continue; // connecting, leave alone - } - if (IsProtocolEnabledService(0, (LPARAM)pa->szModuleName)) { - log_debug(0, "CheckContinuouslyFunction: forcing %s offline", pa->szModuleName); - CallProtoService(pa->szModuleName, PS_SETSTATUS, (WPARAM)ID_STATUS_OFFLINE, 0); - } - } - - if (StartTimer(IDT_CHECKCONN | IDT_PROCESSACK, -1, FALSE)) {// are our 'set offlines' noticed? - log_debug(0, "CheckContinuouslyFunction: currently checking"); - return; - } - - log_info(0, "KeepStatus: connection lost! (continuesly check)"); - NotifyEventHooks(hConnectionEvent, (WPARAM)KS_CONN_STATE_LOST, 0); - ProcessPopup(KS_CONN_STATE_LOST, 0); - maxRetries = KSPlugin.getByte(SETTING_MAXRETRIES, 0); - if (maxRetries == 0) - maxRetries = -1; - StartTimer(IDT_CHECKCONN, initDelay, FALSE); - } -} - -static VOID CALLBACK CheckContinueslyTimer(HWND, UINT, UINT_PTR, DWORD) -{ - if (KSPlugin.getByte(SETTING_BYPING, FALSE)) - mir_forkthread(CheckContinuouslyFunction); - else - CheckContinuouslyFunction(nullptr); -} - -///////////////////////////////////////////////////////////////////////////////////////// -// popup - -static INT_PTR ShowPopup(const wchar_t *msg, HICON hIcon) -{ - POPUPDATAW ppd; - ppd.lchIcon = hIcon; - wcsncpy(ppd.lpwzContactName, TranslateT("Keep status"), MAX_CONTACTNAME); - wcsncpy(ppd.lpwzText, msg, MAX_SECONDLINE); - if (KSPlugin.getByte(SETTING_POPUP_USEWINCOLORS, 0)) { - ppd.colorBack = GetSysColor(COLOR_BTNFACE); - ppd.colorText = GetSysColor(COLOR_WINDOWTEXT); - } - else if (!KSPlugin.getByte(SETTING_POPUP_USEDEFCOLORS, 0)) { - ppd.colorBack = KSPlugin.getDword(SETTING_POPUP_BACKCOLOR, 0xAAAAAA); - ppd.colorText = KSPlugin.getDword(SETTING_POPUP_TEXTCOLOR, 0x0000CC); - } - ppd.PluginWindowProc = KSPopupDlgProc; - - switch (KSPlugin.getByte(SETTING_POPUP_DELAYTYPE, POPUP_DELAYFROMPU)) { - case POPUP_DELAYCUSTOM: - ppd.iSeconds = (int)KSPlugin.getDword(SETTING_POPUP_TIMEOUT, 0); - if (ppd.iSeconds == 0) - ppd.iSeconds = currentDelay / 1000 - 1; - break; - - case POPUP_DELAYPERMANENT: - ppd.iSeconds = -1; - break; - - case POPUP_DELAYFROMPU: - default: - ppd.iSeconds = 0; - break; - } - return (INT_PTR)PUAddPopupW(&ppd); -} - -static wchar_t* GetHumanName(LPARAM lParam) -{ - PROTOACCOUNT *ProtoAccount = Proto_GetAccount((char*)lParam); - return (ProtoAccount != nullptr) ? ProtoAccount->tszAccountName : TranslateT("Protocol"); -} - -static int ProcessPopup(int reason, LPARAM lParam) -{ - if (!KSPlugin.getByte(SETTING_SHOWCONNECTIONPOPUPS, FALSE)) - return -1; - - HICON hIcon = nullptr; - CMStringW wszText; - - switch (reason) { - case KS_CONN_STATE_OTHERLOCATION: // lParam = 1 proto - if (!KSPlugin.getByte(SETTING_PUOTHER, TRUE)) - return -1; - - hIcon = Skin_LoadProtoIcon((char*)lParam, SKINICON_STATUS_OFFLINE); - wszText.Format(TranslateT("%s connected from another location"), GetHumanName(lParam)); - break; - - case KS_CONN_STATE_LOGINERROR: // lParam = 1 proto - if (!KSPlugin.getByte(SETTING_PUOTHER, TRUE)) - return -1; - - hIcon = Skin_LoadProtoIcon((char*)lParam, SKINICON_STATUS_OFFLINE); - if (KSPlugin.getByte(SETTING_LOGINERR, LOGINERR_NOTHING) == LOGINERR_CANCEL) - wszText.Format(TranslateT("%s login error, cancel reconnecting"), GetHumanName(lParam)); - else if (KSPlugin.getByte(SETTING_LOGINERR, LOGINERR_NOTHING) == LOGINERR_SETDELAY) - wszText.Format(TranslateT("%s login error (next retry (%d) in %d s)"), GetHumanName(lParam), retryCount + 1, KSPlugin.getDword(SETTING_LOGINERR_DELAY, DEFAULT_MAXDELAY)); - else - return -1; - break; - - case KS_CONN_STATE_LOST: // lParam = 1 proto, or nullptr - if (!KSPlugin.getByte(SETTING_PUCONNLOST, TRUE)) - return -1; - - if (lParam) { // указатель на имя модуля. - hIcon = Skin_LoadProtoIcon((char*)lParam, SKINICON_STATUS_OFFLINE); - wszText.Format(TranslateT("%s status error (next retry (%d) in %d s)"), GetHumanName(lParam), retryCount + 1, currentDelay / 1000); - } - else wszText.Format(TranslateT("Status error (next retry (%d) in %d s)"), retryCount + 1, currentDelay / 1000); - break; - - case KS_CONN_STATE_RETRY: // lParam = PROTOCOLSETTINGEX** - if (!KSPlugin.getByte(SETTING_PUCONNRETRY, TRUE)) - return -1; - - if (lParam) { - PROTOCOLSETTINGEX **ps = (PROTOCOLSETTINGEX **)lParam; - - if (retryCount == maxRetries - 1) - wszText.Format(TranslateT("Resetting status... (last try (%d))"), retryCount + 1); - else - wszText.Format(TranslateT("Resetting status... (next retry (%d) in %d s)"), retryCount + 2, currentDelay / 1000); - wszText.Append(L"\r\n"); - for (int i = 0; i < protoList.getCount(); i++) { - PROTOCOLSETTINGEX *p = ps[i]; - if (p->m_status == ID_STATUS_DISABLED) - continue; - - if (mir_wstrlen(p->m_tszAccName) > 0) - if (KSPlugin.getByte(SETTING_PUSHOWEXTRA, TRUE)) - wszText.AppendFormat(TranslateT("%s\t(will be set to %s)\r\n"), p->m_tszAccName, Clist_GetStatusModeDescription(p->m_status, 0)); - } - - hIcon = Skin_LoadProtoIcon(ps[0]->m_szName, SKINICON_STATUS_OFFLINE); - wszText.TrimRight(); - } - break; - - case KS_CONN_STATE_RETRYNOCONN: // lParam = nullptr - if (!KSPlugin.getByte(SETTING_PUOTHER, TRUE)) - return -1; - - if (retryCount == maxRetries - 1) - wszText.Format(TranslateT("No internet connection seems available... (last try (%d))"), retryCount + 1); - else - wszText.Format(TranslateT("No internet connection seems available... (next retry (%d) in %d s)"), retryCount + 2, currentDelay / 1000); - break; - - case KS_CONN_STATE_STOPPEDCHECKING: // lParam == BOOL succes - if (!KSPlugin.getByte(SETTING_PURESULT, TRUE)) - return -1; - - if (lParam) { - hIcon = Skin_LoadIcon(SKINICON_STATUS_ONLINE); - wszText.Format(TranslateT("Status was set ok")); - } - else wszText.Format(TranslateT("Giving up")); - break; - } - if (hIcon == nullptr) - hIcon = Skin_LoadIcon(SKINICON_STATUS_OFFLINE); - - Netlib_Logf(0, "KeepStatus: %s", wszText.c_str()); - return ShowPopup(wszText, hIcon); -} - -LRESULT CALLBACK KSPopupDlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) -{ - switch (message) { - case WM_CONTEXTMENU: // right - case WM_COMMAND: // left - switch (db_get_b(0, KSMODULENAME, - (message == WM_COMMAND) ? SETTING_POPUP_LEFTCLICK : SETTING_POPUP_RIGHTCLICK, - POPUP_ACT_CLOSEPOPUP)) { - case POPUP_ACT_CANCEL: - // cancel timer - StopChecking(); - PUDeletePopup(hWnd); - break; - - case POPUP_ACT_CLOSEPOPUP: - // close the popup - PUDeletePopup(hWnd); - break; - } - break; - } - - return DefWindowProc(hWnd, message, wParam, lParam); -} - -///////////////////////////////////////////////////////////////////////////////////////// -// services - -INT_PTR StopReconnectingService(WPARAM, LPARAM) -{ - int ret = StartTimer(IDT_CHECKCONN | IDT_AFTERCHECK, -1, FALSE); - StopChecking(); - return ret; -} - -INT_PTR EnableProtocolService(WPARAM wParam, LPARAM lParam) -{ - char *szProto = (char *)lParam; - if (szProto == nullptr) - return -1; - - char dbSetting[128]; - mir_snprintf(dbSetting, "%s_enabled", szProto); - if (!KSPlugin.getByte(dbSetting, 1)) // 'hard' disabled - return -1; - - int ret = -2; - for (auto &it : protoList) { - if (!mir_strcmp(szProto, it->m_szName)) { - if (wParam) - it->AssignStatus(Proto_GetStatus(it->m_szName)); - else - it->AssignStatus(ID_STATUS_DISABLED); - - ret = 0; - break; - } - } - return ret; -} - -INT_PTR IsProtocolEnabledService(WPARAM, LPARAM lParam) -{ - char *szProto = (char *)lParam; - - char dbSetting[128]; - mir_snprintf(dbSetting, "%s_enabled", szProto); - if (!KSPlugin.getByte(dbSetting, 1)) - return FALSE; - - for (auto &it : protoList) - if (!mir_strcmp(szProto, it->m_szName)) - return it->GetStatus() != ID_STATUS_DISABLED; - - return FALSE; -} - -INT_PTR AnnounceStatusChangeService(WPARAM, LPARAM lParam) -{ - PROTOCOLSETTINGEX *newSituation = (PROTOCOLSETTINGEX *)lParam; - log_info(0, "Another plugin announced a status change to %d for %s", newSituation->m_status, newSituation->m_szName == nullptr ? "all" : newSituation->m_szName); - - for (auto &it : protoList) - if (!mir_strcmp(it->m_szName, newSituation->m_szName)) - it->AssignStatus(newSituation->m_status, newSituation->m_lastStatus, newSituation->m_szMsg); - - return 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// -// window for suspend - -static DWORD CALLBACK MessageWndProc(HWND, UINT msg, WPARAM wParam, LPARAM lParam) -{ - static TProtoSettings *ps = nullptr; - - switch (msg) { - case WM_POWERBROADCAST: - switch (wParam) { - case PBT_APMSUSPEND: - log_info(0, "KeepStatus: suspend state detected: %08X %08X", wParam, lParam); - if (ps == nullptr) { - ps = new TProtoSettings(protoList); - for (auto &it : *ps) - EnableProtocolService(0, (LPARAM)it->m_szName); - - // set proto's offline, the clist will not try to reconnect in that case - Clist_SetStatusMode(ID_STATUS_OFFLINE); - } - break; - - case PBT_APMRESUMESUSPEND: - case PBT_APMRESUMECRITICAL: - // case PBT_APMRESUMEAUTOMATIC: ? - log_info(0, "KeepStatus: resume from suspend state"); - if (ps != nullptr) { - for (auto &it : *ps) { - SMProto *p = protoList.find(it); - if (p) - p->AssignStatus(it->m_status, it->m_lastStatus, it->m_szMsg); - } - delete ps; - ps = nullptr; - } - StartTimer(IDT_PROCESSACK, 0, FALSE); - break; - } - break; - - case WM_DESTROY: - if (ps != nullptr) { - delete ps; - ps = nullptr; - } - break; - } - - return TRUE; -} - -///////////////////////////////////////////////////////////////////////////////////////// -// init stuff - -static int onShutdown(WPARAM, LPARAM) -{ - KSUnloadOptions(); - return 0; -} - -///////////////////////////////////////////////////////////////////////////////////////// - -int KSModuleLoaded(WPARAM, LPARAM) -{ - hMessageWindow = nullptr; - KSLoadOptions(); - - hEvents[0] = HookEvent(ME_OPT_INITIALISE, KeepStatusOptionsInit); - hEvents[1] = HookEvent(ME_SYSTEM_PRESHUTDOWN, onShutdown); - return 0; -} - -void KeepStatusLoad() -{ - if (g_bMirandaLoaded) - KSModuleLoaded(0, 0); - else - HookEvent(ME_SYSTEM_MODULESLOADED, KSModuleLoaded); - - hConnectionEvent = CreateHookableEvent(ME_KS_CONNECTIONEVENT); - - hServices[0] = CreateServiceFunction(MS_KS_STOPRECONNECTING, StopReconnectingService); - hServices[1] = CreateServiceFunction(MS_KS_ENABLEPROTOCOL, EnableProtocolService); - hServices[2] = CreateServiceFunction(MS_KS_ISPROTOCOLENABLED, IsProtocolEnabledService); - hServices[3] = CreateServiceFunction(MS_KS_ANNOUNCESTATUSCHANGE, AnnounceStatusChangeService); -} - -void KeepStatusUnload() -{ - if (g_bMirandaLoaded) - onShutdown(0, 0); - - KillModuleOptions(&KSPlugin); - - for (auto &it : hServices) { - DestroyServiceFunction(it); - it = nullptr; - } - - for (auto &it : hEvents) { - UnhookEvent(it); - it = nullptr; - } - - DestroyHookableEvent(hConnectionEvent); hConnectionEvent = nullptr; -} diff --git a/plugins/StatusManager/src/ks_main.cpp b/plugins/StatusManager/src/ks_main.cpp new file mode 100644 index 0000000000..758b2d090a --- /dev/null +++ b/plugins/StatusManager/src/ks_main.cpp @@ -0,0 +1,1100 @@ +/* + KeepStatus 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 + */ + +#include "stdafx.h" + +CFakePlugin KSPlugin(KSMODULENAME); + +static HANDLE hConnectionEvent = nullptr; +static HANDLE hServices[4], hEvents[2]; + +static mir_cs GenTimerCS, GenStatusCS, CheckContinueslyCS; + +static HANDLE hProtoAckHook = nullptr; +static HANDLE hStatusChangeHook = nullptr; +static HANDLE hCSStatusChangeHook = nullptr; +static HANDLE hCSStatusChangeExHook = nullptr; + +static HWND hMessageWindow = nullptr; + +static UINT_PTR checkConnectionTimerId = 0; +static UINT_PTR afterCheckTimerId = 0; +static UINT_PTR processAckTimerId = 0; +static UINT_PTR checkContinTimerId = 0; +static UINT_PTR checkConnectingTimerId = 0; +static int retryCount = 0; +static BOOL bLastPingResult = TRUE; +// variables (options) +static int maxRetries = 0; +static int initDelay = 0; +static int currentDelay = 0; +static int maxDelay = 0; +static int ackDelay = 500; +static int increaseExponential = 0; +static int showConnectionPopups = 0; +// prototypes +static int StartTimer(int timer, int timeout, BOOL restart); +static int StopTimer(int timer); +static int ProcessProtoAck(WPARAM wParam, LPARAM lParam); +static VOID CALLBACK CheckConnectingTimer(HWND hwnd, UINT message, UINT_PTR idEvent, DWORD dwTime); +static VOID CALLBACK CheckAckStatusTimer(HWND hwnd, UINT message, UINT_PTR idEvent, DWORD dwTime); +static int StatusChange(WPARAM wParam, LPARAM lParam); +static int CSStatusChange(WPARAM wParam, LPARAM lParam); +static int CSStatusChangeEx(WPARAM wParam, LPARAM lParam); +static VOID CALLBACK CheckConnectionTimer(HWND hwnd, UINT message, UINT_PTR idEvent, DWORD dwTime); +static int StopChecking(); +static VOID CALLBACK AfterCheckTimer(HWND hwnd, UINT message, UINT_PTR idEvent, DWORD dwTime); +static VOID CALLBACK CheckContinueslyTimer(HWND hwnd, UINT message, UINT_PTR idEvent, DWORD dwTime); +INT_PTR IsProtocolEnabledService(WPARAM wParam, LPARAM lParam); + +static int ProcessPopup(int reason, LPARAM lParam); +LRESULT CALLBACK KSPopupDlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); +static DWORD CALLBACK MessageWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); + +// options.c +extern int KeepStatusOptionsInit(WPARAM wparam, LPARAM); + +///////////////////////////////////////////////////////////////////////////////////////// + +void KSUnloadOptions() +{ + UnhookEvent(hProtoAckHook); + UnhookEvent(hStatusChangeHook); + UnhookEvent(hCSStatusChangeHook); + UnhookEvent(hCSStatusChangeExHook); + hProtoAckHook = hStatusChangeHook = hCSStatusChangeHook = hCSStatusChangeExHook = nullptr; + + if (IsWindow(hMessageWindow)) + DestroyWindow(hMessageWindow); + + if (StartTimer(IDT_CHECKCONTIN, -1, FALSE)) + WSACleanup(); + + StopTimer(IDT_CHECKCONN | IDT_PROCESSACK | IDT_AFTERCHECK | IDT_CHECKCONTIN | IDT_CHECKCONNECTING); +} + +int KSLoadOptions() +{ + KSUnloadOptions(); + + if (KSPlugin.getByte(SETTING_CHECKCONNECTION, FALSE)) { + if (KSPlugin.getByte(SETTING_CONTCHECK, FALSE)) { + if (KSPlugin.getByte(SETTING_BYPING, FALSE)) { + WSADATA wsaData; + WSAStartup(MAKEWORD(2, 2), &wsaData); + } + StartTimer(IDT_CHECKCONTIN, 0, FALSE); + } + increaseExponential = KSPlugin.getByte(SETTING_INCREASEEXPONENTIAL, FALSE); + currentDelay = initDelay = 1000 * KSPlugin.getDword(SETTING_INITDELAY, DEFAULT_INITDELAY); + maxDelay = 1000 * KSPlugin.getDword(SETTING_MAXDELAY, DEFAULT_MAXDELAY); + maxRetries = KSPlugin.getByte(SETTING_MAXRETRIES, 0); + if (maxRetries == 0) + maxRetries = -1; + hProtoAckHook = HookEvent(ME_PROTO_ACK, ProcessProtoAck); + hStatusChangeHook = HookEvent(ME_CLIST_STATUSMODECHANGE, StatusChange); + if (ServiceExists(ME_CS_STATUSCHANGE)) + hCSStatusChangeHook = HookEvent(ME_CS_STATUSCHANGE, CSStatusChange); + hCSStatusChangeExHook = HookEvent(ME_CS_STATUSCHANGEEX, CSStatusChangeEx); + if (KSPlugin.getByte(SETTING_CHECKAPMRESUME, 0)) { + if (!IsWindow(hMessageWindow)) { + hMessageWindow = CreateWindowEx(0, L"STATIC", nullptr, 0, 0, 0, 0, 0, nullptr, nullptr, nullptr, nullptr); + SetWindowLongPtr(hMessageWindow, GWLP_WNDPROC, (LONG_PTR)MessageWndProc); + } + } + retryCount = 0; + } + + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +int SMProto::AssignStatus(int iStatus, int iLastStatus, wchar_t *pwszMsg) +{ + if (iStatus < MIN_STATUS || iStatus > MAX_STATUS) + return -1; + if (iStatus != ID_STATUS_OFFLINE && m_status == ID_STATUS_DISABLED) + return -2; + if (!KSPlugin.getByte(SETTING_NOLOCKED, 0) && Proto_GetAccount(m_szName)->IsLocked()) + return -3; + + mir_cslock lck(GenStatusCS); + + char dbSetting[128]; + mir_snprintf(dbSetting, "%s_enabled", m_szName); + m_lastStatus = (iLastStatus == 0) ? m_status : iLastStatus; + if (!KSPlugin.getByte(dbSetting, 1)) + m_status = ID_STATUS_DISABLED; + else if (iStatus == ID_STATUS_LAST) + m_status = m_lastStatus; + else + m_status = iStatus; + + log_info(0, "KeepStatus: assigning status %d (%d, %d) to %s", m_status, m_lastStatus, iLastStatus, m_szName); + + if (pwszMsg != nullptr && mir_wstrcmp(pwszMsg, m_szMsg)) { + if (m_szMsg != nullptr) + mir_free(m_szMsg); + + m_szMsg = mir_wstrdup(pwszMsg); + } + else if (pwszMsg != m_szMsg) { + if (m_szMsg != nullptr) + mir_free(m_szMsg); + + m_szMsg = nullptr; + } + return 0; +} + +int SMProto::GetStatus() const +{ + switch (m_status) { + case ID_STATUS_CURRENT: + return Proto_GetStatus(m_szName); + case ID_STATUS_LAST: + return m_lastStatus; + default: + return m_status; + } +} + +static int SetCurrentStatus() +{ + TProtoSettings ps(protoList); + for (auto &p : ps) { + int realStatus = Proto_GetStatus(p->m_szName); + int curStatus = p->GetStatus(); + if (curStatus == ID_STATUS_DISABLED) + continue; + if (curStatus == realStatus) { + p->m_status = ID_STATUS_DISABLED; + continue; + } + + log_info(0, "KeepStatus: status for %s differs: stored = %d, real = %d", p->m_szName, curStatus, realStatus); + + // force offline before reconnecting? + if (realStatus != ID_STATUS_OFFLINE && KSPlugin.getByte(SETTING_FIRSTOFFLINE, FALSE)) { + log_info(0, "KeepStatus: Setting %s offline before making a new connection attempt", p->m_szName); + CallProtoService(p->m_szName, PS_SETSTATUS, (WPARAM)ID_STATUS_OFFLINE, 0); + } + } + ProcessPopup(KS_CONN_STATE_RETRY, (LPARAM)ps.getArray()); + return SetStatusEx(ps); +} + +static int StatusChange(WPARAM wParam, LPARAM lParam) +{ + char *szProto = (char *)lParam; + if (szProto == nullptr) { // global status change + for (auto &it : protoList) + it->AssignStatus(wParam, 0, it->m_szMsg); + } + else { + for (auto &it : protoList) + if (!mir_strcmp(it->m_szName, szProto)) + it->AssignStatus(wParam, 0, it->m_szMsg); + } + + return 0; +} + +static int CSStatusChange(WPARAM wParam, LPARAM) +{ + // the status was changed by commonstatus (old) + if (wParam != 0) { + PROTOCOLSETTING **protoSettings = *(PROTOCOLSETTING***)wParam; + if (protoSettings == nullptr) + return -1; + + for (int i = 0; i < protoList.getCount(); i++) { + auto psi = protoSettings[i]; + if (psi->szName == nullptr) + continue; + + for (auto &it : protoList) { + if (it->m_szName == nullptr) + continue; + + if (!mir_strcmp(psi->szName, it->m_szName)) + it->AssignStatus(psi->status, psi->lastStatus, it->m_szMsg); + } + } + } + + return 0; +} + +static int CSStatusChangeEx(WPARAM wParam, LPARAM pCount) +{ + // the status was changed by commonstatus (new) + if (wParam != 0) { + PROTOCOLSETTINGEX** protoSettings = *(PROTOCOLSETTINGEX***)wParam; + if (protoSettings == nullptr) + return -1; + + for (int i = 0; i < pCount; i++) { + auto psi = protoSettings[i]; + if (psi->m_szName == nullptr) + continue; + + for (auto &it : protoList) { + if (it->m_szName == nullptr) + continue; + + if (!mir_strcmp(psi->m_szName, it->m_szName)) + it->AssignStatus(psi->m_status, psi->m_lastStatus, psi->m_szMsg); + } + } + } + + return 0; +} + +struct TimerInfo +{ + int timer; + int timeout; + BOOL restart; +}; + +static INT_PTR CALLBACK StartTimerApcProc(void *param) +{ + TimerInfo *ti = (TimerInfo *)param; + int res = 0; + + mir_cslock lck(GenTimerCS); + log_debug(0, "StartTimer: %d, %d, %d", ti->timer, ti->timeout, ti->restart); + log_debug(0, "ack: %u, chk: %u, aft: %u, cnt: %u, con: %u", processAckTimerId, checkConnectionTimerId, afterCheckTimerId, checkContinTimerId, checkConnectingTimerId); + if (ti->timer & IDT_PROCESSACK) { + res = (processAckTimerId == 0) ? 0 : 1; + if (((processAckTimerId == 0) && (checkConnectionTimerId == 0)) || (ti->restart)) { + if (ti->timeout != -1) { + if (ti->restart) + KillTimer(nullptr, processAckTimerId); + if (ti->timeout == 0) + ti->timeout = ackDelay; + processAckTimerId = SetTimer(nullptr, 0, ti->timeout, CheckAckStatusTimer); + } + } + } + + if (ti->timer & IDT_CHECKCONN) { + res = (checkConnectionTimerId == 0 ? 0 : 1) || res; + if ((checkConnectionTimerId == 0) || (ti->restart)) { + if (ti->timeout != -1) { + if (ti->restart) + KillTimer(nullptr, checkConnectionTimerId); + + if (ti->timeout == 0) + ti->timeout = initDelay; + checkConnectionTimerId = SetTimer(nullptr, 0, ti->timeout, CheckConnectionTimer); + } + } + } + + if (ti->timer & IDT_AFTERCHECK) { + res = (afterCheckTimerId == 0 ? 0 : 1) || res; + if ((afterCheckTimerId == 0) || (ti->restart)) { + if (ti->timeout != -1) { + if (ti->restart) + KillTimer(nullptr, afterCheckTimerId); + + if (ti->timeout == 0) + ti->timeout = initDelay / 2; + afterCheckTimerId = SetTimer(nullptr, 0, ti->timeout, AfterCheckTimer); + } + } + } + + if (ti->timer & IDT_CHECKCONTIN) { + res = (checkContinTimerId == 0 ? 0 : 1) || res; + if ((checkContinTimerId == 0) || (ti->restart)) { + if (ti->timeout != -1) { + if (ti->restart) + KillTimer(nullptr, checkContinTimerId); + + if (ti->timeout == 0) + ti->timeout = 1000 * KSPlugin.getDword(SETTING_CNTDELAY, CHECKCONTIN_DELAY); + checkContinTimerId = SetTimer(nullptr, 0, ti->timeout, CheckContinueslyTimer); + } + } + } + + if (ti->timer & IDT_CHECKCONNECTING) { + res = (checkConnectingTimerId == 0 ? 0 : 1) || res; + if ((checkConnectingTimerId == 0) || (ti->restart)) { + if (ti->timeout != -1) { + if (ti->restart) + KillTimer(nullptr, checkConnectingTimerId); + if (ti->timeout == 0) + ti->timeout = initDelay / 2; + checkConnectingTimerId = SetTimer(nullptr, 0, ti->timeout, CheckConnectingTimer); + } + } + } + + log_debug(0, "ack: %u, chk: %u, aft: %u, cnt: %u, con: %u", processAckTimerId, checkConnectionTimerId, afterCheckTimerId, checkContinTimerId, checkConnectingTimerId); + log_debug(0, "StartTimer done %d", res); + return res; +} + +static int StartTimer(int timer, int timeout, BOOL restart) +{ + TimerInfo ti = { timer, timeout, restart }; + return CallFunctionSync(&StartTimerApcProc, &ti); +} + +static int StopTimer(int timer) +{ + int res = 0; + + mir_cslock lck(GenTimerCS); + log_debug(0, "StopTimer %d", timer); + log_debug(0, "ack: %u, chk: %u, aft: %u, cnt: %u, con: %u", processAckTimerId, checkConnectionTimerId, afterCheckTimerId, checkContinTimerId, checkConnectingTimerId); + + if (timer & IDT_PROCESSACK) { + if (processAckTimerId == 0) + res = 0; + else { + KillTimer(nullptr, processAckTimerId); + processAckTimerId = 0; + res = 1; + } + } + + if (timer & IDT_CHECKCONN) { + if (checkConnectionTimerId == 0) + res = 0 || res; + else { + KillTimer(nullptr, checkConnectionTimerId); + checkConnectionTimerId = 0; + res = 1; + } + } + + if (timer & IDT_AFTERCHECK) { + if (afterCheckTimerId == 0) + res = 0 || res; + else { + KillTimer(nullptr, afterCheckTimerId); + afterCheckTimerId = 0; + res = 1; + } + } + + if (timer & IDT_CHECKCONTIN) { + if (checkContinTimerId == 0) + res = 0 || res; + else { + KillTimer(nullptr, checkContinTimerId); + checkContinTimerId = 0; + res = 1; + } + } + + if (timer & IDT_CHECKCONNECTING) { + if (checkConnectingTimerId == 0) + res = 0 || res; + else { + KillTimer(nullptr, checkConnectingTimerId); + checkConnectingTimerId = 0; + res = 1; + } + } + + log_debug(0, "ack: %u, chk: %u, aft: %u, cnt: %u, con: %u", processAckTimerId, checkConnectionTimerId, afterCheckTimerId, checkContinTimerId, checkConnectingTimerId); + log_debug(0, "StopTimer done %d", res); + return res; +} + +static int ProcessProtoAck(WPARAM, LPARAM lParam) +{ + ACKDATA *ack = (ACKDATA*)lParam; + if (ack->type != ACKTYPE_STATUS && ack->type != ACKTYPE_LOGIN) + return 0; + + char dbSetting[128]; + mir_snprintf(dbSetting, "%s_enabled", ack->szModule); + if (!KSPlugin.getByte(dbSetting, 1)) + return 0; + + if (ack->type == ACKTYPE_STATUS && ack->result == ACKRESULT_SUCCESS) { + for (auto &it : protoList) + if (!mir_strcmp(it->m_szName, ack->szModule)) + it->lastStatusAckTime = GetTickCount(); + + StartTimer(IDT_PROCESSACK, 0, FALSE); + return 0; + } + + if (ack->type == ACKTYPE_LOGIN) { + if (ack->lParam == LOGINERR_OTHERLOCATION) { + for (auto &it : protoList) { + if (!mir_strcmp(ack->szModule, it->m_szName)) { + it->AssignStatus(ID_STATUS_OFFLINE); + if (KSPlugin.getByte(SETTING_CNCOTHERLOC, 0)) { + StopTimer(IDT_PROCESSACK); + for (auto &jt : protoList) + jt->AssignStatus(ID_STATUS_OFFLINE); + } + + NotifyEventHooks(hConnectionEvent, (WPARAM)KS_CONN_STATE_OTHERLOCATION, (LPARAM)it->m_szName); + ProcessPopup(KS_CONN_STATE_OTHERLOCATION, (LPARAM)ack->szModule); + } + } + } + else if (ack->result == ACKRESULT_FAILED) { + // login failed + NotifyEventHooks(hConnectionEvent, (WPARAM)KS_CONN_STATE_LOGINERROR, (LPARAM)ack->szModule); + + switch (KSPlugin.getByte(SETTING_LOGINERR, LOGINERR_NOTHING)) { + case LOGINERR_CANCEL: + log_info(0, "KeepStatus: cancel on login error (%s)", ack->szModule); + for (auto &it : protoList) + if (!mir_strcmp(ack->szModule, it->m_szName)) + it->AssignStatus(ID_STATUS_OFFLINE); + + ProcessPopup(KS_CONN_STATE_LOGINERROR, (LPARAM)ack->szModule); + StopChecking(); + break; + + case LOGINERR_SETDELAY: + { + int newDelay = 1000 * KSPlugin.getDword(SETTING_LOGINERR_DELAY, DEFAULT_MAXDELAY); + log_info(0, "KeepStatus: set delay to %d ms on login error (%s)", newDelay, ack->szModule); + StartTimer(IDT_CHECKCONN, newDelay, TRUE); + } + ProcessPopup(KS_CONN_STATE_LOGINERROR, (LPARAM)ack->szModule); + break; + + default: + case LOGINERR_NOTHING: + StartTimer(IDT_PROCESSACK, 0, FALSE); + break; + } + } + } + + return 0; +} + +static VOID CALLBACK CheckConnectingTimer(HWND, UINT, UINT_PTR, DWORD) +{ + StopTimer(IDT_CHECKCONNECTING); + + for (auto &it : protoList) { + int curStatus = it->GetStatus(); + if (IsStatusConnecting(curStatus)) { // connecting + int maxConnectingTime = KSPlugin.getDword(SETTING_MAXCONNECTINGTIME, 0); + if (maxConnectingTime > 0) { + if ((unsigned int)maxConnectingTime <= ((GetTickCount() - it->lastStatusAckTime) / 1000)) { + // set offline + log_info(0, "KeepStatus: %s is too long connecting; setting offline", it->m_szName); + CallProtoService(it->m_szName, PS_SETSTATUS, (WPARAM)ID_STATUS_OFFLINE, 0); + } + } + } + } +} + +static VOID CALLBACK CheckAckStatusTimer(HWND, UINT, UINT_PTR, DWORD) +{ + bool needChecking = false; + + StopTimer(IDT_PROCESSACK); + for (auto &it : protoList) { + int curStatus = it->GetStatus(); + int newStatus = Proto_GetStatus(it->m_szName); + // ok, np + if (curStatus == ID_STATUS_CURRENT || curStatus == ID_STATUS_DISABLED || curStatus == newStatus || newStatus > MAX_STATUS) + continue; + + if (IsStatusConnecting(newStatus)) { // connecting + int maxConnectingTime = KSPlugin.getDword(SETTING_MAXCONNECTINGTIME, 0); + if (maxConnectingTime > 0) + StartTimer(IDT_CHECKCONNECTING, (maxConnectingTime * 1000 - (GetTickCount() - it->lastStatusAckTime)), FALSE); + } + // keepstatus' administration was wrong! + else if (newStatus != ID_STATUS_OFFLINE) + it->AssignStatus(newStatus); + + // connection lost + else if (newStatus == ID_STATUS_OFFLINE) {// start checking connection + if (!StartTimer(IDT_CHECKCONN, -1, FALSE)) { /* check if not already checking */ + needChecking = true; + log_info(0, "KeepStatus: connection lost! (%s)", it->m_szName); + NotifyEventHooks(hConnectionEvent, (WPARAM)KS_CONN_STATE_LOST, (LPARAM)it->m_szName); + ProcessPopup(KS_CONN_STATE_LOST, (LPARAM)it->m_szName); + } + } + } + + if (needChecking) + StartTimer(IDT_CHECKCONN, initDelay, FALSE); +} + +static VOID CALLBACK CheckConnectionTimer(HWND, UINT, UINT_PTR, DWORD) +{ + log_debug(0, "CheckConnectionTimer"); + bool setStatus = false; + + for (auto &it : protoList) { + int realStatus = Proto_GetStatus(it->m_szName); + int shouldBeStatus = it->GetStatus(); + if (shouldBeStatus == ID_STATUS_LAST) + shouldBeStatus = it->m_lastStatus; + if (shouldBeStatus == ID_STATUS_DISABLED) + continue; + if ((shouldBeStatus != realStatus) && (realStatus == ID_STATUS_OFFLINE) || (realStatus < MIN_STATUS)) { + setStatus = true; + break; + } + } + + // one of the status was wrong + if (setStatus && (maxRetries == -1 || retryCount < maxRetries)) { + if (increaseExponential) + currentDelay = min(2 * currentDelay, maxDelay); + + if (((KSPlugin.getByte(SETTING_CHKINET, 0)) && (!InternetGetConnectedState(nullptr, 0))) || ((KSPlugin.getByte(SETTING_BYPING, FALSE)) && (!bLastPingResult))) { + // no network + NotifyEventHooks(hConnectionEvent, (WPARAM)KS_CONN_STATE_RETRYNOCONN, (LPARAM)retryCount + 1); + ProcessPopup(KS_CONN_STATE_RETRYNOCONN, 0); + } + else { + NotifyEventHooks(hConnectionEvent, (WPARAM)KS_CONN_STATE_RETRY, (LPARAM)retryCount + 1); + SetCurrentStatus(); // set the status + } + retryCount += 1; + StartTimer(IDT_AFTERCHECK, min(currentDelay, AFTERCHECK_DELAY) / 2, FALSE); + StartTimer(IDT_CHECKCONN, currentDelay, TRUE); // restart this timer + } + else // all status set ok already, or stop checking + StopChecking(); + + log_debug(0, "CheckConnectionTimer done"); +} + +static int StopChecking() +{ + StopTimer(IDT_CHECKCONN | IDT_PROCESSACK | IDT_AFTERCHECK | IDT_CHECKCONNECTING); + + BOOL isOk = TRUE; + for (auto &it : protoList) { + int curStatus = it->GetStatus(); + int newStatus = Proto_GetStatus(it->m_szName); + if (newStatus != curStatus) { + it->AssignStatus(newStatus); + isOk = FALSE; + break; + } + } + + NotifyEventHooks(hConnectionEvent, (WPARAM)KS_CONN_STATE_STOPPEDCHECKING, (LPARAM)isOk); + ProcessPopup(KS_CONN_STATE_STOPPEDCHECKING, (LPARAM)isOk); + log_info(0, "KeepStatus: stop checking (%s)", isOk ? "success" : "failure"); + retryCount = 0; + currentDelay = initDelay; + + return 0; +} + +static VOID CALLBACK AfterCheckTimer(HWND, UINT, UINT_PTR, DWORD) +{ + // after each connection check, this function is called to see if connection was recovered + StopTimer(IDT_AFTERCHECK); + + bool setStatus = false; + + for (auto &it : protoList) { + int realStatus = Proto_GetStatus(it->m_szName); + int shouldBeStatus = it->GetStatus(); + if (shouldBeStatus == ID_STATUS_LAST) // this should never happen + shouldBeStatus = it->m_lastStatus; + if (shouldBeStatus == ID_STATUS_DISABLED) // (on ignoring proto) + continue; + if ((shouldBeStatus != realStatus) && (realStatus == ID_STATUS_OFFLINE) || (realStatus < MIN_STATUS)) + setStatus = true; + } + + if (!setStatus || retryCount == maxRetries) + StopChecking(); +} + +static void CheckContinuouslyFunction(void *) +{ + Thread_SetName("KeepStatus: CheckContinuouslyFunction"); + + static int pingFailures = 0; + + // one at the time is enough, do it the 'easy' way + mir_cslock lck(CheckContinueslyCS); + + // do a ping, even if reconnecting + bool doPing = false; + for (auto &it : protoList) { + int shouldBeStatus = it->GetStatus(); + if (shouldBeStatus == ID_STATUS_LAST) + shouldBeStatus = it->m_lastStatus; + + if (shouldBeStatus == ID_STATUS_DISABLED) + continue; + + if (shouldBeStatus != ID_STATUS_OFFLINE) { + log_debug(0, "CheckContinuouslyFunction: %s should be %d", it->m_szName, shouldBeStatus); + doPing = true; + } + } + + if (!doPing) { + log_debug(0, "CheckContinuouslyFunction: All protocols should be offline, no need to check connection"); + return; + } + + BOOL ping = KSPlugin.getByte(SETTING_BYPING, FALSE); + if (ping) { + DBVARIANT dbv; + if (db_get_s(0, KSMODULENAME, SETTING_PINGHOST, &dbv)) + ping = FALSE; + else { + char *start, *end; + char host[MAX_PATH]; + DWORD *addr; + struct hostent *hostent; + char reply[sizeof(ICMP_ECHO_REPLY) + 8]; + + bLastPingResult = FALSE; + HANDLE hICMPFile = (HANDLE)IcmpCreateFile(); + if (hICMPFile == INVALID_HANDLE_VALUE) { + bLastPingResult = TRUE; + log_info(0, "KeepStatus: icmp.dll error (2)"); + } + if (bLastPingResult == FALSE) { + start = dbv.pszVal; + while ((*start != '\0') && (!bLastPingResult)) { + end = start; + while ((*end != ' ') && (*end != '\0')) + end++; + memset(host, '\0', sizeof(host)); + strncpy(host, start, end - start); + hostent = gethostbyname(host); + if (hostent != nullptr) { + addr = (DWORD *)(*hostent->h_addr_list); + bLastPingResult = (IcmpSendEcho(hICMPFile, *addr, nullptr, 0, nullptr, reply, sizeof(ICMP_ECHO_REPLY) + 8, 5000) != 0); + + if (bLastPingResult) + pingFailures = 0; + else + pingFailures++; + + log_debug(0, "CheckContinuouslyFunction: pinging %s (result: %d/%d)", host, bLastPingResult, pingFailures); + } + else log_debug(0, "CheckContinuouslyFunction: unable to resolve %s", host); + + start = end; + while (*start == ' ') + start++; + } + } + IcmpCloseHandle(hICMPFile); + } + db_free(&dbv); + } + + if (StartTimer(IDT_CHECKCONN, -1, FALSE)) // already connecting, leave + return; + + if (((!ping) && (!InternetGetConnectedState(nullptr, 0))) || ((ping) && (!bLastPingResult) && (pingFailures >= db_get_w(0, KSMODULENAME, SETTING_PINGCOUNT, DEFAULT_PINGCOUNT)))) { + pingFailures = 0; + + for (auto &pa : Accounts()) { + if (!IsSuitableProto(pa)) + continue; + + if (IsStatusConnecting(pa->iRealStatus)) { + log_debug(0, "CheckContinuouslyFunction: %s is connecting", pa->szModuleName); + continue; // connecting, leave alone + } + if (IsProtocolEnabledService(0, (LPARAM)pa->szModuleName)) { + log_debug(0, "CheckContinuouslyFunction: forcing %s offline", pa->szModuleName); + CallProtoService(pa->szModuleName, PS_SETSTATUS, (WPARAM)ID_STATUS_OFFLINE, 0); + } + } + + if (StartTimer(IDT_CHECKCONN | IDT_PROCESSACK, -1, FALSE)) {// are our 'set offlines' noticed? + log_debug(0, "CheckContinuouslyFunction: currently checking"); + return; + } + + log_info(0, "KeepStatus: connection lost! (continuesly check)"); + NotifyEventHooks(hConnectionEvent, (WPARAM)KS_CONN_STATE_LOST, 0); + ProcessPopup(KS_CONN_STATE_LOST, 0); + maxRetries = KSPlugin.getByte(SETTING_MAXRETRIES, 0); + if (maxRetries == 0) + maxRetries = -1; + StartTimer(IDT_CHECKCONN, initDelay, FALSE); + } +} + +static VOID CALLBACK CheckContinueslyTimer(HWND, UINT, UINT_PTR, DWORD) +{ + if (KSPlugin.getByte(SETTING_BYPING, FALSE)) + mir_forkthread(CheckContinuouslyFunction); + else + CheckContinuouslyFunction(nullptr); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// popup + +static INT_PTR ShowPopup(const wchar_t *msg, HICON hIcon) +{ + POPUPDATAW ppd; + ppd.lchIcon = hIcon; + wcsncpy(ppd.lpwzContactName, TranslateT("Keep status"), MAX_CONTACTNAME); + wcsncpy(ppd.lpwzText, msg, MAX_SECONDLINE); + if (KSPlugin.getByte(SETTING_POPUP_USEWINCOLORS, 0)) { + ppd.colorBack = GetSysColor(COLOR_BTNFACE); + ppd.colorText = GetSysColor(COLOR_WINDOWTEXT); + } + else if (!KSPlugin.getByte(SETTING_POPUP_USEDEFCOLORS, 0)) { + ppd.colorBack = KSPlugin.getDword(SETTING_POPUP_BACKCOLOR, 0xAAAAAA); + ppd.colorText = KSPlugin.getDword(SETTING_POPUP_TEXTCOLOR, 0x0000CC); + } + ppd.PluginWindowProc = KSPopupDlgProc; + + switch (KSPlugin.getByte(SETTING_POPUP_DELAYTYPE, POPUP_DELAYFROMPU)) { + case POPUP_DELAYCUSTOM: + ppd.iSeconds = (int)KSPlugin.getDword(SETTING_POPUP_TIMEOUT, 0); + if (ppd.iSeconds == 0) + ppd.iSeconds = currentDelay / 1000 - 1; + break; + + case POPUP_DELAYPERMANENT: + ppd.iSeconds = -1; + break; + + case POPUP_DELAYFROMPU: + default: + ppd.iSeconds = 0; + break; + } + return (INT_PTR)PUAddPopupW(&ppd); +} + +static wchar_t* GetHumanName(LPARAM lParam) +{ + PROTOACCOUNT *ProtoAccount = Proto_GetAccount((char*)lParam); + return (ProtoAccount != nullptr) ? ProtoAccount->tszAccountName : TranslateT("Protocol"); +} + +static int ProcessPopup(int reason, LPARAM lParam) +{ + if (!KSPlugin.getByte(SETTING_SHOWCONNECTIONPOPUPS, FALSE)) + return -1; + + HICON hIcon = nullptr; + CMStringW wszText; + + switch (reason) { + case KS_CONN_STATE_OTHERLOCATION: // lParam = 1 proto + if (!KSPlugin.getByte(SETTING_PUOTHER, TRUE)) + return -1; + + hIcon = Skin_LoadProtoIcon((char*)lParam, SKINICON_STATUS_OFFLINE); + wszText.Format(TranslateT("%s connected from another location"), GetHumanName(lParam)); + break; + + case KS_CONN_STATE_LOGINERROR: // lParam = 1 proto + if (!KSPlugin.getByte(SETTING_PUOTHER, TRUE)) + return -1; + + hIcon = Skin_LoadProtoIcon((char*)lParam, SKINICON_STATUS_OFFLINE); + if (KSPlugin.getByte(SETTING_LOGINERR, LOGINERR_NOTHING) == LOGINERR_CANCEL) + wszText.Format(TranslateT("%s login error, cancel reconnecting"), GetHumanName(lParam)); + else if (KSPlugin.getByte(SETTING_LOGINERR, LOGINERR_NOTHING) == LOGINERR_SETDELAY) + wszText.Format(TranslateT("%s login error (next retry (%d) in %d s)"), GetHumanName(lParam), retryCount + 1, KSPlugin.getDword(SETTING_LOGINERR_DELAY, DEFAULT_MAXDELAY)); + else + return -1; + break; + + case KS_CONN_STATE_LOST: // lParam = 1 proto, or nullptr + if (!KSPlugin.getByte(SETTING_PUCONNLOST, TRUE)) + return -1; + + if (lParam) { // указатель на имя модуля. + hIcon = Skin_LoadProtoIcon((char*)lParam, SKINICON_STATUS_OFFLINE); + wszText.Format(TranslateT("%s status error (next retry (%d) in %d s)"), GetHumanName(lParam), retryCount + 1, currentDelay / 1000); + } + else wszText.Format(TranslateT("Status error (next retry (%d) in %d s)"), retryCount + 1, currentDelay / 1000); + break; + + case KS_CONN_STATE_RETRY: // lParam = PROTOCOLSETTINGEX** + if (!KSPlugin.getByte(SETTING_PUCONNRETRY, TRUE)) + return -1; + + if (lParam) { + PROTOCOLSETTINGEX **ps = (PROTOCOLSETTINGEX **)lParam; + + if (retryCount == maxRetries - 1) + wszText.Format(TranslateT("Resetting status... (last try (%d))"), retryCount + 1); + else + wszText.Format(TranslateT("Resetting status... (next retry (%d) in %d s)"), retryCount + 2, currentDelay / 1000); + wszText.Append(L"\r\n"); + for (int i = 0; i < protoList.getCount(); i++) { + PROTOCOLSETTINGEX *p = ps[i]; + if (p->m_status == ID_STATUS_DISABLED) + continue; + + if (mir_wstrlen(p->m_tszAccName) > 0) + if (KSPlugin.getByte(SETTING_PUSHOWEXTRA, TRUE)) + wszText.AppendFormat(TranslateT("%s\t(will be set to %s)\r\n"), p->m_tszAccName, Clist_GetStatusModeDescription(p->m_status, 0)); + } + + hIcon = Skin_LoadProtoIcon(ps[0]->m_szName, SKINICON_STATUS_OFFLINE); + wszText.TrimRight(); + } + break; + + case KS_CONN_STATE_RETRYNOCONN: // lParam = nullptr + if (!KSPlugin.getByte(SETTING_PUOTHER, TRUE)) + return -1; + + if (retryCount == maxRetries - 1) + wszText.Format(TranslateT("No internet connection seems available... (last try (%d))"), retryCount + 1); + else + wszText.Format(TranslateT("No internet connection seems available... (next retry (%d) in %d s)"), retryCount + 2, currentDelay / 1000); + break; + + case KS_CONN_STATE_STOPPEDCHECKING: // lParam == BOOL succes + if (!KSPlugin.getByte(SETTING_PURESULT, TRUE)) + return -1; + + if (lParam) { + hIcon = Skin_LoadIcon(SKINICON_STATUS_ONLINE); + wszText.Format(TranslateT("Status was set ok")); + } + else wszText.Format(TranslateT("Giving up")); + break; + } + if (hIcon == nullptr) + hIcon = Skin_LoadIcon(SKINICON_STATUS_OFFLINE); + + Netlib_Logf(0, "KeepStatus: %s", wszText.c_str()); + return ShowPopup(wszText, hIcon); +} + +LRESULT CALLBACK KSPopupDlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + switch (message) { + case WM_CONTEXTMENU: // right + case WM_COMMAND: // left + switch (db_get_b(0, KSMODULENAME, + (message == WM_COMMAND) ? SETTING_POPUP_LEFTCLICK : SETTING_POPUP_RIGHTCLICK, + POPUP_ACT_CLOSEPOPUP)) { + case POPUP_ACT_CANCEL: + // cancel timer + StopChecking(); + PUDeletePopup(hWnd); + break; + + case POPUP_ACT_CLOSEPOPUP: + // close the popup + PUDeletePopup(hWnd); + break; + } + break; + } + + return DefWindowProc(hWnd, message, wParam, lParam); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// services + +INT_PTR StopReconnectingService(WPARAM, LPARAM) +{ + int ret = StartTimer(IDT_CHECKCONN | IDT_AFTERCHECK, -1, FALSE); + StopChecking(); + return ret; +} + +INT_PTR EnableProtocolService(WPARAM wParam, LPARAM lParam) +{ + char *szProto = (char *)lParam; + if (szProto == nullptr) + return -1; + + char dbSetting[128]; + mir_snprintf(dbSetting, "%s_enabled", szProto); + if (!KSPlugin.getByte(dbSetting, 1)) // 'hard' disabled + return -1; + + int ret = -2; + for (auto &it : protoList) { + if (!mir_strcmp(szProto, it->m_szName)) { + if (wParam) + it->AssignStatus(Proto_GetStatus(it->m_szName)); + else + it->AssignStatus(ID_STATUS_DISABLED); + + ret = 0; + break; + } + } + return ret; +} + +INT_PTR IsProtocolEnabledService(WPARAM, LPARAM lParam) +{ + char *szProto = (char *)lParam; + + char dbSetting[128]; + mir_snprintf(dbSetting, "%s_enabled", szProto); + if (!KSPlugin.getByte(dbSetting, 1)) + return FALSE; + + for (auto &it : protoList) + if (!mir_strcmp(szProto, it->m_szName)) + return it->GetStatus() != ID_STATUS_DISABLED; + + return FALSE; +} + +INT_PTR AnnounceStatusChangeService(WPARAM, LPARAM lParam) +{ + PROTOCOLSETTINGEX *newSituation = (PROTOCOLSETTINGEX *)lParam; + log_info(0, "Another plugin announced a status change to %d for %s", newSituation->m_status, newSituation->m_szName == nullptr ? "all" : newSituation->m_szName); + + for (auto &it : protoList) + if (!mir_strcmp(it->m_szName, newSituation->m_szName)) + it->AssignStatus(newSituation->m_status, newSituation->m_lastStatus, newSituation->m_szMsg); + + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// window for suspend + +static DWORD CALLBACK MessageWndProc(HWND, UINT msg, WPARAM wParam, LPARAM lParam) +{ + static TProtoSettings *ps = nullptr; + + switch (msg) { + case WM_POWERBROADCAST: + switch (wParam) { + case PBT_APMSUSPEND: + log_info(0, "KeepStatus: suspend state detected: %08X %08X", wParam, lParam); + if (ps == nullptr) { + ps = new TProtoSettings(protoList); + for (auto &it : *ps) + EnableProtocolService(0, (LPARAM)it->m_szName); + + // set proto's offline, the clist will not try to reconnect in that case + Clist_SetStatusMode(ID_STATUS_OFFLINE); + } + break; + + case PBT_APMRESUMESUSPEND: + case PBT_APMRESUMECRITICAL: + // case PBT_APMRESUMEAUTOMATIC: ? + log_info(0, "KeepStatus: resume from suspend state"); + if (ps != nullptr) { + for (auto &it : *ps) { + SMProto *p = protoList.find(it); + if (p) + p->AssignStatus(it->m_status, it->m_lastStatus, it->m_szMsg); + } + delete ps; + ps = nullptr; + } + StartTimer(IDT_PROCESSACK, 0, FALSE); + break; + } + break; + + case WM_DESTROY: + if (ps != nullptr) { + delete ps; + ps = nullptr; + } + break; + } + + return TRUE; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// init stuff + +static int onShutdown(WPARAM, LPARAM) +{ + KSUnloadOptions(); + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +int KSModuleLoaded(WPARAM, LPARAM) +{ + hMessageWindow = nullptr; + KSLoadOptions(); + + hEvents[0] = HookEvent(ME_OPT_INITIALISE, KeepStatusOptionsInit); + hEvents[1] = HookEvent(ME_SYSTEM_PRESHUTDOWN, onShutdown); + return 0; +} + +void KeepStatusLoad() +{ + if (g_bMirandaLoaded) + KSModuleLoaded(0, 0); + else + HookEvent(ME_SYSTEM_MODULESLOADED, KSModuleLoaded); + + hConnectionEvent = CreateHookableEvent(ME_KS_CONNECTIONEVENT); + + hServices[0] = CreateServiceFunction(MS_KS_STOPRECONNECTING, StopReconnectingService); + hServices[1] = CreateServiceFunction(MS_KS_ENABLEPROTOCOL, EnableProtocolService); + hServices[2] = CreateServiceFunction(MS_KS_ISPROTOCOLENABLED, IsProtocolEnabledService); + hServices[3] = CreateServiceFunction(MS_KS_ANNOUNCESTATUSCHANGE, AnnounceStatusChangeService); +} + +void KeepStatusUnload() +{ + if (g_bMirandaLoaded) + onShutdown(0, 0); + + KillModuleOptions(&KSPlugin); + + for (auto &it : hServices) { + DestroyServiceFunction(it); + it = nullptr; + } + + for (auto &it : hEvents) { + UnhookEvent(it); + it = nullptr; + } + + DestroyHookableEvent(hConnectionEvent); hConnectionEvent = nullptr; +} diff --git a/plugins/StatusManager/src/ss_main.cpp b/plugins/StatusManager/src/ss_main.cpp new file mode 100644 index 0000000000..9b586ebece --- /dev/null +++ b/plugins/StatusManager/src/ss_main.cpp @@ -0,0 +1,455 @@ +/* + StartupStatus 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 +*/ + +#include "stdafx.h" + +CFakePlugin SSPlugin(SSMODULENAME); + +static HANDLE hServices[3], hEvents[3]; +static UINT_PTR setStatusTimerId = 0; + +///////////////////////////////////////////////////////////////////////////////////////// + +static HANDLE hProtoAckHook, hCSStatusChangeHook, hStatusChangeHook; + +static HWND hMessageWindow; + +static BYTE showDialogOnStartup = 0; + +///////////////////////////////////////////////////////////////////////////////////////// +// command line options + +static PROTOCOLSETTINGEX* IsValidProtocol(TProtoSettings &protoSettings, const char *protoName) +{ + for (auto &it : protoSettings) + if (!it->ssDisabled && !strncmp(it->m_szName, protoName, mir_strlen(it->m_szName))) + return it; + + return nullptr; +} + +static int IsValidStatusDesc(char* statusDesc) +{ + if (!strncmp("away", statusDesc, 4)) + return ID_STATUS_AWAY; + if (!strncmp("na", statusDesc, 2)) + return ID_STATUS_NA; + if (!strncmp("dnd", statusDesc, 3)) + return ID_STATUS_DND; + if (!strncmp("occupied", statusDesc, 8)) + return ID_STATUS_OCCUPIED; + if (!strncmp("freechat", statusDesc, 8)) + return ID_STATUS_FREECHAT; + if (!strncmp("online", statusDesc, 6)) + return ID_STATUS_ONLINE; + if (!strncmp("offline", statusDesc, 7)) + return ID_STATUS_OFFLINE; + if (!strncmp("invisible", statusDesc, 9)) + return ID_STATUS_INVISIBLE; + if (!strncmp("last", statusDesc, 4)) + return ID_STATUS_LAST; + + return 0; +} + +static void ProcessCommandLineOptions(TProtoSettings &protoSettings) +{ + if (protoSettings.getCount() == 0) + return; + + char *cmdl = GetCommandLineA(); + while (*cmdl != '\0') { + while (*cmdl != '/') { + if (*cmdl == '\0') + return; + + cmdl++; + } + if (*cmdl == '\0') + return; + + cmdl++; + if (!strncmp(cmdl, "showdialog", 10)) { + showDialogOnStartup = TRUE; + continue; + } + char *protoName = cmdl; // first protocol ? + PROTOCOLSETTINGEX* protoSetting = IsValidProtocol(protoSettings, protoName); + if (protoSetting != nullptr) { + while (*cmdl != '=') { + if (*cmdl == '\0') + return; + + cmdl++; // skip to status + } + + if (*cmdl == '\0') + return; + + cmdl++; + char *statusDesc = cmdl; + int status = IsValidStatusDesc(statusDesc); + if (status != 0) + protoSetting->m_status = status; + } + } +} + +static void SetLastStatusMessages(TProtoSettings &ps) +{ + for (auto &it : ps) { + if (it->m_status != ID_STATUS_LAST) + continue; + + char dbSetting[128]; + mir_snprintf(dbSetting, "%s%s", PREFIX_LASTMSG, it->m_szName); + it->m_szMsg = SSPlugin.getWStringA(dbSetting); + } +} + +///////////////////////////////////////////////////////////////////////////////////////// +// Account control event + +// 'allow override' +static int ProcessProtoAck(WPARAM, LPARAM lParam) +{ + // 'something' made a status change + ACKDATA *ack = (ACKDATA*)lParam; + if (ack->type != ACKTYPE_STATUS && ack->result != ACKRESULT_FAILED) + return 0; + + if (!SSPlugin.getByte(SETTING_OVERRIDE, 1) || protoList.getCount() == 0) + return 0; + + for (auto &it : protoList) { + if (!mir_strcmp(ack->szModule, it->m_szName)) { + it->ssDisabled = true; + log_debug(0, "StartupStatus: %s overridden by ME_PROTO_ACK, status will not be set", ack->szModule); + } + } + + return 0; +} + +static int StatusChange(WPARAM, LPARAM lParam) +{ + // change by menu + if (!SSPlugin.getByte(SETTING_OVERRIDE, 1) || protoList.getCount() == 0) + return 0; + + char *szProto = (char *)lParam; + if (szProto == nullptr) { // global status change + for (auto &it : protoList) { + it->ssDisabled = true; + log_debug(0, "StartupStatus: all protos overridden by ME_CLIST_STATUSMODECHANGE, status will not be set"); + } + } + else { + for (auto &it : protoList) { + if (!mir_strcmp(it->m_szName, szProto)) { + it->ssDisabled = true; + log_debug(0, "StartupStatus: %s overridden by ME_CLIST_STATUSMODECHANGE, status will not be set", szProto); + } + } + } + + return 0; +} + +static int CSStatusChangeEx(WPARAM wParam, LPARAM) +{ + // another status plugin made the change + if (!SSPlugin.getByte(SETTING_OVERRIDE, 1) || protoList.getCount() == 0) + return 0; + + if (wParam != 0) { + PROTOCOLSETTINGEX **ps = *(PROTOCOLSETTINGEX***)wParam; + if (ps == nullptr) + return -1; + + for (int i = 0; i < protoList.getCount(); i++) { + for (auto &it : protoList) { + if (ps[i]->m_szName == nullptr || it->m_szName == nullptr) + continue; + + if (!mir_strcmp(ps[i]->m_szName, it->m_szName)) { + log_debug(0, "StartupStatus: %s overridden by MS_CS_SETSTATUSEX, status will not be set", ps[i]->m_szName); + it->ssDisabled = true; + } + } + } + } + + return 0; +} + +static void CALLBACK SetStatusTimed(HWND, UINT, UINT_PTR, DWORD) +{ + KillTimer(nullptr, setStatusTimerId); + UnhookEvent(hProtoAckHook); + UnhookEvent(hCSStatusChangeHook); + UnhookEvent(hStatusChangeHook); + + TProtoSettings ps(protoList); + for (auto &it : ps) + if (it->ssDisabled) + it->m_status = ID_STATUS_DISABLED; + + SetStatusEx(ps); +} + +static int OnOkToExit(WPARAM, LPARAM) +{ + // save last protocolstatus + for (auto &pa : Accounts()) { + if (!IsSuitableProto(pa)) + continue; + + if (!Proto_GetAccount(pa->szModuleName)) + continue; + + char lastName[128], lastMsg[128]; + mir_snprintf(lastName, "%s%s", PREFIX_LAST, pa->szModuleName); + SSPlugin.setWord(lastName, pa->iRealStatus); + mir_snprintf(lastMsg, "%s%s", PREFIX_LASTMSG, pa->szModuleName); + SSPlugin.delSetting(lastMsg); + + if (!(CallProtoService(pa->szModuleName, PS_GETCAPS, PFLAGNUM_1, 0) & PF1_MODEMSGSEND & ~PF1_INDIVMODEMSG)) + continue; + + if (!(CallProtoService(pa->szModuleName, PS_GETCAPS, PFLAGNUM_3, 0) & Proto_Status2Flag(pa->iRealStatus))) + continue; + + // NewAwaySys + if (ServiceExists(MS_NAS_GETSTATE)) { + NAS_PROTOINFO npi = { sizeof(npi) }; + npi.szProto = pa->szModuleName; + CallService(MS_NAS_GETSTATE, (WPARAM)&npi, 1); + if (npi.szMsg == nullptr) { + npi.status = 0; + npi.szProto = nullptr; + CallService(MS_NAS_GETSTATE, (WPARAM)&npi, 1); + } + if (npi.szMsg != nullptr) { + SSPlugin.setWString(lastMsg, npi.tszMsg); + mir_free(npi.tszMsg); + } + } + } + + if (SSPlugin.getByte(SETTING_SETPROFILE, 1) || SSPlugin.getByte(SETTING_OFFLINECLOSE, 0)) + Clist_SetStatusMode(ID_STATUS_OFFLINE); + + return 0; +} + +static int OnShutdown(WPARAM, LPARAM) +{ + // set windowstate and docked for next startup + if (SSPlugin.getByte(SETTING_SETWINSTATE, 0)) { + int state = SSPlugin.getByte(SETTING_WINSTATE, SETTING_STATE_NORMAL); + HWND hClist = g_clistApi.hwndContactList; + BOOL isHidden = !IsWindowVisible(hClist); + switch (state) { + case SETTING_STATE_HIDDEN: + // try to use services where possible + if (!isHidden) + g_clistApi.pfnShowHide(); + break; + + case SETTING_STATE_MINIMIZED: + if (!db_get_b(0, MODULE_CLIST, SETTING_TOOLWINDOW, 0)) + ShowWindow(hClist, SW_SHOWMINIMIZED); + break; + + case SETTING_STATE_NORMAL: + // try to use services where possible (that's what they're for) + if (isHidden) + g_clistApi.pfnShowHide(); + break; + } + } + + // hangup + if (SSPlugin.getByte(SETTING_AUTOHANGUP, 0)) + InternetAutodialHangup(0); + + int state = SSPlugin.getByte(SETTING_WINSTATE, SETTING_STATE_NORMAL); + // set windowstate and docked for next startup + if (SSPlugin.getByte(SETTING_SETWINSTATE, 0)) + db_set_b(0, MODULE_CLIST, SETTING_WINSTATE, (BYTE)state); + + if (hMessageWindow) + DestroyWindow(hMessageWindow); + + protoList.destroy(); + return 0; +} + +/* Window proc for poweroff event */ +static DWORD CALLBACK MessageWndProc(HWND, UINT msg, WPARAM wParam, LPARAM) +{ + switch (msg) { + case WM_ENDSESSION: + log_debug(0, "WM_ENDSESSION"); + if (wParam) { + log_debug(0, "WM_ENDSESSION: calling exit"); + OnShutdown(0, 0); + log_debug(0, "WM_ENDSESSION: exit called"); + } + break; + } + + return TRUE; +} + +int SSModuleLoaded(WPARAM, LPARAM) +{ + InitProfileModule(); + + hEvents[0] = HookEvent(ME_OPT_INITIALISE, StartupStatusOptionsInit); + + /* shutdown hook for normal shutdown */ + hEvents[1] = HookEvent(ME_SYSTEM_OKTOEXIT, OnOkToExit); + hEvents[2] = HookEvent(ME_SYSTEM_PRESHUTDOWN, OnShutdown); + /* message window for poweroff */ + hMessageWindow = CreateWindowEx(0, L"STATIC", nullptr, 0, 0, 0, 0, 0, nullptr, nullptr, nullptr, nullptr); + SetWindowLongPtr(hMessageWindow, GWLP_WNDPROC, (LONG_PTR)MessageWndProc); + + GetProfile(-1, protoList); + + // override with cmdl + ProcessCommandLineOptions(protoList); + if (protoList.getCount() == 0) + return 0;// no protocols are loaded + + SetLastStatusMessages(protoList); + showDialogOnStartup = (showDialogOnStartup || SSPlugin.getByte(SETTING_SHOWDIALOG, 0)); + + // dial + if (showDialogOnStartup || SSPlugin.getByte(SETTING_SETPROFILE, 1)) + if (SSPlugin.getByte(SETTING_AUTODIAL, 0)) + InternetAutodial(0, nullptr); + + // set the status! + if (showDialogOnStartup || SSPlugin.getByte(SETTING_SHOWDIALOG, 0)) + ShowConfirmDialogEx((TProtoSettings*)&protoList, SSPlugin.getDword(SETTING_DLGTIMEOUT, 5)); + else if (SSPlugin.getByte(SETTING_SETPROFILE, 1)) { + // set hooks for override + if (SSPlugin.getByte(SETTING_OVERRIDE, 1)) { + hProtoAckHook = HookEvent(ME_PROTO_ACK, ProcessProtoAck); + hCSStatusChangeHook = HookEvent(ME_CS_STATUSCHANGEEX, CSStatusChangeEx); + hStatusChangeHook = HookEvent(ME_CLIST_STATUSMODECHANGE, StatusChange); + } + setStatusTimerId = SetTimer(nullptr, 0, SSPlugin.getDword(SETTING_SETPROFILEDELAY, 500), SetStatusTimed); + } + + // win size and location + if (SSPlugin.getByte(SETTING_SETWINLOCATION, 0) || SSPlugin.getByte(SETTING_SETWINSIZE, 0)) { + HWND hClist = g_clistApi.hwndContactList; + + // store in db + if (SSPlugin.getByte(SETTING_SETWINLOCATION, 0)) { + db_set_dw(0, MODULE_CLIST, SETTING_XPOS, SSPlugin.getDword(SETTING_XPOS, 0)); + db_set_dw(0, MODULE_CLIST, SETTING_YPOS, SSPlugin.getDword(SETTING_YPOS, 0)); + } + if (SSPlugin.getByte(SETTING_SETWINSIZE, 0)) { + db_set_dw(0, MODULE_CLIST, SETTING_WIDTH, SSPlugin.getDword(SETTING_WIDTH, 0)); + if (!db_get_b(0, MODULE_CLUI, SETTING_AUTOSIZE, 0)) + db_set_dw(0, MODULE_CLIST, SETTING_HEIGHT, SSPlugin.getDword(SETTING_HEIGHT, 0)); + } + + WINDOWPLACEMENT wndpl = { sizeof(wndpl) }; + if (GetWindowPlacement(hClist, &wndpl)) { + if (wndpl.showCmd == SW_SHOWNORMAL && !Clist_IsDocked()) { + RECT rc; + if (GetWindowRect(hClist, &rc)) { + int x = rc.left; + int y = rc.top; + int width = rc.right - rc.left; + int height = rc.bottom - rc.top; + if (SSPlugin.getByte(SETTING_SETWINLOCATION, 0)) { + x = SSPlugin.getDword(SETTING_XPOS, x); + y = SSPlugin.getDword(SETTING_YPOS, y); + } + if (SSPlugin.getByte(SETTING_SETWINSIZE, 0)) { + width = SSPlugin.getDword(SETTING_WIDTH, width); + if (!db_get_b(0, MODULE_CLUI, SETTING_AUTOSIZE, 0)) + height = SSPlugin.getDword(SETTING_HEIGHT, height); + } + MoveWindow(hClist, x, y, width, height, TRUE); + } + } + } + } + + return 0; +} + +static INT_PTR SrvGetProfile(WPARAM wParam, LPARAM lParam) +{ + return GetProfile((int)wParam, *(TProtoSettings*)lParam); +} + +void StartupStatusLoad() +{ + if (g_bMirandaLoaded) + SSModuleLoaded(0, 0); + else + HookEvent(ME_SYSTEM_MODULESLOADED, SSModuleLoaded); + + if (SSPlugin.getByte(SETTING_SETPROFILE, 1) || SSPlugin.getByte(SETTING_OFFLINECLOSE, 0)) + db_set_w(0, "CList", "Status", (WORD)ID_STATUS_OFFLINE); + + // docking + if (SSPlugin.getByte(SETTING_SETDOCKED, 0)) { + int docked = SSPlugin.getByte(SETTING_DOCKED, DOCKED_NONE); + if (docked == DOCKED_LEFT || docked == DOCKED_RIGHT) + docked = -docked; + + db_set_b(0, MODULE_CLIST, SETTING_DOCKED, (BYTE)docked); + } + + // Create service functions; the get functions are created here; they don't rely on commonstatus + hServices[0] = CreateServiceFunction(MS_SS_GETPROFILE, SrvGetProfile); + hServices[1] = CreateServiceFunction(MS_SS_GETPROFILECOUNT, GetProfileCount); + hServices[2] = CreateServiceFunction(MS_SS_GETPROFILENAME, GetProfileName); + + LoadProfileModule(); +} + +void StartupStatusUnload() +{ + if (g_bMirandaLoaded) + OnShutdown(0, 0); + + KillModuleOptions(&SSPlugin); + + for (auto &it : hServices) { + DestroyServiceFunction(it); + it = nullptr; + } + + for (auto &it : hEvents) { + UnhookEvent(it); + it = nullptr; + } + + DeinitProfilesModule(); +} diff --git a/plugins/StatusManager/src/startupstatus.cpp b/plugins/StatusManager/src/startupstatus.cpp deleted file mode 100644 index 9b586ebece..0000000000 --- a/plugins/StatusManager/src/startupstatus.cpp +++ /dev/null @@ -1,455 +0,0 @@ -/* - StartupStatus 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 -*/ - -#include "stdafx.h" - -CFakePlugin SSPlugin(SSMODULENAME); - -static HANDLE hServices[3], hEvents[3]; -static UINT_PTR setStatusTimerId = 0; - -///////////////////////////////////////////////////////////////////////////////////////// - -static HANDLE hProtoAckHook, hCSStatusChangeHook, hStatusChangeHook; - -static HWND hMessageWindow; - -static BYTE showDialogOnStartup = 0; - -///////////////////////////////////////////////////////////////////////////////////////// -// command line options - -static PROTOCOLSETTINGEX* IsValidProtocol(TProtoSettings &protoSettings, const char *protoName) -{ - for (auto &it : protoSettings) - if (!it->ssDisabled && !strncmp(it->m_szName, protoName, mir_strlen(it->m_szName))) - return it; - - return nullptr; -} - -static int IsValidStatusDesc(char* statusDesc) -{ - if (!strncmp("away", statusDesc, 4)) - return ID_STATUS_AWAY; - if (!strncmp("na", statusDesc, 2)) - return ID_STATUS_NA; - if (!strncmp("dnd", statusDesc, 3)) - return ID_STATUS_DND; - if (!strncmp("occupied", statusDesc, 8)) - return ID_STATUS_OCCUPIED; - if (!strncmp("freechat", statusDesc, 8)) - return ID_STATUS_FREECHAT; - if (!strncmp("online", statusDesc, 6)) - return ID_STATUS_ONLINE; - if (!strncmp("offline", statusDesc, 7)) - return ID_STATUS_OFFLINE; - if (!strncmp("invisible", statusDesc, 9)) - return ID_STATUS_INVISIBLE; - if (!strncmp("last", statusDesc, 4)) - return ID_STATUS_LAST; - - return 0; -} - -static void ProcessCommandLineOptions(TProtoSettings &protoSettings) -{ - if (protoSettings.getCount() == 0) - return; - - char *cmdl = GetCommandLineA(); - while (*cmdl != '\0') { - while (*cmdl != '/') { - if (*cmdl == '\0') - return; - - cmdl++; - } - if (*cmdl == '\0') - return; - - cmdl++; - if (!strncmp(cmdl, "showdialog", 10)) { - showDialogOnStartup = TRUE; - continue; - } - char *protoName = cmdl; // first protocol ? - PROTOCOLSETTINGEX* protoSetting = IsValidProtocol(protoSettings, protoName); - if (protoSetting != nullptr) { - while (*cmdl != '=') { - if (*cmdl == '\0') - return; - - cmdl++; // skip to status - } - - if (*cmdl == '\0') - return; - - cmdl++; - char *statusDesc = cmdl; - int status = IsValidStatusDesc(statusDesc); - if (status != 0) - protoSetting->m_status = status; - } - } -} - -static void SetLastStatusMessages(TProtoSettings &ps) -{ - for (auto &it : ps) { - if (it->m_status != ID_STATUS_LAST) - continue; - - char dbSetting[128]; - mir_snprintf(dbSetting, "%s%s", PREFIX_LASTMSG, it->m_szName); - it->m_szMsg = SSPlugin.getWStringA(dbSetting); - } -} - -///////////////////////////////////////////////////////////////////////////////////////// -// Account control event - -// 'allow override' -static int ProcessProtoAck(WPARAM, LPARAM lParam) -{ - // 'something' made a status change - ACKDATA *ack = (ACKDATA*)lParam; - if (ack->type != ACKTYPE_STATUS && ack->result != ACKRESULT_FAILED) - return 0; - - if (!SSPlugin.getByte(SETTING_OVERRIDE, 1) || protoList.getCount() == 0) - return 0; - - for (auto &it : protoList) { - if (!mir_strcmp(ack->szModule, it->m_szName)) { - it->ssDisabled = true; - log_debug(0, "StartupStatus: %s overridden by ME_PROTO_ACK, status will not be set", ack->szModule); - } - } - - return 0; -} - -static int StatusChange(WPARAM, LPARAM lParam) -{ - // change by menu - if (!SSPlugin.getByte(SETTING_OVERRIDE, 1) || protoList.getCount() == 0) - return 0; - - char *szProto = (char *)lParam; - if (szProto == nullptr) { // global status change - for (auto &it : protoList) { - it->ssDisabled = true; - log_debug(0, "StartupStatus: all protos overridden by ME_CLIST_STATUSMODECHANGE, status will not be set"); - } - } - else { - for (auto &it : protoList) { - if (!mir_strcmp(it->m_szName, szProto)) { - it->ssDisabled = true; - log_debug(0, "StartupStatus: %s overridden by ME_CLIST_STATUSMODECHANGE, status will not be set", szProto); - } - } - } - - return 0; -} - -static int CSStatusChangeEx(WPARAM wParam, LPARAM) -{ - // another status plugin made the change - if (!SSPlugin.getByte(SETTING_OVERRIDE, 1) || protoList.getCount() == 0) - return 0; - - if (wParam != 0) { - PROTOCOLSETTINGEX **ps = *(PROTOCOLSETTINGEX***)wParam; - if (ps == nullptr) - return -1; - - for (int i = 0; i < protoList.getCount(); i++) { - for (auto &it : protoList) { - if (ps[i]->m_szName == nullptr || it->m_szName == nullptr) - continue; - - if (!mir_strcmp(ps[i]->m_szName, it->m_szName)) { - log_debug(0, "StartupStatus: %s overridden by MS_CS_SETSTATUSEX, status will not be set", ps[i]->m_szName); - it->ssDisabled = true; - } - } - } - } - - return 0; -} - -static void CALLBACK SetStatusTimed(HWND, UINT, UINT_PTR, DWORD) -{ - KillTimer(nullptr, setStatusTimerId); - UnhookEvent(hProtoAckHook); - UnhookEvent(hCSStatusChangeHook); - UnhookEvent(hStatusChangeHook); - - TProtoSettings ps(protoList); - for (auto &it : ps) - if (it->ssDisabled) - it->m_status = ID_STATUS_DISABLED; - - SetStatusEx(ps); -} - -static int OnOkToExit(WPARAM, LPARAM) -{ - // save last protocolstatus - for (auto &pa : Accounts()) { - if (!IsSuitableProto(pa)) - continue; - - if (!Proto_GetAccount(pa->szModuleName)) - continue; - - char lastName[128], lastMsg[128]; - mir_snprintf(lastName, "%s%s", PREFIX_LAST, pa->szModuleName); - SSPlugin.setWord(lastName, pa->iRealStatus); - mir_snprintf(lastMsg, "%s%s", PREFIX_LASTMSG, pa->szModuleName); - SSPlugin.delSetting(lastMsg); - - if (!(CallProtoService(pa->szModuleName, PS_GETCAPS, PFLAGNUM_1, 0) & PF1_MODEMSGSEND & ~PF1_INDIVMODEMSG)) - continue; - - if (!(CallProtoService(pa->szModuleName, PS_GETCAPS, PFLAGNUM_3, 0) & Proto_Status2Flag(pa->iRealStatus))) - continue; - - // NewAwaySys - if (ServiceExists(MS_NAS_GETSTATE)) { - NAS_PROTOINFO npi = { sizeof(npi) }; - npi.szProto = pa->szModuleName; - CallService(MS_NAS_GETSTATE, (WPARAM)&npi, 1); - if (npi.szMsg == nullptr) { - npi.status = 0; - npi.szProto = nullptr; - CallService(MS_NAS_GETSTATE, (WPARAM)&npi, 1); - } - if (npi.szMsg != nullptr) { - SSPlugin.setWString(lastMsg, npi.tszMsg); - mir_free(npi.tszMsg); - } - } - } - - if (SSPlugin.getByte(SETTING_SETPROFILE, 1) || SSPlugin.getByte(SETTING_OFFLINECLOSE, 0)) - Clist_SetStatusMode(ID_STATUS_OFFLINE); - - return 0; -} - -static int OnShutdown(WPARAM, LPARAM) -{ - // set windowstate and docked for next startup - if (SSPlugin.getByte(SETTING_SETWINSTATE, 0)) { - int state = SSPlugin.getByte(SETTING_WINSTATE, SETTING_STATE_NORMAL); - HWND hClist = g_clistApi.hwndContactList; - BOOL isHidden = !IsWindowVisible(hClist); - switch (state) { - case SETTING_STATE_HIDDEN: - // try to use services where possible - if (!isHidden) - g_clistApi.pfnShowHide(); - break; - - case SETTING_STATE_MINIMIZED: - if (!db_get_b(0, MODULE_CLIST, SETTING_TOOLWINDOW, 0)) - ShowWindow(hClist, SW_SHOWMINIMIZED); - break; - - case SETTING_STATE_NORMAL: - // try to use services where possible (that's what they're for) - if (isHidden) - g_clistApi.pfnShowHide(); - break; - } - } - - // hangup - if (SSPlugin.getByte(SETTING_AUTOHANGUP, 0)) - InternetAutodialHangup(0); - - int state = SSPlugin.getByte(SETTING_WINSTATE, SETTING_STATE_NORMAL); - // set windowstate and docked for next startup - if (SSPlugin.getByte(SETTING_SETWINSTATE, 0)) - db_set_b(0, MODULE_CLIST, SETTING_WINSTATE, (BYTE)state); - - if (hMessageWindow) - DestroyWindow(hMessageWindow); - - protoList.destroy(); - return 0; -} - -/* Window proc for poweroff event */ -static DWORD CALLBACK MessageWndProc(HWND, UINT msg, WPARAM wParam, LPARAM) -{ - switch (msg) { - case WM_ENDSESSION: - log_debug(0, "WM_ENDSESSION"); - if (wParam) { - log_debug(0, "WM_ENDSESSION: calling exit"); - OnShutdown(0, 0); - log_debug(0, "WM_ENDSESSION: exit called"); - } - break; - } - - return TRUE; -} - -int SSModuleLoaded(WPARAM, LPARAM) -{ - InitProfileModule(); - - hEvents[0] = HookEvent(ME_OPT_INITIALISE, StartupStatusOptionsInit); - - /* shutdown hook for normal shutdown */ - hEvents[1] = HookEvent(ME_SYSTEM_OKTOEXIT, OnOkToExit); - hEvents[2] = HookEvent(ME_SYSTEM_PRESHUTDOWN, OnShutdown); - /* message window for poweroff */ - hMessageWindow = CreateWindowEx(0, L"STATIC", nullptr, 0, 0, 0, 0, 0, nullptr, nullptr, nullptr, nullptr); - SetWindowLongPtr(hMessageWindow, GWLP_WNDPROC, (LONG_PTR)MessageWndProc); - - GetProfile(-1, protoList); - - // override with cmdl - ProcessCommandLineOptions(protoList); - if (protoList.getCount() == 0) - return 0;// no protocols are loaded - - SetLastStatusMessages(protoList); - showDialogOnStartup = (showDialogOnStartup || SSPlugin.getByte(SETTING_SHOWDIALOG, 0)); - - // dial - if (showDialogOnStartup || SSPlugin.getByte(SETTING_SETPROFILE, 1)) - if (SSPlugin.getByte(SETTING_AUTODIAL, 0)) - InternetAutodial(0, nullptr); - - // set the status! - if (showDialogOnStartup || SSPlugin.getByte(SETTING_SHOWDIALOG, 0)) - ShowConfirmDialogEx((TProtoSettings*)&protoList, SSPlugin.getDword(SETTING_DLGTIMEOUT, 5)); - else if (SSPlugin.getByte(SETTING_SETPROFILE, 1)) { - // set hooks for override - if (SSPlugin.getByte(SETTING_OVERRIDE, 1)) { - hProtoAckHook = HookEvent(ME_PROTO_ACK, ProcessProtoAck); - hCSStatusChangeHook = HookEvent(ME_CS_STATUSCHANGEEX, CSStatusChangeEx); - hStatusChangeHook = HookEvent(ME_CLIST_STATUSMODECHANGE, StatusChange); - } - setStatusTimerId = SetTimer(nullptr, 0, SSPlugin.getDword(SETTING_SETPROFILEDELAY, 500), SetStatusTimed); - } - - // win size and location - if (SSPlugin.getByte(SETTING_SETWINLOCATION, 0) || SSPlugin.getByte(SETTING_SETWINSIZE, 0)) { - HWND hClist = g_clistApi.hwndContactList; - - // store in db - if (SSPlugin.getByte(SETTING_SETWINLOCATION, 0)) { - db_set_dw(0, MODULE_CLIST, SETTING_XPOS, SSPlugin.getDword(SETTING_XPOS, 0)); - db_set_dw(0, MODULE_CLIST, SETTING_YPOS, SSPlugin.getDword(SETTING_YPOS, 0)); - } - if (SSPlugin.getByte(SETTING_SETWINSIZE, 0)) { - db_set_dw(0, MODULE_CLIST, SETTING_WIDTH, SSPlugin.getDword(SETTING_WIDTH, 0)); - if (!db_get_b(0, MODULE_CLUI, SETTING_AUTOSIZE, 0)) - db_set_dw(0, MODULE_CLIST, SETTING_HEIGHT, SSPlugin.getDword(SETTING_HEIGHT, 0)); - } - - WINDOWPLACEMENT wndpl = { sizeof(wndpl) }; - if (GetWindowPlacement(hClist, &wndpl)) { - if (wndpl.showCmd == SW_SHOWNORMAL && !Clist_IsDocked()) { - RECT rc; - if (GetWindowRect(hClist, &rc)) { - int x = rc.left; - int y = rc.top; - int width = rc.right - rc.left; - int height = rc.bottom - rc.top; - if (SSPlugin.getByte(SETTING_SETWINLOCATION, 0)) { - x = SSPlugin.getDword(SETTING_XPOS, x); - y = SSPlugin.getDword(SETTING_YPOS, y); - } - if (SSPlugin.getByte(SETTING_SETWINSIZE, 0)) { - width = SSPlugin.getDword(SETTING_WIDTH, width); - if (!db_get_b(0, MODULE_CLUI, SETTING_AUTOSIZE, 0)) - height = SSPlugin.getDword(SETTING_HEIGHT, height); - } - MoveWindow(hClist, x, y, width, height, TRUE); - } - } - } - } - - return 0; -} - -static INT_PTR SrvGetProfile(WPARAM wParam, LPARAM lParam) -{ - return GetProfile((int)wParam, *(TProtoSettings*)lParam); -} - -void StartupStatusLoad() -{ - if (g_bMirandaLoaded) - SSModuleLoaded(0, 0); - else - HookEvent(ME_SYSTEM_MODULESLOADED, SSModuleLoaded); - - if (SSPlugin.getByte(SETTING_SETPROFILE, 1) || SSPlugin.getByte(SETTING_OFFLINECLOSE, 0)) - db_set_w(0, "CList", "Status", (WORD)ID_STATUS_OFFLINE); - - // docking - if (SSPlugin.getByte(SETTING_SETDOCKED, 0)) { - int docked = SSPlugin.getByte(SETTING_DOCKED, DOCKED_NONE); - if (docked == DOCKED_LEFT || docked == DOCKED_RIGHT) - docked = -docked; - - db_set_b(0, MODULE_CLIST, SETTING_DOCKED, (BYTE)docked); - } - - // Create service functions; the get functions are created here; they don't rely on commonstatus - hServices[0] = CreateServiceFunction(MS_SS_GETPROFILE, SrvGetProfile); - hServices[1] = CreateServiceFunction(MS_SS_GETPROFILECOUNT, GetProfileCount); - hServices[2] = CreateServiceFunction(MS_SS_GETPROFILENAME, GetProfileName); - - LoadProfileModule(); -} - -void StartupStatusUnload() -{ - if (g_bMirandaLoaded) - OnShutdown(0, 0); - - KillModuleOptions(&SSPlugin); - - for (auto &it : hServices) { - DestroyServiceFunction(it); - it = nullptr; - } - - for (auto &it : hEvents) { - UnhookEvent(it); - it = nullptr; - } - - DeinitProfilesModule(); -} -- cgit v1.2.3