/* Name: NewEventNotify - Plugin for Miranda IM File: popup.c - Displays a popup using the popup-plugin by hrk Version: 2.2.3 Description: Notifies you about some events Author: icebreaker, Date: 21.07.02 15:46 / Update: 16.09.02 17:45 Copyright: (C) 2002 Starzinger Michael 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" extern int g_IsServiceAvail; static int PopupCount = 0; PLUGIN_DATA* PopupList[MAX_POPUPS]; int NumberPopupData(MCONTACT hContact, int eventType) { for (int n = 0; n < MAX_POPUPS; n++) { if (!PopupList[n] && !hContact && eventType == -1) return n; if (PopupList[n] && (PopupList[n]->hContact == hContact) && (PopupList[n]->iLock == 0) && (eventType == -1 || PopupList[n]->eventType == (UINT)eventType)) return n; } return -1; } static int FindPopupData(PLUGIN_DATA* pdata) { for (int n = 0; n < MAX_POPUPS; n++) if (PopupList[n] == pdata) return n; return -1; } static void FreePopupEventData(PLUGIN_DATA* pdata) { pdata->iLock = 1; EVENT_DATA_EX* eventData = pdata->firstEventData; while (eventData) { if (eventData->next) { eventData = eventData->next; mir_free(eventData->prev); eventData->prev = nullptr; } else { mir_free(eventData); eventData = nullptr; } } pdata->lastEventData = pdata->firstEventData = pdata->firstShowEventData = nullptr; // remove from popup list if present if (FindPopupData(pdata) != -1) PopupList[FindPopupData(pdata)] = nullptr; } int PopupAct(HWND hWnd, UINT mask, PLUGIN_DATA* pdata) { if (mask & MASK_OPEN) { if (pdata) { // do MS_MSG_SENDMESSAGE instead if wanted to reply and not read! if (g_plugin.bMsgReplyWindow && pdata->eventType == EVENTTYPE_MESSAGE) CallServiceSync(MS_MSG_SENDMESSAGE, (WPARAM)pdata->hContact, 0); // JK, use core (since 0.3.3+) else { EVENT_DATA_EX *eventData = pdata->firstEventData; if (eventData == nullptr) return 0; for (int idx = 0;; idx++) { CLISTEVENT *cle = g_clistApi.pfnGetEvent(pdata->hContact, idx); if (cle == nullptr) break; if (cle->hDbEvent == eventData->hEvent) { if (ServiceExists(cle->pszService)) CallServiceSync(cle->pszService, 0, (LPARAM)cle); // JK, use core (since 0.3.3+) break; } } } } } if (mask & MASK_REMOVE) { if (pdata) { EVENT_DATA_EX *eventData = pdata->firstEventData; pdata->iLock = 1; while (eventData) { g_clistApi.pfnRemoveEvent(pdata->hContact, eventData->hEvent); db_event_markRead(pdata->hContact, eventData->hEvent); eventData = eventData->next; } FreePopupEventData(pdata); } } if (mask & MASK_DISMISS) { KillTimer(hWnd, TIMER_TO_ACTION); FreePopupEventData(pdata); PUDeletePopup(hWnd); } return 0; } static LRESULT CALLBACK PopupDlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { PLUGIN_DATA *pdata = (PLUGIN_DATA*)PUGetPluginData(hWnd); if (pdata == nullptr) return FALSE; switch (message) { case WM_COMMAND: PopupAct(hWnd, g_plugin.maskActL, pdata); break; case WM_CONTEXTMENU: PopupAct(hWnd, g_plugin.maskActR, pdata); break; case UM_FREEPLUGINDATA: PopupCount--; mir_free(pdata); return TRUE; case UM_INITPOPUP: pdata->hWnd = hWnd; SetTimer(hWnd, TIMER_TO_ACTION, pdata->iSeconds * 1000, nullptr); break; case WM_MOUSEWHEEL: if ((short)HIWORD(wParam) > 0 && pdata->firstShowEventData->prev && (g_plugin.bShowON || pdata->firstShowEventData->number >= g_plugin.iNumberMsg)) { pdata->firstShowEventData = pdata->firstShowEventData->prev; PopupUpdate(pdata->hContact, NULL); } if ((short)HIWORD(wParam) < 0 && pdata->firstShowEventData->next && (!g_plugin.bShowON || pdata->countEvent - pdata->firstShowEventData->number >= g_plugin.iNumberMsg)) { pdata->firstShowEventData = pdata->firstShowEventData->next; PopupUpdate(pdata->hContact, NULL); } break; case WM_SETCURSOR: SetFocus(hWnd); break; case WM_TIMER: if (wParam != TIMER_TO_ACTION) break; PopupAct(hWnd, g_plugin.maskActTE, pdata); break; } return DefWindowProc(hWnd, message, wParam, lParam); } static wchar_t* GetEventPreview(DBEVENTINFO *dbei) { wchar_t *comment1 = nullptr; wchar_t *comment2 = nullptr; char *commentFix = nullptr; // now get text switch (dbei->eventType) { case EVENTTYPE_MESSAGE: if (dbei->pBlob) { if (dbei->flags & DBEF_UTF) { // utf-8 in blob comment1 = mir_utf8decodeW((char*)dbei->pBlob); } else if (dbei->cbBlob == (mir_wstrlen((wchar_t *)dbei->pBlob) + 1)*(sizeof(wchar_t) + 1)) { // wchar in blob (the old hack) comment1 = mir_wstrdup((wchar_t*)dbei->pBlob); } else comment1 = mir_a2u((char *)dbei->pBlob); } commentFix = POPUP_COMMENT_MESSAGE; break; case EVENTTYPE_FILE: if (dbei->pBlob) { char *p = (char*)dbei->pBlob + sizeof(DWORD); // filenames comment2 = (dbei->flags & DBEF_UTF) ? mir_utf8decodeW(p) : mir_a2u(p); p += mir_strlen(p) + 1; // description comment1 = (dbei->flags & DBEF_UTF) ? mir_utf8decodeW(p) : mir_a2u(p); } commentFix = POPUP_COMMENT_FILE; break; case EVENTTYPE_CONTACTS: // blob format is: // ASCIIZ nick // ASCIIZ UID if (dbei->pBlob) { // count contacts in event char* pcBlob = (char *)dbei->pBlob; char* pcEnd = (char *)(dbei->pBlob + dbei->cbBlob); int nContacts; wchar_t szBuf[512]; for (nContacts = 1; ; nContacts++) { // Nick pcBlob += mir_strlen(pcBlob) + 1; // UIN pcBlob += mir_strlen(pcBlob) + 1; // check for end of contacts if (pcBlob >= pcEnd) break; } mir_snwprintf(szBuf, TranslateT("Received %d contacts."), nContacts); comment1 = mir_wstrdup(szBuf); } commentFix = POPUP_COMMENT_CONTACTS; break; case EVENTTYPE_ADDED: // blob format is: // DWORD numeric uin (ICQ only afaik) // DWORD HANDLE to contact // ASCIIZ nick (or text UID) // ASCIIZ first name // ASCIIZ last name // ASCIIZ email (or YID) if (dbei->pBlob) { char szUin[16]; wchar_t szBuf[2048]; wchar_t* szNick = nullptr; char *pszNick = (char *)dbei->pBlob + 8; char *pszFirst = pszNick + mir_strlen(pszNick) + 1; char *pszLast = pszFirst + mir_strlen(pszFirst) + 1; char *pszEmail = pszLast + mir_strlen(pszLast) + 1; mir_snprintf(szUin, "%d", *((DWORD*)dbei->pBlob)); if (mir_strlen(pszNick) > 0) { if (dbei->flags & DBEF_UTF) szNick = mir_utf8decodeW(pszNick); else szNick = mir_a2u(pszNick); } else if (mir_strlen(pszEmail) > 0) { if (dbei->flags & DBEF_UTF) szNick = mir_utf8decodeW(pszEmail); else szNick = mir_a2u(pszEmail); } else if (*((DWORD*)dbei->pBlob) > 0) szNick = mir_a2u(szUin); if (szNick) { mir_wstrcpy(szBuf, szNick); mir_wstrcat(szBuf, TranslateT(" added you to the contact list")); mir_free(szNick); comment1 = mir_wstrdup(szBuf); } } commentFix = POPUP_COMMENT_ADDED; break; case EVENTTYPE_AUTHREQUEST: if (dbei->pBlob) { DB::AUTH_BLOB blob(dbei->pBlob); wchar_t *szNick = nullptr; if (blob.get_nick()) szNick = dbei->getString(blob.get_nick()); else if (blob.get_email()) szNick = dbei->getString(blob.get_email()); else if (blob.get_uin()) { char szUin[16]; szNick = mir_a2u(itoa(blob.get_uin(), szUin, 10)); } if (szNick) comment1 = CMStringW(FORMAT, L"%s%s", szNick, TranslateT(" requested authorization")).Detach(); } commentFix = POPUP_COMMENT_AUTH; break; default: DBEVENTTYPEDESCR *pei = DbEvent_GetType(dbei->szModule, dbei->eventType); // support for custom database event types if (pei && dbei->pBlob) { comment1 = DbEvent_GetTextW(dbei, CP_ACP); commentFix = pei->descr; } else commentFix = POPUP_COMMENT_OTHER; } if (mir_wstrlen(comment1) > 0) { mir_free(comment2); return comment1; } if (mir_wstrlen(comment2) > 0) { mir_free(comment1); return comment2; } mir_free(comment1); mir_free(comment2); return TranslateW(mir_a2u(commentFix)); } int PopupShow(MCONTACT hContact, MEVENT hEvent, UINT eventType) { wchar_t *sampleEvent; long iSeconds; // there has to be a maximum number of popups shown at the same time if (PopupCount >= MAX_POPUPS) return 2; // check if we should report this kind of event // get the prefered icon as well // CHANGE: iSeconds is -1 because I use my timer to hide popup POPUPDATAW pudw = {}; pudw.iSeconds = -1; switch (eventType) { case EVENTTYPE_MESSAGE: if (!(g_plugin.maskNotify & MASK_MESSAGE)) return 1; pudw.lchIcon = Skin_LoadIcon(SKINICON_EVENT_MESSAGE); pudw.colorBack = g_plugin.bDefaultColorMsg ? 0 : g_plugin.colBackMsg; pudw.colorText = g_plugin.bDefaultColorMsg ? 0 : g_plugin.colTextMsg; iSeconds = g_plugin.iDelayMsg; sampleEvent = TranslateT("This is a sample message event :-)"); break; case EVENTTYPE_FILE: if (!(g_plugin.maskNotify & MASK_FILE)) return 1; pudw.lchIcon = Skin_LoadIcon(SKINICON_EVENT_FILE); pudw.colorBack = g_plugin.bDefaultColorFile ? 0 : g_plugin.colBackFile; pudw.colorText = g_plugin.bDefaultColorFile ? 0 : g_plugin.colTextFile; iSeconds = g_plugin.iDelayFile; sampleEvent = TranslateT("This is a sample file event :-D"); break; default: if (!(g_plugin.maskNotify & MASK_OTHER)) return 1; pudw.lchIcon = Skin_LoadIcon(SKINICON_OTHER_MIRANDA); pudw.colorBack = g_plugin.bDefaultColorOthers ? 0 : g_plugin.colBackOthers; pudw.colorText = g_plugin.bDefaultColorOthers ? 0 : g_plugin.colTextOthers; iSeconds = g_plugin.iDelayOthers; sampleEvent = TranslateT("This is a sample other event ;-D"); break; } // get DBEVENTINFO with pBlob if preview is needed (when is test then is off) DBEVENTINFO dbe = {}; if (hEvent) { if ((g_plugin.bPreview || eventType == EVENTTYPE_ADDED || eventType == EVENTTYPE_AUTHREQUEST)) { dbe.cbBlob = db_event_getBlobSize(hEvent); dbe.pBlob = (PBYTE)mir_alloc(dbe.cbBlob); } db_event_get(hEvent, &dbe); } EVENT_DATA_EX *eventData = (EVENT_DATA_EX*)mir_alloc(sizeof(EVENT_DATA_EX)); eventData->hEvent = hEvent; eventData->number = 1; eventData->next = nullptr; eventData->prev = nullptr; // retrieve correct hContact for AUTH events if (dbe.pBlob && (eventType == EVENTTYPE_ADDED || eventType == EVENTTYPE_AUTHREQUEST)) hContact = DbGetAuthEventContact(&dbe); // set plugin_data ... will be usable within PopupDlgProc PLUGIN_DATA *pdata = (PLUGIN_DATA*)mir_calloc(sizeof(PLUGIN_DATA)); pdata->eventType = eventType; pdata->hContact = hContact; pdata->countEvent = 1; pdata->iLock = 0; pdata->iSeconds = (iSeconds > 0) ? iSeconds : g_plugin.iDelayDefault; pdata->firstEventData = pdata->firstShowEventData = pdata->lastEventData = eventData; // finally create the popup pudw.lchContact = hContact; pudw.PluginWindowProc = PopupDlgProc; pudw.PluginData = pdata; // if hContact is NULL, && hEvent is NULL then popup is only Test if ((hContact == NULL) && (hEvent == NULL)) { wcsncpy(pudw.lpwzContactName, TranslateT("Plugin Test"), MAX_CONTACTNAME); wcsncpy(pudw.lpwzText, TranslateW(sampleEvent), MAX_SECONDLINE); } else { // get the needed event data wcsncpy(pudw.lpwzContactName, Clist_GetContactDisplayName(hContact), MAX_CONTACTNAME); wcsncpy(pudw.lpwzText, ptrW(GetEventPreview(&dbe)), MAX_SECONDLINE); } PopupCount++; PopupList[NumberPopupData(NULL, -1)] = pdata; // send data to popup plugin // popup creation failed, release popupdata if (PUAddPopupW(&pudw) < 0) { FreePopupEventData(pdata); mir_free(pdata); } if (dbe.pBlob) mir_free(dbe.pBlob); return 0; } int PopupUpdate(MCONTACT hContact, MEVENT hEvent) { // merge only message popups PLUGIN_DATA *pdata = (PLUGIN_DATA*)PopupList[NumberPopupData(hContact, EVENTTYPE_MESSAGE)]; if (hEvent) { pdata->countEvent++; pdata->lastEventData->next = (EVENT_DATA_EX *)mir_alloc(sizeof(EVENT_DATA_EX)); pdata->lastEventData->next->prev = pdata->lastEventData; pdata->lastEventData = pdata->lastEventData->next; pdata->lastEventData->hEvent = hEvent; pdata->lastEventData->number = pdata->lastEventData->prev->number + 1; pdata->lastEventData->next = nullptr; if (!g_plugin.bShowON && pdata->countEvent > g_plugin.iNumberMsg && g_plugin.iNumberMsg) pdata->firstShowEventData = pdata->firstShowEventData->next; // re-init timer delay KillTimer(pdata->hWnd, TIMER_TO_ACTION); SetTimer(pdata->hWnd, TIMER_TO_ACTION, pdata->iSeconds * 1000, nullptr); } wchar_t lpzText[MAX_SECONDLINE * 2] = L"\0\0"; if (g_plugin.bShowHeaders) mir_snwprintf(lpzText, TranslateT("[b]Number of new message(s): %d[/b]\n"), pdata->countEvent); int doReverse = g_plugin.bShowON; if ((pdata->firstShowEventData != pdata->firstEventData && doReverse) || (pdata->firstShowEventData != pdata->lastEventData && !doReverse)) mir_snwprintf(lpzText, L"%s...\n", lpzText); // take the active event as starting one EVENT_DATA_EX *eventData = pdata->firstShowEventData; int iEvent = 0; while (true) { if (iEvent) eventData = (doReverse) ? eventData->next : eventData->prev; iEvent++; // get DBEVENTINFO with pBlob if preview is needed (when is test then is off) DBEVENTINFO dbe = {}; dbe.pBlob = nullptr; dbe.cbBlob = 0; if (g_plugin.bPreview && eventData->hEvent) { dbe.cbBlob = db_event_getBlobSize(eventData->hEvent); dbe.pBlob = (PBYTE)mir_alloc(dbe.cbBlob); } if (eventData->hEvent) db_event_get(eventData->hEvent, &dbe); if (g_plugin.bShowDate || g_plugin.bShowTime) { wchar_t timestamp[MAX_DATASIZE]; wchar_t formatTime[MAX_DATASIZE]; if (g_plugin.bShowDate) wcsncpy(formatTime, L"%Y.%m.%d", _countof(formatTime)); else if (g_plugin.bShowTime) mir_wstrncat(formatTime, L" %H:%M", _countof(formatTime) - mir_wstrlen(formatTime)); time_t localTime = dbe.timestamp; wcsftime(timestamp, _countof(timestamp), formatTime, localtime(&localTime)); mir_snwprintf(lpzText, L"%s[b][i]%s[/i][/b]\n", lpzText, timestamp); } // prepare event preview wchar_t* szEventPreview = GetEventPreview(&dbe); mir_snwprintf(lpzText, L"%s%s", lpzText, szEventPreview); mir_free(szEventPreview); if (dbe.pBlob) mir_free(dbe.pBlob); if (doReverse) { if ((iEvent >= g_plugin.iNumberMsg && g_plugin.iNumberMsg) || !eventData->next) break; } else if ((iEvent >= g_plugin.iNumberMsg && g_plugin.iNumberMsg) || !eventData->prev) break; mir_snwprintf(lpzText, L"%s\n", lpzText); } if ((doReverse && eventData->next) || (!doReverse && eventData->prev)) mir_snwprintf(lpzText, L"%s\n...", lpzText); PUChangeTextW(pdata->hWnd, lpzText); return 0; } int PopupPreview() { PopupShow(0, 0, EVENTTYPE_MESSAGE); PopupShow(0, 0, EVENTTYPE_FILE); PopupShow(0, 0, -1); return 0; }