/* 'AutoShutdown'-Plugin for Miranda IM Copyright 2004-2007 H. Herkenrath 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 (Shutdown-License.txt); if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "stdafx.h" /* Msg Shutdown */ static HANDLE hHookEventAdded; /* Transfer Shutdown */ static HANDLE hHookProtoAck; /* Idle Shutdown */ static HANDLE hHookIdleChanged; /* Status Shutdown */ static HANDLE hHookSettingChanged; /* Weather Shutdown */ static HANDLE hHookWeatherUpdated; /* Services */ static HANDLE hEventWatcherChanged; /* Misc */ static HANDLE hHookModulesLoaded; /************************* Shared *************************************/ static WORD currentWatcherType; static void __stdcall MainThreadMapping(void *param) { HANDLE *phDoneEvent = (HANDLE*)param; ServiceShutdown(0, TRUE); /* ensure main thread (for cpu usage shutdown) */ ServiceStopWatcher(0, 0); if (*phDoneEvent != nullptr) SetEvent(*phDoneEvent); } static void __inline ShutdownAndStopWatcher(void) { HANDLE hDoneEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); CallFunctionAsync(MainThreadMapping, &hDoneEvent); if (hDoneEvent != nullptr) { WaitForSingleObject(hDoneEvent, INFINITE); CloseHandle(hDoneEvent); } } /************************* Msg Shutdown *******************************/ // ppBlob might get reallocated, must have been allocated using mir_alloc() static wchar_t* GetMessageText(BYTE **ppBlob, DWORD *pcbBlob) { (*ppBlob)[*pcbBlob] = 0; size_t cb = mir_strlen((char*)*ppBlob); /* use Unicode data if present */ if (*pcbBlob > (cb + 3)) { (*ppBlob)[*pcbBlob - 1] = 0; return (WCHAR*)&(*ppBlob)[cb]; } /* no Unicode data present, convert from ANSI */ int len = MultiByteToWideChar(CP_ACP, 0, (char*)*ppBlob, -1, nullptr, 0); if (!len) return nullptr; BYTE *buf = (BYTE*)mir_realloc(*ppBlob, (*pcbBlob) + (len*sizeof(WCHAR))); if (buf == nullptr) return nullptr; *pcbBlob += len*sizeof(WCHAR); *ppBlob = buf; buf = &(*ppBlob)[cb]; MultiByteToWideChar(CP_ACP, 0, (char*)*ppBlob, -1, (WCHAR*)buf, len); ((WCHAR*)buf)[len - 1] = 0; return (WCHAR*)buf; } static int MsgEventAdded(WPARAM, LPARAM hDbEvent) { if (currentWatcherType & SDWTF_MESSAGE) { DBEVENTINFO dbe = {}; dbe.cbBlob = db_event_getBlobSize(hDbEvent); dbe.pBlob = (BYTE*)mir_alloc(dbe.cbBlob + 2); /* ensure term zero */ if (dbe.pBlob == nullptr) return 0; if (!db_event_get(hDbEvent, &dbe)) if (dbe.eventType == EVENTTYPE_MESSAGE && !(dbe.flags & DBEF_SENT)) { DBVARIANT dbv; if (!g_plugin.getWString("Message", &dbv)) { TrimString(dbv.pwszVal); wchar_t *pszMsg = GetMessageText(&dbe.pBlob, &dbe.cbBlob); if (pszMsg != nullptr && wcsstr(pszMsg, dbv.pwszVal) != nullptr) ShutdownAndStopWatcher(); /* msg with specified text recvd */ mir_free(dbv.pwszVal); /* does NULL check */ } } mir_free(dbe.pBlob); } return 0; } /************************* Transfer Shutdown **************************/ static HANDLE *transfers; static int nTransfersCount; static int ProtoAck(WPARAM, LPARAM lParam) { ACKDATA *ack = (ACKDATA*)lParam; if (ack->type != ACKTYPE_FILE) return 0; switch (ack->result) { case ACKRESULT_DATA: { for (int i = 0; i < nTransfersCount; ++i) if (transfers[i] == ack->hProcess) break; /* already in list */ /* insert into list */ HANDLE *buf = (HANDLE*)mir_realloc(transfers, (nTransfersCount + 1)*sizeof(HANDLE)); if (buf != nullptr) { transfers = buf; transfers[nTransfersCount] = ack->hProcess; ++nTransfersCount; } break; } case ACKRESULT_SUCCESS: case ACKRESULT_FAILED: case ACKRESULT_DENIED: for (int i = 0; i < nTransfersCount; ++i) { if (transfers[i] == ack->hProcess) { /* remove from list */ if (i < (nTransfersCount - 1)) memmove(&transfers[i], &transfers[i + 1], (nTransfersCount - i - 1)*sizeof(HANDLE)); --nTransfersCount; HANDLE *buf = (HANDLE*)mir_realloc(transfers, nTransfersCount*sizeof(HANDLE)); if (buf != nullptr) transfers = buf; else if (!nTransfersCount) transfers = nullptr; /* stop watcher */ if (!nTransfersCount && (currentWatcherType&SDWTF_FILETRANSFER)) ShutdownAndStopWatcher(); break; } } break; } return 0; } /************************* Idle Shutdown ******************************/ static int IdleChanged(WPARAM, LPARAM lParam) { if (currentWatcherType&SDWTF_IDLE && lParam&IDF_ISIDLE) ShutdownAndStopWatcher(); return 0; } /************************* Status Shutdown ****************************/ static BOOL CheckAllContactsOffline(void) { BOOL fSmartCheck, fAllOffline = TRUE; /* tentatively */ fSmartCheck = g_plugin.getByte("SmartOfflineCheck", SETTING_SMARTOFFLINECHECK_DEFAULT); for (auto &hContact : Contacts()) { char *pszProto = Proto_GetBaseAccountName(hContact); if (pszProto != nullptr && Proto_GetStatus(pszProto) != ID_STATUS_OFFLINE) { if (db_get_b(hContact, pszProto, "ChatRoom", 0)) continue; if (db_get_w(hContact, pszProto, "Status", 0) != ID_STATUS_OFFLINE) { if (fSmartCheck) { if (Contact_IsHidden(hContact)) continue; if (!Contact_OnList(hContact)) continue; } fAllOffline = FALSE; break; } } } return fAllOffline; } static int StatusSettingChanged(WPARAM wParam, LPARAM lParam) { if (currentWatcherType&SDWTF_STATUS) { DBCONTACTWRITESETTING *dbcws = (DBCONTACTWRITESETTING*)lParam; if ((HANDLE)wParam != nullptr && dbcws->value.wVal == ID_STATUS_OFFLINE && !strcmp(dbcws->szSetting, "Status")) { char *pszProto = Proto_GetBaseAccountName(wParam); if (pszProto != nullptr && !strcmp(dbcws->szModule, pszProto)) if (CheckAllContactsOffline()) ShutdownAndStopWatcher(); } } return 0; } /************************* Cpu Shutdown *******************************/ static DWORD idCpuUsageThread; static BOOL CALLBACK CpuUsageWatcherProc(BYTE nCpuUsage, LPARAM lParam) { static BYTE nTimesBelow = 0; /* only one watcher thread */ /* terminated? */ if (idCpuUsageThread != GetCurrentThreadId()) { nTimesBelow = 0; return FALSE; /* stop poll thread */ } /* ignore random peaks */ if (nCpuUsage < (BYTE)lParam) ++nTimesBelow; else nTimesBelow = 0; if (nTimesBelow == 3) { nTimesBelow = 0; ShutdownAndStopWatcher(); return FALSE; /* stop poll thread */ } return TRUE; } /************************* Weather Shutdown ***************************/ static int WeatherUpdated(WPARAM wParam, LPARAM lParam) { char *pszProto = Proto_GetBaseAccountName(wParam); if ((BOOL)lParam && pszProto != nullptr && Proto_GetStatus(pszProto) == ID_STATUS_INVISIBLE) if (g_plugin.getByte("WeatherShutdown", SETTING_WEATHERSHUTDOWN_DEFAULT)) ServiceShutdown(SDSDT_SHUTDOWN, TRUE); return 0; } /************************* Services ***********************************/ INT_PTR ServiceStartWatcher(WPARAM, LPARAM lParam) { /* passing watcherType as lParam is only to be used internally, undocumented */ if (lParam == 0) lParam = (LPARAM)g_plugin.getWord("WatcherFlags", 0); /* invalid flags or empty? */ if (!(lParam&SDWTF_MASK)) return 1; /* no specific time choice? */ if (lParam&SDWTF_SPECIFICTIME && !(lParam&SDWTF_ST_MASK)) return 2; if (currentWatcherType == (WORD)lParam) return 3; if (currentWatcherType != 0) { /* Time Shutdown */ CloseCountdownFrame(); /* fails if not opened */ /* Cpu Shutdown */ idCpuUsageThread = 0; } SetShutdownMenuItem(true); SetShutdownToolbarButton(true); currentWatcherType = (WORD)lParam; NotifyEventHooks(hEventWatcherChanged, TRUE, 0); /* Time Shutdown */ if (currentWatcherType&SDWTF_SPECIFICTIME) ShowCountdownFrame(currentWatcherType); /* after modules loaded */ /* Cpu Shutdown */ if (currentWatcherType&SDWTF_CPUUSAGE) idCpuUsageThread = PollCpuUsage(CpuUsageWatcherProc, (LPARAM)DBGetContactSettingRangedByte(0, MODULENAME, "CpuUsageThreshold", SETTING_CPUUSAGETHRESHOLD_DEFAULT, 1, 100), 1500); /* Transfer Shutdown */ if (currentWatcherType&SDWTF_FILETRANSFER && !nTransfersCount) ShutdownAndStopWatcher(); /* Status Shutdown */ if (currentWatcherType&SDWTF_STATUS && CheckAllContactsOffline()) ShutdownAndStopWatcher(); return 0; } INT_PTR ServiceStopWatcher(WPARAM, LPARAM) { if (currentWatcherType == 0) return 1; /* Time Shutdown */ if (currentWatcherType&SDWTF_SPECIFICTIME) CloseCountdownFrame(); /* Cpu Shutdown */ idCpuUsageThread = 0; currentWatcherType = 0; SetShutdownMenuItem(FALSE); SetShutdownToolbarButton(FALSE); NotifyEventHooks(hEventWatcherChanged, FALSE, 0); return 0; } INT_PTR ServiceIsWatcherEnabled(WPARAM, LPARAM) { return currentWatcherType != 0; } /************************* Misc ***********************************/ void WatcherModulesLoaded(void) { /* Weather Shutdown */ if (ServiceExists(MS_WEATHER_UPDATE)) hHookWeatherUpdated = HookEvent(ME_WEATHER_UPDATED, WeatherUpdated); /* restore watcher if it was running on last exit */ if (g_plugin.getByte("RememberOnRestart", 0) == SDROR_RUNNING) { g_plugin.setByte("RememberOnRestart", 1); ServiceStartWatcher(0, 0); /* after modules loaded */ } } void InitWatcher(void) { /* Shared */ currentWatcherType = 0; /* Message Shutdown */ hHookEventAdded = HookEvent(ME_DB_EVENT_ADDED, MsgEventAdded); /* Status Shutdown*/ hHookSettingChanged = HookEvent(ME_DB_CONTACT_SETTINGCHANGED, StatusSettingChanged); /* Idle Shutdown */ hHookIdleChanged = HookEvent(ME_IDLE_CHANGED, IdleChanged); /* Transfer Shutdown */ transfers = nullptr; nTransfersCount = 0; hHookProtoAck = HookEvent(ME_PROTO_ACK, ProtoAck); /* Weather Shutdown */ hHookWeatherUpdated = nullptr; /* Services */ hEventWatcherChanged = CreateHookableEvent(ME_AUTOSHUTDOWN_WATCHERCHANGED); CreateServiceFunction(MS_AUTOSHUTDOWN_STARTWATCHER, ServiceStartWatcher); CreateServiceFunction(MS_AUTOSHUTDOWN_STOPWATCHER, ServiceStopWatcher); CreateServiceFunction(MS_AUTOSHUTDOWN_ISWATCHERENABLED, ServiceIsWatcherEnabled); } void UninitWatcher(void) { /* remember watcher if running */ if (!ServiceStopWatcher(0, 0)) if (g_plugin.getByte("RememberOnRestart", SETTING_REMEMBERONRESTART_DEFAULT)) g_plugin.setByte("RememberOnRestart", SDROR_RUNNING); /* Message Shutdown */ UnhookEvent(hHookEventAdded); /* Status Shutdown*/ UnhookEvent(hHookSettingChanged); /* Idle Shutdown */ UnhookEvent(hHookIdleChanged); /* Transfer Shutdown */ UnhookEvent(hHookProtoAck); mir_free(transfers); /* does NULL check */ /* Weather Shutdown */ UnhookEvent(hHookWeatherUpdated); /* does NULL check */ /* Services */ DestroyHookableEvent(hEventWatcherChanged); /* Misc */ UnhookEvent(hHookModulesLoaded); }