/* AdvancedAutoAway Plugin for Miranda-IM (www.miranda-im.org) KeepStatus Plugin for Miranda-IM (www.miranda-im.org) 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" // handles for hooks and other Miranda thingies static HANDLE hCSStatusChangedExEvent; OBJLIST *protoList; // prototypes char* StatusModeToDbSetting(int status, const char *suffix); DWORD StatusModeToProtoFlag(int status); INT_PTR SetStatusEx(WPARAM wParam, LPARAM lParam); int InitCommonStatus(); int GetProtoCount(); // extern extern INT_PTR ShowConfirmDialogEx(WPARAM wParam, LPARAM lParam); // some helpers from awaymsg.c ================================================================ char *StatusModeToDbSetting(int status, const char *suffix) { char *prefix; static char str[64]; switch (status) { case ID_STATUS_AWAY: prefix = "Away"; break; case ID_STATUS_NA: prefix = "Na"; break; case ID_STATUS_DND: prefix = "Dnd"; break; case ID_STATUS_OCCUPIED: prefix = "Occupied"; break; case ID_STATUS_FREECHAT: prefix = "FreeChat"; break; case ID_STATUS_ONLINE: prefix = "On"; break; case ID_STATUS_OFFLINE: prefix = "Off"; break; case ID_STATUS_INVISIBLE: prefix = "Inv"; break; case ID_STATUS_ONTHEPHONE: prefix = "Otp"; break; case ID_STATUS_OUTTOLUNCH: prefix = "Otl"; break; default: return NULL; } mir_strcpy(str, prefix); mir_strcat(str, suffix); return str; } DWORD StatusModeToProtoFlag(int status) { // *not* the same as in core, switch (status) { case ID_STATUS_ONLINE: return PF2_ONLINE; case ID_STATUS_OFFLINE: return PF2_OFFLINE; case ID_STATUS_INVISIBLE: return PF2_INVISIBLE; case ID_STATUS_OUTTOLUNCH: return PF2_OUTTOLUNCH; case ID_STATUS_ONTHEPHONE: return PF2_ONTHEPHONE; case ID_STATUS_AWAY: return PF2_SHORTAWAY; case ID_STATUS_NA: return PF2_LONGAWAY; case ID_STATUS_OCCUPIED: return PF2_LIGHTDND; case ID_STATUS_DND: return PF2_HEAVYDND; case ID_STATUS_FREECHAT: return PF2_FREECHAT; } return 0; } int GetActualStatus(PROTOCOLSETTINGEX *protoSetting) { if (protoSetting->status == ID_STATUS_LAST) { if ((protoSetting->lastStatus < MIN_STATUS) || (protoSetting->lastStatus > MAX_STATUS)) return CallProtoService(protoSetting->szName, PS_GETSTATUS, 0, 0); return protoSetting->lastStatus; } if (protoSetting->status == ID_STATUS_CURRENT) return CallProtoService(protoSetting->szName, PS_GETSTATUS, 0, 0); if ((protoSetting->status < ID_STATUS_OFFLINE) || (protoSetting->status > ID_STATUS_OUTTOLUNCH)) { log_debugA("invalid status detected: %d", protoSetting->status); return 0; } return protoSetting->status; } // helper, from core static wchar_t* GetDefaultMessage(int status) { switch (status) { case ID_STATUS_AWAY: return TranslateT("I've been away since %time%."); case ID_STATUS_NA: return TranslateT("Give it up, I'm not in!"); case ID_STATUS_OCCUPIED: return TranslateT("Not right now."); case ID_STATUS_DND: return TranslateT("Give a guy some peace, would ya?"); case ID_STATUS_FREECHAT: return TranslateT("I'm a chatbot!"); case ID_STATUS_ONLINE: return TranslateT("Yep, I'm here."); case ID_STATUS_OFFLINE: return TranslateT("Nope, not here."); case ID_STATUS_INVISIBLE: return TranslateT("I'm hiding from the mafia."); case ID_STATUS_ONTHEPHONE: return TranslateT("That'll be the phone."); case ID_STATUS_OUTTOLUNCH: return TranslateT("Mmm... food."); case ID_STATUS_IDLE: return TranslateT("idleeeeeeee"); } return NULL; } wchar_t* GetDefaultStatusMessage(PROTOCOLSETTINGEX *ps, int newstatus) { if (ps->szMsg != NULL) {// custom message set log_infoA("CommonStatus: Status message set by calling plugin"); return mir_wstrdup(ps->szMsg); } wchar_t *tMsg = (wchar_t*)CallService(MS_AWAYMSG_GETSTATUSMSGW, newstatus, (LPARAM)ps->szName); log_debugA("CommonStatus: Status message retrieved from general awaysys: %S", tMsg); return tMsg; } static int equalsGlobalStatus(PROTOCOLSETTINGEX **ps) { int i, j, pstatus = 0, gstatus = 0; for (i = 0; i < protoList->getCount(); i++) if (ps[i]->szMsg != NULL && GetActualStatus(ps[i]) != ID_STATUS_OFFLINE) return 0; int count; PROTOACCOUNT **protos; Proto_EnumAccounts(&count, &protos); for (i = 0; i < count; i++) { if (!IsSuitableProto(protos[i])) continue; pstatus = 0; for (j = 0; j < protoList->getCount(); j++) if (!mir_strcmp(protos[i]->szModuleName, ps[j]->szName)) pstatus = GetActualStatus(ps[j]); if (pstatus == 0) pstatus = CallProtoService(protos[i]->szModuleName, PS_GETSTATUS, 0, 0); if (db_get_b(NULL, protos[i]->szModuleName, "LockMainStatus", 0)) { // if proto is locked, pstatus must be the current status if (pstatus != CallProtoService(protos[i]->szModuleName, PS_GETSTATUS, 0, 0)) return 0; } else { if (gstatus == 0) gstatus = pstatus; if (pstatus != gstatus) return 0; } } return gstatus; } static void SetStatusMsg(PROTOCOLSETTINGEX *ps, int newstatus) { wchar_t* tszMsg = GetDefaultStatusMessage(ps, newstatus); if (tszMsg) { /* replace the default vars in msg (I believe this is from core) */ for (int j = 0; tszMsg[j]; j++) { if (tszMsg[j] != '%') continue; wchar_t substituteStr[128]; if (!wcsnicmp(tszMsg + j, L"%time%", 6)) GetTimeFormat(LOCALE_USER_DEFAULT, TIME_NOSECONDS, 0, 0, substituteStr, _countof(substituteStr)); else if (!wcsnicmp(tszMsg + j, L"%date%", 6)) GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, 0, 0, substituteStr, _countof(substituteStr)); else continue; if (mir_wstrlen(substituteStr) > 6) tszMsg = (wchar_t*)mir_realloc(tszMsg, sizeof(wchar_t)*(mir_wstrlen(tszMsg) + 1 + mir_wstrlen(substituteStr) - 6)); memmove(tszMsg + j + mir_wstrlen(substituteStr), tszMsg + j + 6, sizeof(wchar_t)*(mir_wstrlen(tszMsg) - j - 5)); memcpy(tszMsg + j, substituteStr, sizeof(wchar_t)*mir_wstrlen(substituteStr)); } wchar_t *szFormattedMsg = variables_parsedup(tszMsg, ps->tszAccName, NULL); if (szFormattedMsg != NULL) { mir_free(tszMsg); tszMsg = szFormattedMsg; } } log_debugA("CommonStatus sets status message for %s directly", ps->szName); CallProtoService(ps->szName, PS_SETAWAYMSG, newstatus, (LPARAM)tszMsg); mir_free(tszMsg); } INT_PTR SetStatusEx(WPARAM wParam, LPARAM) { PROTOCOLSETTINGEX** protoSettings = *(PROTOCOLSETTINGEX***)wParam; if (protoSettings == NULL) return -1; int globStatus = equalsGlobalStatus(protoSettings); // issue with setting global status; // things get messy because SRAway hooks ME_CLIST_STATUSMODECHANGE, so the status messages of SRAway and // commonstatus will clash NotifyEventHooks(hCSStatusChangedExEvent, (WPARAM)&protoSettings, protoList->getCount()); // set all status messages first for (int i = 0; i < protoList->getCount(); i++) { char *szProto = protoSettings[i]->szName; if (!Proto_GetAccount(szProto)) { log_debugA("CommonStatus: %s is not loaded", szProto); continue; } // some checks int newstatus = GetActualStatus(protoSettings[i]); if (newstatus == 0) { log_debugA("CommonStatus: incorrect status for %s (%d)", szProto, protoSettings[i]->status); continue; } int oldstatus = CallProtoService(szProto, PS_GETSTATUS, 0, 0); // set last status protoSettings[i]->lastStatus = oldstatus; if (IsStatusConnecting(oldstatus)) { // ignore if connecting, but it didn't came this far if it did log_debugA("CommonStatus: %s is already connecting", szProto); continue; } // status checks long protoFlag = Proto_Status2Flag(newstatus); int b_Caps2 = CallProtoService(szProto, PS_GETCAPS, PFLAGNUM_2, 0) & protoFlag; int b_Caps5 = CallProtoService(szProto, PS_GETCAPS, PFLAGNUM_5, 0) & protoFlag; if (newstatus != ID_STATUS_OFFLINE && (!b_Caps2 || b_Caps5)) { // status and status message for this status not supported //log_debug("CommonStatus: status not supported %s", szProto); continue; } int b_Caps1 = CallProtoService(szProto, PS_GETCAPS, PFLAGNUM_1, 0) & PF1_MODEMSGSEND & ~PF1_INDIVMODEMSG; int b_Caps3 = CallProtoService(szProto, PS_GETCAPS, PFLAGNUM_3, 0) & protoFlag; if (newstatus == oldstatus && (!b_Caps1 || !b_Caps3)) { // no status change and status messages are not supported //log_debug("CommonStatus: no change, %s (%d %d)", szProto, oldstatus, newstatus); continue; } // set status message first if (b_Caps1 && b_Caps3) SetStatusMsg(protoSettings[i], newstatus); // set the status if (newstatus != oldstatus /*&& !(b_Caps1 && b_Caps3 && ServiceExists(MS_NAS_SETSTATE))*/) { log_debugA("CommonStatus sets status for %s to %d", szProto, newstatus); CallProtoService(szProto, PS_SETSTATUS, newstatus, 0); } } if (globStatus != 0) { if (!ServiceExists(MS_CLIST_SETSTATUSMODE)) { log_debugA("CommonStatus: MS_CLIST_SETSTATUSMODE not available!"); return -1; } log_debugA("CommonStatus: setting global status %u", globStatus); CallService(MS_CLIST_SETSTATUSMODE, globStatus, 0); } return 0; } static INT_PTR GetProtocolCountService(WPARAM, LPARAM) { return GetProtoCount(); } bool IsSuitableProto(PROTOACCOUNT *pa) { return (pa == NULL) ? false : (pcli->pfnGetProtocolVisibility(pa->szModuleName) != 0); } int GetProtoCount() { int pCount = 0, count; PROTOACCOUNT **accs; Proto_EnumAccounts(&count, &accs); for (int i = 0; i < count; i++) if (IsSuitableProto(accs[i])) pCount++; return pCount; } static int CreateServices() { if (ServiceExists(MS_CS_SETSTATUSEX)) return -1; hCSStatusChangedExEvent = CreateHookableEvent(ME_CS_STATUSCHANGEEX); CreateServiceFunction(MS_CS_SETSTATUSEX, SetStatusEx); CreateServiceFunction(MS_CS_SHOWCONFIRMDLGEX, ShowConfirmDialogEx); CreateServiceFunction(MS_CS_GETPROTOCOUNT, GetProtocolCountService); return 0; } static int onShutdown(WPARAM, LPARAM) { DestroyHookableEvent(hCSStatusChangedExEvent); return 0; } int InitCommonStatus() { if (!CreateServices()) HookEvent(ME_SYSTEM_PRESHUTDOWN, onShutdown); return 0; }