/* 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 "../commonstatus.h" #include "keepstatus.h" #include "../resource.h" struct TimerInfo { int timer; int timeout; BOOL restart; int result; HANDLE hEvent; }; static CRITICAL_SECTION GenTimerCS, GenStatusCS, CheckContinueslyCS; static HANDLE hOptionsHook = NULL; static HANDLE hShutdownHook = NULL; static HANDLE hAccChangeHook = NULL; static HANDLE hProtoAckHook = NULL; static HANDLE hStatusChangeHook = NULL; static HANDLE hCSStatusChangeHook = NULL; static HANDLE hCSStatusChangeExHook = NULL; extern HANDLE hConnectionEvent; extern PLUGININFOEX pluginInfoEx; static HWND hMessageWindow = NULL; // variables (general) static int CompareConnections( const TConnectionSettings* p1, const TConnectionSettings* p2 ) { return lstrcmpA( p1->szName, p2->szName ); } static OBJLIST connectionSettings( 10, CompareConnections ); 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; static HMODULE hICMP = NULL; // 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); int LoadMainOptions(); static void GetCurrentConnectionSettings(); static int AssignStatus(TConnectionSettings* connSetting, int status, int lastStatus, char *szMsg); 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 StatusChangeTimer(HWND hwnd,UINT message,UINT_PTR idEvent,DWORD dwTime); 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 ContinueslyCheckFunction(void *arg); 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); static int ShowPopup(char* msg, HICON hIcon); int CALLBACK PopupDlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); static DWORD CALLBACK MessageWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); static int Exit(WPARAM wParam, LPARAM lParam); // options.c extern int OptionsInit(WPARAM wparam,LPARAM lparam); extern int InitCommonStatus(); #define TRIGGERPLUGIN /* remove this to compile without it */ #ifdef TRIGGERPLUGIN extern int RegisterAction(); extern int DeInitAction(); extern int RegisterTrigger(); extern int DeInitTrigger(); #endif TConnectionSettings::TConnectionSettings( PROTOACCOUNT* pa ) { cbSize = sizeof(PROTOCOLSETTINGEX); szName = pa->szModuleName; tszAccName = pa->tszAccountName; szMsg = NULL; int status = CallProtoService( pa->szModuleName, PS_GETSTATUS, 0, 0); AssignStatus( this, status, status, NULL); } TConnectionSettings::~TConnectionSettings() { if ( szMsg != NULL) free( szMsg ); } int LoadMainOptions() { UnhookEvent(hProtoAckHook); UnhookEvent(hStatusChangeHook); UnhookEvent(hCSStatusChangeHook); UnhookEvent(hCSStatusChangeExHook); hProtoAckHook = hStatusChangeHook = hCSStatusChangeHook = hCSStatusChangeExHook = 0; if (IsWindow(hMessageWindow)) DestroyWindow(hMessageWindow); if (StartTimer(IDT_CHECKCONTIN, -1, FALSE)) { FreeLibrary(hICMP); WSACleanup(); } StopTimer(IDT_CHECKCONN|IDT_PROCESSACK|IDT_AFTERCHECK|IDT_CHECKCONTIN|IDT_CHECKCONNECTING); GetCurrentConnectionSettings(); if (DBGetContactSettingByte(NULL, MODULENAME, SETTING_CHECKCONNECTION, FALSE)) { if (DBGetContactSettingByte(NULL, MODULENAME, SETTING_CONTCHECK, FALSE)) { if (DBGetContactSettingByte(NULL, MODULENAME, SETTING_BYPING, FALSE)) { WSADATA wsaData; WSAStartup(MAKEWORD(2, 2), &wsaData); hICMP = LoadLibraryA("ICMP.DLL"); if (hICMP == NULL) { log_infoA("KeepStatus: icmp.dll not found, ping disabled"); } } StartTimer(IDT_CHECKCONTIN, 0, FALSE); } increaseExponential = DBGetContactSettingByte(NULL, MODULENAME, SETTING_INCREASEEXPONENTIAL, FALSE); currentDelay = initDelay = 1000*DBGetContactSettingDword(NULL, MODULENAME, SETTING_INITDELAY, DEFAULT_INITDELAY); maxDelay = 1000*DBGetContactSettingDword(NULL, MODULENAME, SETTING_MAXDELAY, DEFAULT_MAXDELAY); maxRetries = DBGetContactSettingByte(NULL, MODULENAME, 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 (DBGetContactSettingByte(NULL, MODULENAME, SETTING_CHECKAPMRESUME, 0)&&(CallService(MS_SYSTEM_GETVERSION,0,0) >= 0x00040000)) { if (!IsWindow(hMessageWindow)) { hMessageWindow = CreateWindowEx(0, _T("STATIC"), NULL, 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL); SetWindowLongPtr(hMessageWindow, GWLP_WNDPROC, (LONG_PTR)MessageWndProc); } } retryCount = 0; } return 0; } static void GetCurrentConnectionSettings() { connectionSettings.destroy(); int count; PROTOACCOUNT** protos; ProtoEnumAccounts( &count, &protos ); for ( int i=0; i < count; i++ ) if ( IsSuitableProto( protos[i] )) connectionSettings.insert( new TConnectionSettings( protos[i] )); } static PROTOCOLSETTINGEX** GetCurrentProtoSettingsCopy() { PROTOCOLSETTINGEX** ps; int i; EnterCriticalSection(&GenStatusCS); ps = ( PROTOCOLSETTINGEX** )malloc(connectionSettings.getCount()*sizeof(PROTOCOLSETTINGEX *)); if (ps == NULL) { LeaveCriticalSection(&GenStatusCS); return NULL; } for(i=0;icbSize = sizeof(PROTOCOLSETTINGEX); ps[i]->lastStatus = cs.lastStatus; ps[i]->status = cs.status; ps[i]->szMsg = NULL; ps[i]->szName = cs.szName; ps[i]->tszAccName = cs.tszAccName; if ( (ServiceExists(MS_NAS_GETSTATE)) && (CallProtoService(ps[i]->szName, PS_GETSTATUS, 0, 0) == ID_STATUS_OFFLINE) && (ps[i]->status != ID_STATUS_OFFLINE) ) { NAS_PROTOINFO npi; ZeroMemory(&npi, sizeof(NAS_PROTOINFO)); npi.cbSize = sizeof(NAS_PROTOINFO); npi.status = 0; npi.szProto = ps[i]->szName; if (!CallService(MS_NAS_GETSTATEA, (WPARAM)&npi, (LPARAM)1)) { if (npi.szMsg != NULL) { ps[i]->szMsg = _strdup(npi.szMsg); log_infoA("KeepStatus: Status message for %s retrieved from NAS", ps[i]->szName); } else { npi.status = 0; npi.szProto = NULL; if (!CallService(MS_NAS_GETSTATEA, (WPARAM)&npi, (LPARAM)1)) { if (npi.szMsg != NULL) { ps[i]->szMsg = _strdup(npi.szMsg); log_infoA("KeepStatus: Status message for %s retrieved from NAS (global)", ps[i]->szName); } } } } } } LeaveCriticalSection(&GenStatusCS); return ps; } static void FreeProtoSettings(PROTOCOLSETTINGEX** ps) { for(int i=0;iszMsg != NULL) free(ps[i]->szMsg); free(ps[i]); } free(ps); } static int AssignStatus(TConnectionSettings* cs, int status, int lastStatus, char *szMsg) { if ( status < MIN_STATUS || status > MAX_STATUS ) return -1; EnterCriticalSection(&GenStatusCS); char dbSetting[128]; _snprintf(dbSetting, sizeof(dbSetting), "%s_enabled", cs->szName); cs->lastStatus = lastStatus == 0 ? cs->status : lastStatus; if (!DBGetContactSettingByte(NULL, MODULENAME, dbSetting, 1)) cs->status = ID_STATUS_DISABLED; else if (status == ID_STATUS_LAST) cs->status = cs->lastStatus; else cs->status = status; log_infoA("KeepStatus: assigning status %d to %s", cs->status, cs->szName); if ( szMsg != NULL && szMsg != cs->szMsg ) { if ( cs->szMsg != NULL ) free(cs->szMsg); cs->szMsg = _strdup(szMsg); } else if (szMsg != cs->szMsg) { if (cs->szMsg != NULL) free(cs->szMsg); cs->szMsg = NULL; } LeaveCriticalSection(&GenStatusCS); return 0; } static int GetStatus(const TConnectionSettings& cs) { if (cs.status == ID_STATUS_CURRENT) return CallProtoService(cs.szName, PS_GETSTATUS, 0, 0); return cs.status; } static int SetCurrentStatus() { int ret, i, realStatus; PROTOCOLSETTINGEX **ps; ps = GetCurrentProtoSettingsCopy(); for (i=0;iszName, PS_GETSTATUS, 0, 0); if ( (ps[i]->status == ID_STATUS_DISABLED) || (ps[i]->status == realStatus) || (DBGetContactSettingByte(NULL, ps[i]->szName, SETTING_PROTORETRY, 0)) ) { // ignore this proto by removing it's name (not so nice) ps[i]->szName = ""; } else if ( (ps[i]->status != ID_STATUS_DISABLED) && (ps[i]->status != realStatus) && (realStatus != ID_STATUS_OFFLINE) && (DBGetContactSettingByte(NULL, MODULENAME, SETTING_FIRSTOFFLINE, FALSE)) ) { // force offline before reconnecting log_infoA("KeepStatus: Setting %s offline before making a new connection attempt", ps[i]->szName); CallProtoService(ps[i]->szName, PS_SETSTATUS, (WPARAM)ID_STATUS_OFFLINE, 0); } } ProcessPopup(KS_CONN_STATE_RETRY, (LPARAM)ps); ret = CallService(MS_CS_SETSTATUSEX, (WPARAM)&ps, 0); FreeProtoSettings(ps); return ret; } static int StatusChange(WPARAM wParam, LPARAM lParam) { char* szProto = (char *)lParam; if (szProto == NULL) { // global status change for (int i=0;iszName == NULL) || (connectionSettings[j].szName == NULL) ) continue; if (!strcmp(protoSettings[i]->szName, connectionSettings[j].szName)) if (GetStatus(connectionSettings[j]) != ID_STATUS_DISABLED) AssignStatus(&connectionSettings[j], protoSettings[i]->status, protoSettings[i]->lastStatus, connectionSettings[j].szMsg); } } } return 0; } static int CSStatusChangeEx(WPARAM wParam, LPARAM lParam) { // the status was changed by commonstatus (new) if (wParam != 0) { int i, j; PROTOCOLSETTINGEX** protoSettings = *(PROTOCOLSETTINGEX***)wParam; if (protoSettings == NULL) return -1; for (i=0;iszName == NULL) || (connectionSettings[j].szName == NULL) ) continue; if (!strcmp(protoSettings[i]->szName, connectionSettings[j].szName)) { if (GetStatus(connectionSettings[j]) != ID_STATUS_DISABLED) AssignStatus(&connectionSettings[j], protoSettings[i]->status, protoSettings[i]->lastStatus, protoSettings[i]->szMsg); } } } } return 0; } static int StartTimerFunction(int timer, int timeout, BOOL restart) { int res = 0; EnterCriticalSection(&GenTimerCS); log_debugA("StartTimer: %d, %d, %d", timer, timeout, restart); log_debugA("ack: %u, chk: %u, aft: %u, cnt: %u, con: %u", processAckTimerId, checkConnectionTimerId, afterCheckTimerId, checkContinTimerId, checkConnectingTimerId); if ( timer & IDT_PROCESSACK ) { res = (processAckTimerId == 0)?0:1; if ( ((processAckTimerId == 0) && (checkConnectionTimerId == 0)) || (restart) ) { if (timeout != -1) { if (restart) KillTimer(NULL, processAckTimerId); if (timeout == 0) processAckTimerId = SetTimer(NULL, 0, ackDelay, CheckAckStatusTimer); else processAckTimerId = SetTimer(NULL, 0, timeout, CheckAckStatusTimer); } } } if ( timer & IDT_CHECKCONN ) { res = (checkConnectionTimerId == 0?0:1)||res; if ( (checkConnectionTimerId == 0) || (restart) ) { if (timeout != -1) { if (restart) KillTimer(NULL, checkConnectionTimerId); if (timeout == 0) checkConnectionTimerId = SetTimer(NULL, 0, initDelay, CheckConnectionTimer); else checkConnectionTimerId = SetTimer(NULL, 0, timeout, CheckConnectionTimer); } } } if ( timer & IDT_AFTERCHECK ) { res = (afterCheckTimerId==0?0:1)||res; if ( (afterCheckTimerId == 0) || (restart) ) { if (timeout != -1) { if (restart) KillTimer(NULL, afterCheckTimerId); if (timeout == 0) afterCheckTimerId = SetTimer(NULL, 0, initDelay/2, AfterCheckTimer); else afterCheckTimerId = SetTimer(NULL, 0, timeout, AfterCheckTimer); } } } if ( timer & IDT_CHECKCONTIN ) { res = (checkContinTimerId==0?0:1)||res; if ( (checkContinTimerId == 0) || (restart) ) { if (timeout != -1) { if (restart) KillTimer(NULL, checkContinTimerId); if (timeout == 0) { checkContinTimerId = SetTimer(NULL, 0, 1000*DBGetContactSettingDword(NULL, MODULENAME, SETTING_CNTDELAY, CHECKCONTIN_DELAY), CheckContinueslyTimer); } else checkContinTimerId = SetTimer(NULL, 0, timeout, CheckContinueslyTimer); } } } if ( timer & IDT_CHECKCONNECTING ) { res = (checkConnectingTimerId==0?0:1)||res; if ( (checkConnectingTimerId == 0) || (restart) ) { if (timeout != -1) { if (restart) KillTimer(NULL, checkConnectingTimerId); if (timeout == 0) { timeout = initDelay/2; } checkConnectingTimerId = SetTimer(NULL, 0, timeout, CheckConnectingTimer); } } } log_debugA("ack: %u, chk: %u, aft: %u, cnt: %u, con: %u", processAckTimerId, checkConnectionTimerId, afterCheckTimerId, checkContinTimerId, checkConnectingTimerId); log_debugA("StartTimer done %d", res); LeaveCriticalSection(&GenTimerCS); return res; } static VOID CALLBACK StartTimerApcProc(ULONG_PTR param) { struct TimerInfo *ti = (struct TimerInfo *)param; log_debugA("StartTimerApcProc %d %d %d", ti->timer, ti->timeout, ti->restart); ti->result = StartTimerFunction(ti->timer, ti->timeout, ti->restart); SetEvent(ti->hEvent); } static int StartTimer(int timer, int timeout, BOOL restart) { if (GetCurrentThreadId() == mainThreadId) return StartTimerFunction(timer, timeout, restart); TimerInfo *ti = ( TimerInfo* )calloc(1, sizeof(struct TimerInfo)); ti->timer = timer; ti->timeout = timeout; ti->restart = restart; ti->hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); QueueUserAPC(StartTimerApcProc, hMainThread, (ULONG_PTR)ti); WaitForSingleObject(ti->hEvent, INFINITE); CloseHandle(ti->hEvent); int res = ti->result; free(ti); return res; } static int StopTimer(int timer) { int res = 0; EnterCriticalSection(&GenTimerCS); log_debugA("StopTimer %d", timer); log_debugA("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(NULL, processAckTimerId); processAckTimerId = 0; res = 1; } } if ( timer & IDT_CHECKCONN ) { if (checkConnectionTimerId == 0) res = 0||res; else { KillTimer(NULL, checkConnectionTimerId); checkConnectionTimerId = 0; res = 1; } } if ( timer & IDT_AFTERCHECK ) { if (afterCheckTimerId == 0) res = 0||res; else { KillTimer(NULL, afterCheckTimerId); afterCheckTimerId = 0; res = 1; } } if ( timer & IDT_CHECKCONTIN ) { if (checkContinTimerId == 0) res = 0||res; else { KillTimer(NULL, checkContinTimerId); checkContinTimerId = 0; res = 1; } } if ( timer & IDT_CHECKCONNECTING ) { if (checkConnectingTimerId == 0) res = 0||res; else { KillTimer(NULL, checkConnectingTimerId); checkConnectingTimerId = 0; res = 1; } } log_debugA("ack: %u, chk: %u, aft: %u, cnt: %u, con: %u", processAckTimerId, checkConnectionTimerId, afterCheckTimerId, checkContinTimerId, checkConnectingTimerId); log_debugA("StopTimer done %d", res); LeaveCriticalSection(&GenTimerCS); return res; } static int ProcessProtoAck(WPARAM wParam,LPARAM lParam) { ACKDATA *ack=(ACKDATA*)lParam; char dbSetting[128]; int i; if ( (ack->type != ACKTYPE_STATUS) && (ack->type != ACKTYPE_LOGIN) ) return 0; mir_snprintf(dbSetting, sizeof(dbSetting), "%s_enabled", ack->szModule); if (!DBGetContactSettingByte(NULL, MODULENAME, dbSetting, 1)) return 0; if (DBGetContactSettingByte(NULL, ack->szModule, SETTING_PROTORETRY, 0)) { log_infoA("KeepStatus: %s has built-in reconnection enabled", ack->szModule); return 0; } if ( ack->type == ACKTYPE_STATUS && ack->result == ACKRESULT_SUCCESS ) { for ( i=0; i < connectionSettings.getCount(); i++ ) { TConnectionSettings& cs = connectionSettings[i]; if ( !strcmp( cs.szName, ack->szModule )) cs.lastStatusAckTime = GetTickCount(); } StartTimer(IDT_PROCESSACK, 0, FALSE); return 0; } if (ack->type == ACKTYPE_LOGIN) { if (ack->lParam == LOGINERR_OTHERLOCATION) { int i, j; for (i=0;iszModule, cs.szName)) { AssignStatus(&cs, ID_STATUS_OFFLINE, 0, NULL); if (DBGetContactSettingByte(NULL, MODULENAME, SETTING_CNCOTHERLOC, 0)) { StopTimer(IDT_PROCESSACK); for (j=0;jszModule); } } } else if (ack->result == ACKRESULT_FAILED) { // login failed NotifyEventHooks(hConnectionEvent, (WPARAM)KS_CONN_STATE_LOGINERROR, (LPARAM)ack->szModule); switch (DBGetContactSettingByte(NULL, MODULENAME, SETTING_LOGINERR, LOGINERR_NOTHING)) { case LOGINERR_CANCEL: { log_infoA("KeepStatus: cancel on login error (%s)", ack->szModule); for ( int i=0; i szModule, cs.szName)) AssignStatus(&cs, ID_STATUS_OFFLINE, 0, NULL); } ProcessPopup(KS_CONN_STATE_LOGINERROR, (LPARAM)ack->szModule); StopChecking(); } break; case LOGINERR_SETDELAY: { int newDelay = newDelay = 1000*DBGetContactSettingDword(NULL, MODULENAME, SETTING_LOGINERR_DELAY, DEFAULT_MAXDELAY); log_infoA("KeepStatus: set delay to %d on login error (%s)", newDelay/1000, 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 hwnd,UINT message,UINT_PTR idEvent,DWORD dwTime) { int i, maxConnectingTime; StopTimer(IDT_CHECKCONNECTING); //log_debugA("KeepStatus: CheckConnectingTimer"); for (i=0;i 0) { if ( (unsigned int)maxConnectingTime <= ((GetTickCount() - cs.lastStatusAckTime)/1000) ) { // set offline log_infoA("KeepStatus: %s is too long connecting; setting offline", cs.szName); CallProtoService(cs.szName, PS_SETSTATUS, (WPARAM)ID_STATUS_OFFLINE, 0); } } } } } static VOID CALLBACK CheckAckStatusTimer(HWND hwnd,UINT message,UINT_PTR idEvent,DWORD dwTime) { int i, maxConnectingTime; BOOL needChecking; StopTimer(IDT_PROCESSACK); needChecking = FALSE; for (i=0;i MAX_STATUS ) continue; if (newStatus < MAX_CONNECT_RETRIES) { // connecting maxConnectingTime = DBGetContactSettingDword(NULL, MODULENAME, SETTING_MAXCONNECTINGTIME, 0); if (maxConnectingTime > 0) StartTimer(IDT_CHECKCONNECTING, (maxConnectingTime*1000 - (GetTickCount() - cs.lastStatusAckTime)), FALSE); } // keepstatus' administration was wrong! else if (newStatus != ID_STATUS_OFFLINE) AssignStatus(&cs, newStatus, 0, NULL); // 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_infoA("KeepStatus: connection lost! (%s)", cs.szName); NotifyEventHooks(hConnectionEvent, (WPARAM)KS_CONN_STATE_LOST, (LPARAM)cs.szName); ProcessPopup(KS_CONN_STATE_LOST, (LPARAM)cs.szName); } } } if (needChecking == TRUE) StartTimer(IDT_CHECKCONN, initDelay, FALSE); } static VOID CALLBACK CheckConnectionTimer(HWND hwnd,UINT message,UINT_PTR idEvent,DWORD dwTime) { int i, shouldBeStatus, realStatus, setStatus; HICON hIcon; log_debugA("CheckConnectionTimer"); setStatus = FALSE; if (showConnectionPopups) hIcon = (HICON)CallService(MS_SKIN_LOADICON, (WPARAM)SKINICON_STATUS_OFFLINE, 0); for ( i=0; i < connectionSettings.getCount() && !setStatus; i++ ) { TConnectionSettings& cs = connectionSettings[i]; realStatus = CallProtoService(cs.szName, PS_GETSTATUS, 0, 0); shouldBeStatus = GetStatus(cs); if (shouldBeStatus == ID_STATUS_LAST) shouldBeStatus = cs.lastStatus; if (shouldBeStatus == ID_STATUS_DISABLED) continue; if ( (shouldBeStatus != realStatus) && (realStatus == ID_STATUS_OFFLINE) || (realStatus < MIN_STATUS) ) { setStatus = TRUE; if (showConnectionPopups) hIcon = (HICON)CallService(MS_SKIN_LOADPROTOICON, (WPARAM)cs.szName, (LPARAM)SKINICON_STATUS_OFFLINE); } } // one of the status was wrong if ( setStatus == TRUE && ( maxRetries == -1 || retryCount < maxRetries )) { if (increaseExponential) currentDelay = min(2*currentDelay,maxDelay); if ( ((DBGetContactSettingByte(NULL, MODULENAME, SETTING_CHKINET, 0)) && (!InternetGetConnectedState(NULL, 0))) || ((DBGetContactSettingByte(NULL, MODULENAME, 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); /* set the status */ SetCurrentStatus(); } 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_debugA("CheckConnectionTimer done"); } static int StopChecking() { StopTimer(IDT_CHECKCONN|IDT_PROCESSACK|IDT_AFTERCHECK|IDT_CHECKCONNECTING); BOOL isOk = TRUE; for ( int i=0; i < connectionSettings.getCount() && isOk; i++ ) { TConnectionSettings& cs = connectionSettings[i]; int curStatus = GetStatus(cs); int newStatus = CallProtoService(cs.szName, PS_GETSTATUS, 0, 0); if ( newStatus != curStatus && curStatus != ID_STATUS_DISABLED ) { AssignStatus(&cs, newStatus, 0, NULL); isOk = FALSE; } } NotifyEventHooks(hConnectionEvent, (WPARAM)KS_CONN_STATE_STOPPEDCHECKING, (LPARAM)isOk); ProcessPopup(KS_CONN_STATE_STOPPEDCHECKING, (LPARAM)isOk); log_infoA("KeepStatus: stop checking (%s)", isOk?"success":"failure"); retryCount = 0; currentDelay = initDelay; return 0; } static VOID CALLBACK AfterCheckTimer(HWND hwnd,UINT message,UINT_PTR idEvent,DWORD dwTime) { // after each connection check, this function is called to see if connection was recovered StopTimer(IDT_AFTERCHECK); int setStatus = FALSE; for( int i=0; i h_addr_list ); bLastPingResult = (lpfnIcmpSendEcho(hICMPFile, *addr, 0,0,NULL, reply,sizeof(ICMP_ECHO_REPLY)+8,5000) != 0); if (bLastPingResult) pingFailures = 0; else pingFailures++; log_debugA("CheckContinueslyFunction: pinging %s (result: %d/%d)", host, bLastPingResult, pingFailures); } else log_debugA("CheckContinueslyFunction: unable to resolve %s", host); start = end; while (*start == ' ') start++; } } lpfnIcmpCloseHandle(hICMPFile); } DBFreeVariant(&dbv); } if (StartTimer(IDT_CHECKCONN, -1, FALSE)) { LeaveCriticalSection(&CheckContinueslyCS); return; // already connecting, leave } if ( ((!ping) && (!InternetGetConnectedState(NULL, 0))) || ( (ping) && (!bLastPingResult) && (pingFailures >= DBGetContactSettingWord(NULL, MODULENAME, SETTING_PINGCOUNT, DEFAULT_PINGCOUNT))) ) { pingFailures = 0; int count; PROTOACCOUNT** protos; ProtoEnumAccounts( &count, &protos ); for(int i=0; i < count; i++ ) { if ( !IsSuitableProto( protos[i] )) continue; if (CallProtoService(protos[i]->szModuleName, PS_GETSTATUS, 0, 0) < MAX_CONNECT_RETRIES) { log_debugA("CheckContinueslyFunction: %s is connecting", protos[i]->szModuleName); continue; // connecting, leave alone } if (IsProtocolEnabledService(0, (LPARAM)protos[i]->szModuleName)) { log_debugA("CheckContinueslyFunction: forcing %s offline", protos[i]->szModuleName); CallProtoService(protos[i]->szModuleName, PS_SETSTATUS, (WPARAM)ID_STATUS_OFFLINE, 0); } } if (StartTimer(IDT_CHECKCONN|IDT_PROCESSACK, -1, FALSE)) {// are our 'set offlines' noticed? log_debugA("CheckContinueslyFunction: currently checking"); LeaveCriticalSection(&CheckContinueslyCS); return; } log_infoA("KeepStatus: connection lost! (continuesly check)"); NotifyEventHooks(hConnectionEvent, (WPARAM)KS_CONN_STATE_LOST, (LPARAM)NULL); ProcessPopup(KS_CONN_STATE_LOST, (LPARAM)NULL); maxRetries = DBGetContactSettingByte(NULL, MODULENAME, SETTING_MAXRETRIES, 0); if (maxRetries == 0) maxRetries = -1; StartTimer(IDT_CHECKCONN, initDelay, FALSE); } LeaveCriticalSection(&CheckContinueslyCS); } static VOID CALLBACK CheckContinueslyTimer(HWND hwnd, UINT message, UINT_PTR idEvent, DWORD dwTime) { if (DBGetContactSettingByte(NULL, MODULENAME, SETTING_BYPING, FALSE)) mir_forkthread(CheckContinueslyFunction, NULL); else CheckContinueslyFunction(NULL); } // =============== popup ====================== static int ProcessPopup(int reason, LPARAM lParam) { HICON hIcon; char text[MAX_SECONDLINE], protoName[128], *szProto; if ( !DBGetContactSettingByte(NULL, MODULENAME, SETTING_SHOWCONNECTIONPOPUPS,FALSE) || !ServiceExists(MS_POPUP_ADDPOPUP)) return -1; memset(protoName, '\0', sizeof(protoName)); strcpy(text, Translate("KeepStatus")); hIcon = NULL; szProto = NULL; switch(reason) { case KS_CONN_STATE_OTHERLOCATION: // lParam = 1 proto if (!DBGetContactSettingByte(NULL, MODULENAME, SETTING_PUOTHER, TRUE)) return -1; szProto = (char *)lParam; hIcon = (HICON)CallService(MS_SKIN_LOADPROTOICON, (WPARAM)szProto, (LPARAM)SKINICON_STATUS_OFFLINE); CallProtoService(szProto, PS_GETNAME, sizeof(protoName), (LPARAM)protoName); mir_snprintf(text, sizeof(text), Translate("%s Connected from another location"), protoName); break; case KS_CONN_STATE_LOGINERROR: // lParam = 1 proto /******************* rethink this ********************/ if (!DBGetContactSettingByte(NULL, MODULENAME, SETTING_PUOTHER, TRUE)) return -1; szProto = (char *)lParam; hIcon = (HICON)CallService(MS_SKIN_LOADPROTOICON, (WPARAM)szProto, (LPARAM)SKINICON_STATUS_OFFLINE); CallProtoService(szProto, PS_GETNAME, sizeof(protoName), (LPARAM)protoName); if (DBGetContactSettingByte(NULL, MODULENAME, SETTING_LOGINERR, LOGINERR_NOTHING) == LOGINERR_CANCEL) _snprintf(text, sizeof(text), Translate("%s Login error, cancel reconnecting"), protoName); else if (DBGetContactSettingByte(NULL, MODULENAME, SETTING_LOGINERR, LOGINERR_NOTHING) == LOGINERR_SETDELAY) _snprintf(text, sizeof(text), Translate("%s Login error (next retry (%d) in %ds)"), protoName, retryCount+1, DBGetContactSettingDword(NULL, MODULENAME, SETTING_LOGINERR_DELAY, DEFAULT_MAXDELAY)); else return -1; break; case KS_CONN_STATE_LOST: // lParam = 1 proto, or NULL if (!DBGetContactSettingByte(NULL, MODULENAME, SETTING_PUCONNLOST, TRUE)) return -1; szProto = (char *)lParam; if (szProto != NULL) { hIcon = (HICON)CallService(MS_SKIN_LOADPROTOICON, (WPARAM)szProto, (LPARAM)SKINICON_STATUS_OFFLINE); CallProtoService(szProto, PS_GETNAME, sizeof(protoName), (LPARAM)protoName); _snprintf(text, sizeof(text), Translate("%s Status error (next retry (%d) in %ds)"), protoName, retryCount+1, currentDelay/1000); } else { hIcon = (HICON)CallService(MS_SKIN_LOADICON, (WPARAM)SKINICON_STATUS_OFFLINE, 0); _snprintf(text, sizeof(text), Translate("Status error (next retry (%d) in %ds)"), retryCount+1, currentDelay/1000); } break; case KS_CONN_STATE_RETRY: // lParam = PROTOCOLSETTINGEX** { int i; PROTOCOLSETTINGEX **ps; char protoInfoLine[512], protoInfo[MAX_SECONDLINE]; if (!DBGetContactSettingByte(NULL, MODULENAME, SETTING_PUCONNRETRY, TRUE)) return -1; memset(protoInfoLine, '\0', sizeof(protoInfoLine)); memset(protoInfo, '\0', sizeof(protoInfo)); strcpy(protoInfo, "\r\n"); ps = (PROTOCOLSETTINGEX **)lParam; szProto = NULL; for (i=0;iszName) > 0) { if (szProto == NULL) { szProto = ps[i]->szName; } if (DBGetContactSettingByte(NULL, MODULENAME, SETTING_PUSHOWEXTRA, TRUE)) { CallProtoService(ps[i]->szName, PS_GETNAME, sizeof(protoName), (LPARAM)protoName); //_snprintf(protoInfoLine, sizeof(protoInfoLine), Translate("%s\t(will be set to %s)\r\n"), protoName, (char *)CallService(MS_CLIST_GETSTATUSMODEDESCRIPTION, (WPARAM)ps[i]->status, (LPARAM)0)); _snprintf(protoInfoLine, sizeof(protoInfoLine), "%s\t(%s %s)\r\n", protoName, Translate("will be set to"), (char *)CallService(MS_CLIST_GETSTATUSMODEDESCRIPTION, (WPARAM)ps[i]->status, (LPARAM)0)); strncat(protoInfo, protoInfoLine, sizeof(protoInfo)-strlen(protoInfo)-1); } } } if (strlen(protoInfo) > 0) { // cut the last end of line (this may also be the first one ;) ) *(protoInfo + strlen(protoInfo) - 2) = '\0'; } if (szProto != NULL) hIcon = (HICON)CallService(MS_SKIN_LOADPROTOICON, (WPARAM)szProto, (LPARAM)SKINICON_STATUS_OFFLINE); else hIcon = (HICON)CallService(MS_SKIN_LOADICON, (WPARAM)SKINICON_STATUS_OFFLINE, 0); if (retryCount == maxRetries-1) _snprintf(text, sizeof(text), Translate("Resetting status... (last try (%d))%s"), retryCount+1, protoInfo); else _snprintf(text, sizeof(text), Translate("Resetting status... (next retry (%d) in %ds)%s"), retryCount+2, currentDelay/1000, protoInfo); } break; case KS_CONN_STATE_RETRYNOCONN: // lParam = NULL if (!DBGetContactSettingByte(NULL, MODULENAME, SETTING_PUOTHER, TRUE)) return -1; hIcon = (HICON)CallService(MS_SKIN_LOADICON, (WPARAM)SKINICON_STATUS_OFFLINE, 0); if (retryCount == maxRetries-1) _snprintf(text, sizeof(text), Translate("No internet connection seems available... (last try (%d))"), retryCount+1); else _snprintf(text, sizeof(text), Translate("No internet connection seems available... (next retry (%d) in %ds)"), retryCount+2, currentDelay/1000); break; case KS_CONN_STATE_STOPPEDCHECKING: // lParam == BOOL succes if (!DBGetContactSettingByte(NULL, MODULENAME, SETTING_PURESULT, TRUE)) return -1; if (lParam) { hIcon = (HICON)CallService(MS_SKIN_LOADICON, (WPARAM)SKINICON_STATUS_ONLINE, 0); _snprintf(text, sizeof(text), Translate("Status was set ok")); } else { hIcon = (HICON)CallService(MS_SKIN_LOADICON, (WPARAM)SKINICON_STATUS_OFFLINE, 0); _snprintf(text, sizeof(text), Translate("Giving up")); } break; } if (hIcon == NULL) hIcon = (HICON)CallService(MS_SKIN_LOADICON, (WPARAM)SKINICON_STATUS_OFFLINE, 0); log_infoA("KeepStatus: %s", text); return ShowPopup(text, hIcon); } static int ShowPopup(char* msg, HICON hIcon) { POPUPDATAEX ppd = { NULL }; ppd.lchContact = NULL; ppd.lchIcon = hIcon; memset(ppd.lpzContactName, '\0', sizeof(ppd.lpzContactName)); memset(ppd.lpzText, '\0', sizeof(ppd.lpzText)); strncpy(ppd.lpzContactName, Translate("KeepStatus"), sizeof(ppd.lpzContactName)-1); strncpy(ppd.lpzText, msg, sizeof(ppd.lpzText)-1); if (DBGetContactSettingByte(NULL, MODULENAME, SETTING_POPUP_USEWINCOLORS, 0)) ppd.colorBack = GetSysColor(COLOR_BTNFACE); else if (DBGetContactSettingByte(NULL, MODULENAME, SETTING_POPUP_USEDEFCOLORS, 0)) ppd.colorBack = (COLORREF)NULL; else ppd.colorBack = DBGetContactSettingDword(NULL, MODULENAME, SETTING_POPUP_BACKCOLOR, 0xAAAAAA); if (DBGetContactSettingByte(NULL, MODULENAME, SETTING_POPUP_USEWINCOLORS, 0)) ppd.colorText = GetSysColor(COLOR_WINDOWTEXT); else if (DBGetContactSettingByte(NULL, MODULENAME, SETTING_POPUP_USEDEFCOLORS, 0)) ppd.colorText = (COLORREF)NULL; else ppd.colorText = DBGetContactSettingDword(NULL, MODULENAME, SETTING_POPUP_TEXTCOLOR, 0x0000CC); ppd.PluginWindowProc = ( WNDPROC )PopupDlgProc; ppd.PluginData = NULL; if (!ServiceExists(MS_POPUP_ADDPOPUPEX)) return CallService(MS_POPUP_ADDPOPUP, (WPARAM)&ppd, 0); else { switch (DBGetContactSettingByte(NULL, MODULENAME, SETTING_POPUP_DELAYTYPE, POPUP_DELAYFROMPU)) { case POPUP_DELAYCUSTOM: ppd.iSeconds = (int)DBGetContactSettingDword(NULL, MODULENAME, 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 CallService(MS_POPUP_ADDPOPUPEX, (WPARAM)&ppd, 0); } } int CALLBACK PopupDlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { switch(message) { case WM_CONTEXTMENU: // right case WM_COMMAND: // left switch( DBGetContactSettingByte( NULL, MODULENAME, (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; case UM_FREEPLUGINDATA: break; } return DefWindowProc(hWnd, message, wParam, lParam); } // =============== services =================== INT_PTR StopReconnectingService(WPARAM wParam, LPARAM 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 == NULL) return -1; char dbSetting[128]; _snprintf(dbSetting, sizeof(dbSetting), "%s_enabled", szProto); if (!DBGetContactSettingByte(NULL, MODULENAME, dbSetting, 1)) // 'hard' disabled return -1; int ret = -2; for ( int i=0; i < connectionSettings.getCount(); i++ ) { TConnectionSettings& cs = connectionSettings[i]; if (!strcmp(szProto, cs.szName)) { if (wParam) { if (GetStatus(cs) == ID_STATUS_DISABLED) AssignStatus(&cs, CallProtoService(cs.szName, PS_GETSTATUS, 0, 0), 0, NULL); } else AssignStatus(&cs, ID_STATUS_DISABLED, 0, NULL); ret = 0; break; } } return ret; } INT_PTR IsProtocolEnabledService(WPARAM wParam, LPARAM lParam) { int ret = -2; char *szProto = (char *)lParam; char dbSetting[128]; _snprintf(dbSetting, sizeof(dbSetting), "%s_enabled", szProto); if ( !DBGetContactSettingByte(NULL, MODULENAME, dbSetting, 1 )) return FALSE; for ( int i=0; i < connectionSettings.getCount(); i++ ) { TConnectionSettings& cs = connectionSettings[i]; if ( !strcmp(szProto, cs.szName )) return GetStatus(cs) != ID_STATUS_DISABLED; } return FALSE; } INT_PTR AnnounceStatusChangeService(WPARAM wParam, LPARAM lParam) { PROTOCOLSETTINGEX *newSituation = (PROTOCOLSETTINGEX *)lParam; log_infoA("Another plugin announced a status change to %d for %s", newSituation->status, newSituation->szName==NULL?"all":newSituation->szName); for ( int i=0; i < connectionSettings.getCount(); i++ ) { TConnectionSettings& cs = connectionSettings[i]; if ( !lstrcmpA( cs.szName, newSituation->szName )) AssignStatus(&cs, newSituation->status, newSituation->lastStatus, newSituation->szMsg); } return 0; } // =============== window for suspend =============== static DWORD CALLBACK MessageWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { static PROTOCOLSETTINGEX** ps = NULL; switch(msg) { case WM_POWERBROADCAST: switch (wParam) { case PBT_APMSUSPEND: log_infoA("KeepStatus: suspend state detected: %08X %08X", wParam, lParam); if (ps == NULL) { ps = GetCurrentProtoSettingsCopy(); for ( int i=0; i < connectionSettings.getCount(); i++) EnableProtocolService( 0, (LPARAM)ps[i]->szName ); // set proto's offline, the clist will not try to reconnect in that case CallService(MS_CLIST_SETSTATUSMODE, (WPARAM)ID_STATUS_OFFLINE, 0); } break; //case PBT_APMRESUMEAUTOMATIC: ? case PBT_APMRESUMESUSPEND: case PBT_APMRESUMECRITICAL: log_infoA("KeepStatus: resume from suspend state"); if (ps != NULL) { for (int i=0;istatus, ps[i]->lastStatus, ps[i]->szMsg); FreeProtoSettings(ps); ps = NULL; } StartTimer(IDT_PROCESSACK, 0, FALSE); break; } break; case WM_DESTROY: if ( ps != NULL ) { FreeProtoSettings(ps); ps = NULL; } break; } return TRUE; } ///////////////////////////////////////////////////////////////////////////////////////// // Account control event int OnAccChanged(WPARAM wParam,LPARAM lParam) { PROTOACCOUNT* pa = ( PROTOACCOUNT* )lParam; switch( wParam ) { case PRAC_ADDED: connectionSettings.insert( new TConnectionSettings( pa )); break; case PRAC_REMOVED: { for ( int i=0; i < connectionSettings.getCount(); i++ ) { if ( !lstrcmpA( connectionSettings[i].szName, pa->szModuleName )) { connectionSettings.remove( i ); break; } } } break; } return 0; } // =============== init stuff ================= int CSModuleLoaded(WPARAM wParam,LPARAM lParam) { InitializeCriticalSection(&GenTimerCS); InitializeCriticalSection(&GenStatusCS); InitializeCriticalSection(&CheckContinueslyCS); protoList = ( OBJLIST* )&connectionSettings; hMessageWindow = NULL; LoadMainOptions(); hOptionsHook = HookEvent(ME_OPT_INITIALISE, OptionsInit); hShutdownHook = HookEvent(ME_SYSTEM_OKTOEXIT, Exit); hAccChangeHook = HookEvent(ME_PROTO_ACCLISTCHANGED, OnAccChanged); #ifdef TRIGGERPLUGIN RegisterAction(); RegisterTrigger(); #endif if (ServiceExists(MS_UPDATE_REGISTERFL)) CallService(MS_UPDATE_REGISTERFL, 2206, (LPARAM)&pluginInfoEx); return 0; } static int Exit(WPARAM wParam, LPARAM lParam) { #ifdef TRIGGERPLUGIN DeInitTrigger(); DeInitAction(); #endif UnhookEvent(hOptionsHook); UnhookEvent(hShutdownHook); UnhookEvent(hAccChangeHook); UnhookEvent(hStatusChangeHook); UnhookEvent(hProtoAckHook); UnhookEvent(hCSStatusChangeHook); UnhookEvent(hCSStatusChangeExHook); StopTimer(IDT_CHECKCONN|IDT_PROCESSACK|IDT_AFTERCHECK|IDT_CHECKCONTIN); if (IsWindow(hMessageWindow)) DestroyWindow(hMessageWindow); connectionSettings.destroy(); DeleteCriticalSection(&GenTimerCS); DeleteCriticalSection(&GenStatusCS); DeleteCriticalSection(&CheckContinueslyCS); return 0; }