#include "stdafx.h" #define ID_CLOSETIMER 0x0101 #define ID_MOVETIMER 0x0102 DWORD pop_start_x, pop_start_y; int global_mouse_in = 0; void trimW(wchar_t *str) { int len = (int)mir_wstrlen(str), pos; // trim whitespace (e.g. from OTR detection) for (pos = len - 1; pos >= 0; pos--) { if (str[pos] == L' ' || str[pos] == L'\t' || str[pos] == L'\r' || str[pos] == L'\n') str[pos] = 0; else break; } // remove tabs for (pos = len - 1; pos >= 0; pos--) if (str[pos] == L'\t') str[pos] = L' '; } struct HWNDStackNode { HWND hwnd; struct HWNDStackNode *next; }; HWNDStackNode *hwnd_stack_top = nullptr; int stack_size = 0; void RepositionWindows() { HWNDStackNode *current; int x = pop_start_x, y; int height; if (options.animate == ANIMATE_HORZ) { if (options.location == PL_BOTTOMRIGHT || options.location == PL_TOPRIGHT) x -= options.win_width + 1; if (options.location == PL_BOTTOMLEFT || options.location == PL_TOPLEFT) x += options.win_width + 1; } // Особый случай: выдвижение окна из-за верхнего края экрана. if ((options.animate == ANIMATE_VERT) && (options.location == PL_TOPLEFT || options.location == PL_TOPRIGHT)) y = 0; else y = pop_start_y; current = hwnd_stack_top; while (current) { SendMessage(current->hwnd, PUM_GETHEIGHT, (WPARAM)&height, 0); // Если окна размещать у нижнего края, то координата текущего окна меньше на его высоту. if (options.location == PL_BOTTOMLEFT || options.location == PL_BOTTOMRIGHT) y -= height + 1; // Перемещаем окно. SendMessage(current->hwnd, PUM_MOVE, (WPARAM)x, (LPARAM)y); // Координата для следующего окна. // Если окна размещать у верхнего края, то координата следующего окна больше на высоту текущего окна. if (options.location == PL_TOPLEFT || options.location == PL_TOPRIGHT) y += height + 1; // Переходим к следующему окну. current = current->next; } } void AddWindowToStack(HWND hwnd) { HWNDStackNode *new_node = (HWNDStackNode *)mir_alloc(sizeof(HWNDStackNode)); new_node->hwnd = hwnd; new_node->next = hwnd_stack_top; hwnd_stack_top = new_node; int height; SendMessage(hwnd, PUM_GETHEIGHT, (WPARAM)&height, 0); RECT wa_rect; SystemParametersInfo(SPI_GETWORKAREA, 0, &wa_rect, 0); if (options.use_mim_monitor) { RECT clr; GetWindowRect(pcli->hwndContactList, &clr); HMONITOR hMonitor = MonitorFromRect(&clr, MONITOR_DEFAULTTONEAREST); if (hMonitor) { MONITORINFO mi; mi.cbSize = sizeof(mi); if (GetMonitorInfo(hMonitor, &mi)) wa_rect = mi.rcWork; } } switch (options.animate) { case ANIMATE_NO: if (options.location == PL_BOTTOMRIGHT || options.location == PL_TOPRIGHT) pop_start_x = wa_rect.right - options.win_width - 1; else pop_start_x = wa_rect.left + 1; if (options.location == PL_BOTTOMRIGHT || options.location == PL_BOTTOMLEFT) pop_start_y = wa_rect.bottom; else pop_start_y = wa_rect.top + 1; break; case ANIMATE_HORZ: if (options.location == PL_BOTTOMRIGHT || options.location == PL_TOPRIGHT) pop_start_x = wa_rect.right; else pop_start_x = wa_rect.left - options.win_width; if (options.location == PL_BOTTOMRIGHT || options.location == PL_BOTTOMLEFT) pop_start_y = wa_rect.bottom; else pop_start_y = wa_rect.top + 1; break; case ANIMATE_VERT: if (options.location == PL_BOTTOMRIGHT || options.location == PL_TOPRIGHT) pop_start_x = wa_rect.right - options.win_width - 1; else pop_start_x = wa_rect.left + 1; if (options.location == PL_BOTTOMRIGHT || options.location == PL_BOTTOMLEFT) pop_start_y = wa_rect.bottom; else pop_start_y = wa_rect.top - height + 1; break; } SetWindowPos(hwnd, nullptr, pop_start_x, pop_start_y, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); stack_size++; RepositionWindows(); } void RemoveWindowFromStack(HWND hwnd) { HWNDStackNode *current = hwnd_stack_top, *prev = nullptr; while(current) { if (current->hwnd == hwnd) { if (prev) prev->next = current->next; else hwnd_stack_top = current->next; mir_free(current); stack_size--; break; } prev = current; current = current->next; } // Если после удаления в стеке остались окна, то нужно провести сжатие: // сдвинуть все окна к верхнему/нижнему краю экрана. if (hwnd_stack_top) RepositionWindows(); } void BroadcastMessage(UINT msg, WPARAM wParam, LPARAM lParam) { HWNDStackNode *current = hwnd_stack_top; while(current) { SendMessage(current->hwnd, msg, wParam, lParam); current = current->next; } } void DeinitWindowStack() { HWNDStackNode *current = hwnd_stack_top; hwnd_stack_top = nullptr; while(current) { HWNDStackNode *pNext = current->next; DestroyWindow(current->hwnd); current = pNext; } } struct PopupWindowData { PopupData *pd; int new_x, new_y; bool is_round, av_is_round, mouse_in, close_on_leave; bool custom_col; HBRUSH bkBrush, barBrush, underlineBrush; HPEN bPen; wchar_t tbuff[128]; int tb_height, av_height, text_height, time_height, time_width; int real_av_width, real_av_height; bool have_av; HANDLE hNotify; }; LRESULT CALLBACK PopupWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { PopupWindowData *pwd = (PopupWindowData *)GetWindowLongPtr(hwnd, GWLP_USERDATA); PopupData *pd = nullptr; if (pwd) pd = pwd->pd; switch(uMsg) { case WM_CREATE: { CREATESTRUCT *cs = (CREATESTRUCT *)lParam; pwd = (PopupWindowData *)mir_alloc(sizeof(PopupWindowData)); pd = (PopupData *)cs->lpCreateParams; pwd->pd = pd; pwd->hNotify = nullptr; trimW(pwd->pd->pwzTitle); trimW(pwd->pd->pwzText); pwd->is_round = options.round; pwd->av_is_round = options.av_round; pwd->mouse_in = pwd->close_on_leave = false; pwd->custom_col = (pd->colorBack != pd->colorText); pwd->tb_height = pwd->av_height = pwd->text_height = pwd->time_height = pwd->time_width = 0; pwd->have_av = false; if (pwd->custom_col) { pwd->bkBrush = CreateSolidBrush(pd->colorBack); DWORD darkBg = pd->colorBack - ((pd->colorBack >> 2) & 0x3f3f3f); // 3/4 of current individual RGB components pwd->barBrush = CreateSolidBrush(darkBg); // make sidebar a dark version of the bg pwd->underlineBrush = CreateSolidBrush(pd->colorBack); // make sidebar a dark version of the bg } else { pwd->bkBrush = CreateSolidBrush(colBg); pwd->barBrush = CreateSolidBrush(colSidebar); pwd->underlineBrush = CreateSolidBrush(colTitleUnderline); } if (options.border) pwd->bPen = (HPEN)CreatePen(PS_SOLID, 1, colBorder); else pwd->bPen = CreatePen(PS_SOLID, 1, pwd->custom_col ? pd->colorBack : colBg); SYSTEMTIME st; GetLocalTime(&st); GetTimeFormat(LOCALE_USER_DEFAULT, TIME_NOSECONDS, &st, nullptr, pwd->tbuff, 128); SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)pwd); // make a really long timeout - say 7 days? ;) if (pd->timeout == -1 || (pd->timeout == 0 && options.default_timeout == -1)) SetTimer(hwnd, ID_CLOSETIMER, 7 * 24 * 60 * 60 * 1000, nullptr); else if (pd->timeout == 0) SetTimer(hwnd, ID_CLOSETIMER, options.default_timeout * 1000, nullptr); else SetTimer(hwnd, ID_CLOSETIMER, pd->timeout * 1000, nullptr); AddWindowToStack(hwnd); // this updates our size } // transparency #ifdef WS_EX_LAYERED SetWindowLongPtr(hwnd, GWL_EXSTYLE, GetWindowLongPtr(hwnd, GWL_EXSTYLE) | WS_EX_LAYERED); #endif #ifdef CS_DROPSHADOW if (options.drop_shadow) { SetClassLong(hwnd, GCL_STYLE, CS_DROPSHADOW); } #endif #ifdef LWA_ALPHA SetLayeredWindowAttributes(hwnd, RGB(0,0,0), (int)(options.opacity / 100.0 * 255), LWA_ALPHA); if (options.trans_bg) { COLORREF bg; if (pd->colorBack == pd->colorText) bg = colBg; else bg = pd->colorBack; SetLayeredWindowAttributes(hwnd, bg, 0, LWA_COLORKEY); } #endif PostMessage(hwnd, UM_INITPOPUP, (WPARAM)hwnd, 0); return 0; case WM_MOUSEMOVE: if (pwd && !pwd->mouse_in) { pwd->mouse_in = true; global_mouse_in++; TRACKMOUSEEVENT tme = { 0 }; tme.cbSize = sizeof(tme); tme.dwFlags = TME_LEAVE; tme.hwndTrack = hwnd; TrackMouseEvent(&tme); } break; case WM_MOUSELEAVE: if (pwd && pwd->mouse_in) { pwd->mouse_in = false; global_mouse_in--; } return 0; case WM_LBUTTONUP: // fake STN_CLICKED notification SendMessage(hwnd, WM_COMMAND, MAKEWPARAM(0, STN_CLICKED), 0); break; case WM_TIMER: if (wParam == ID_CLOSETIMER) { KillTimer(hwnd, ID_CLOSETIMER); if (pwd->mouse_in || (options.global_hover && global_mouse_in)) SetTimer(hwnd, ID_CLOSETIMER, 800, nullptr); // reset timer if mouse in window - allow another 800 ms else { PostMessage(hwnd, UM_DESTROYPOPUP, 0, 0); } return TRUE; } if (wParam == ID_MOVETIMER) { RECT r; GetWindowRect(hwnd, &r); if (r.left == pwd->new_x && r.top == pwd->new_y) { KillTimer(hwnd, ID_MOVETIMER); return TRUE; } int adj_x = (pwd->new_x - r.left) / 4, adj_y = (pwd->new_y - r.top) / 4; if (adj_x == 0) adj_x = (pwd->new_x - r.left); if (adj_y == 0) adj_y = (pwd->new_y - r.top); int x = r.left + adj_x, y = r.top + adj_y; SetWindowPos(hwnd, nullptr, x, y, 0, 0, SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE); if (!IsWindowVisible(hwnd)) { ShowWindow(hwnd, SW_SHOWNOACTIVATE); UpdateWindow(hwnd); } return TRUE; } break; case WM_ERASEBKGND: { HDC hdc = (HDC) wParam; RECT r, r_bar; GetClientRect(hwnd, &r); // bg FillRect(hdc, &r, pwd->bkBrush); // sidebar r_bar = r; r_bar.right = r.left + options.sb_width; FillRect(hdc, &r_bar, pwd->barBrush); // border if (options.border) { HBRUSH hOldBrush = (HBRUSH)SelectObject(hdc, GetStockObject(NULL_BRUSH)); HPEN hOldPen = (HPEN)SelectObject(hdc, pwd->bPen); int h = 0; if (options.round) { int v; int w=14; h=(r.right-r.left)>(w*2)?w:(r.right-r.left); v=(r.bottom-r.top)>(w*2)?w:(r.bottom-r.top); h=(hhave_av || options.time_layout == PT_WITHAV)) { RECT avr = { 0 }; avr.top = options.av_padding; if (options.av_layout == PAV_LEFT) { avr.left = r.left + options.av_padding; if (pwd->have_av && options.time_layout == PT_WITHAV) avr.right = avr.left + max(pwd->real_av_width, pwd->time_width); else if (pwd->have_av) avr.right = avr.left + pwd->real_av_width; else avr.right = avr.left + pwd->time_width; r.left = avr.right; } else if (options.av_layout == PAV_RIGHT) { avr.right = r.right - options.av_padding; if (pwd->have_av && options.time_layout == PT_WITHAV) avr.left = avr.right - max(pwd->real_av_width, pwd->time_width); else if (pwd->have_av) avr.left = avr.right - pwd->real_av_width; else avr.left = avr.right - pwd->time_width; r.right = avr.left; } if (options.time_layout == PT_WITHAV) { avr.top = options.padding; avr.bottom = avr.top + pwd->time_height; if (pwd->custom_col) SetTextColor(ps.hdc, pd->colorText); else SetTextColor(ps.hdc, colTime); if (hFontTime) SelectObject(hdc, (HGDIOBJ)hFontTime); DrawText(ps.hdc, pwd->tbuff, -1, &avr, DT_VCENTER | DT_CENTER | DT_LEFT | DT_SINGLELINE | DT_NOPREFIX); avr.top = avr.bottom + options.av_padding; } if (pwd->have_av) { // correct for wider time if (options.time_layout == PT_WITHAV && pwd->time_width > options.av_size) { avr.left = avr.left + (pwd->time_width - pwd->real_av_width) / 2; avr.right = avr.left + pwd->real_av_width; } avr.bottom = avr.top + pwd->real_av_height; AVATARDRAWREQUEST adr = {0}; adr.cbSize = sizeof(adr); adr.hContact = pd->hContact; adr.hTargetDC = ps.hdc; adr.rcDraw = avr; adr.dwFlags = (pwd->av_is_round ? AVDRQ_ROUNDEDCORNER : 0); adr.radius = 5; //(pwd->av_is_round ? 5 : 0); CallService(MS_AV_DRAWAVATAR, 0, (LPARAM)&adr); } } // title icon int iconx, textxmin = r.left + options.padding, textxmax = r.right - options.padding; if (pd->hIcon) { if (options.right_icon) { iconx = r.right - (16 + options.padding); textxmax -= 16 + options.padding; } else { iconx = r.left + options.padding; textxmin += 16 + options.padding; } DrawIconEx(ps.hdc, iconx, options.padding + (pwd->tb_height - 16) / 2, pd->hIcon, 16, 16, 0, nullptr, DI_NORMAL); } // title time if (options.time_layout == PT_LEFT || options.time_layout == PT_RIGHT) { RECT ttr; ttr.top = r.top + options.padding; ttr.bottom = ttr.top + pwd->tb_height; if (pwd->custom_col) SetTextColor(ps.hdc, pd->colorText); else SetTextColor(ps.hdc, colTime); if (hFontTime) SelectObject(hdc, (HGDIOBJ)hFontTime); switch(options.time_layout) { case PT_LEFT: ttr.left = textxmin; ttr.right = ttr.left + pwd->time_width; textxmin += pwd->time_width + options.padding; DrawText(ps.hdc, pwd->tbuff, -1, &ttr, DT_VCENTER | DT_LEFT | DT_SINGLELINE | DT_NOPREFIX); break; case PT_RIGHT: ttr.right = textxmax; ttr.left = ttr.right - pwd->time_width; textxmax -= pwd->time_width + options.padding; DrawText(ps.hdc, pwd->tbuff, -1, &ttr, DT_VCENTER | DT_LEFT | DT_SINGLELINE | DT_NOPREFIX); break; } } if (textxmin < options.sb_width) textxmin = options.sb_width + options.padding / 2; // title text if (hFontFirstLine) SelectObject(ps.hdc, (HGDIOBJ)hFontFirstLine); RECT tr; tr.left = r.left + options.padding + options.text_indent; tr.right = textxmax; tr.top = r.top + options.padding; tr.bottom = tr.top + pwd->tb_height; if (pwd->custom_col) SetTextColor(ps.hdc, pd->colorText); else SetTextColor(ps.hdc, colFirstLine); wchar_t *title = mir_wstrdup(pd->pwzTitle); DrawText(ps.hdc, title, -1, &tr, DT_VCENTER | DT_LEFT | DT_END_ELLIPSIS | DT_SINGLELINE | DT_NOPREFIX); mir_free(title); // title underline RECT tur; tur.left = r.left + options.sb_width + options.padding; tur.right = r.right - options.padding; tur.top = tr.bottom + options.padding/2; tur.bottom = tur.top + 1; FillRect(ps.hdc, &tur, pwd->underlineBrush); // second line(s) if (pd->pwzText[0]) { if (hFontSecondLine) SelectObject(ps.hdc, (HGDIOBJ)hFontSecondLine); if (!pwd->custom_col) SetTextColor(ps.hdc, colSecondLine); // expand text if no avatar and the time isn't too large if (options.av_layout != PAV_NONE && options.time_layout == PT_WITHAV && pwd->time_height <= pwd->tb_height && !pwd->have_av) GetClientRect(hwnd, &r); wchar_t *text = mir_wstrdup(pd->pwzText); tr.left = r.left + options.padding + options.text_indent; tr.right = r.right - options.padding; tr.top = tr.bottom + options.padding; tr.bottom = r.bottom - options.padding; DrawText(ps.hdc, text, -1, &tr, DT_NOPREFIX | DT_WORDBREAK | DT_EXTERNALLEADING | DT_TOP | DT_LEFT | DT_WORD_ELLIPSIS); mir_free(text); } EndPaint(hwnd, &ps); } return 0; case WM_DESTROY: if (pwd->mouse_in) global_mouse_in--; ShowWindow(hwnd, SW_HIDE); DeleteObject(pwd->bkBrush); DeleteObject(pwd->bPen); DeleteObject(pwd->barBrush); DeleteObject(pwd->underlineBrush); KillTimer(hwnd, ID_MOVETIMER); KillTimer(hwnd, ID_CLOSETIMER); RemoveWindowFromStack(hwnd); SendMessage(hwnd, UM_FREEPLUGINDATA, 0, 0); if (pd) { pd->SetIcon(nullptr); mir_free(pd->pwzTitle); mir_free(pd->pwzText); mir_free(pd); } mir_free(pwd); pwd = nullptr; pd = nullptr; SetWindowLongPtr(hwnd, GWLP_USERDATA, 0); break; case PUM_UPDATERGN: // round corners if (pwd->is_round) { HRGN hRgn1; RECT r; int w = 11; GetWindowRect(hwnd,&r); int h = (r.right-r.left) > (w*2)?w:(r.right-r.left); int v = (r.bottom-r.top) > (w*2)?w:(r.bottom-r.top); h=(hnew_x = (int)wParam; pwd->new_y = (int)lParam; SetTimer(hwnd, ID_MOVETIMER, 10, nullptr); } else { SetWindowPos(hwnd, nullptr, (int)wParam, (int)lParam, 0, 0, SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE); if (!IsWindowVisible(hwnd)) { ShowWindow(hwnd, SW_SHOWNOACTIVATE); UpdateWindow(hwnd); } } return TRUE; case PUM_SETTEXT: replaceStrW(pd->ptzText, (wchar_t*)lParam); InvalidateRect(hwnd, nullptr, TRUE); RepositionWindows(); return TRUE; case PUM_GETCONTACT: { MCONTACT *phContact = (MCONTACT*)wParam; *phContact = pd->hContact; if (lParam) SetEvent((HANDLE)lParam); } return TRUE; case PUM_GETHEIGHT: { int *pHeight = (int*)wParam; HDC hdc = GetDC(hwnd); SIZE size; HFONT hOldFont = (HFONT)GetCurrentObject(hdc, OBJ_FONT); // time_height + width if (options.time_layout != PT_NONE) { SIZE size_t; if (hFontTime) SelectObject(hdc, (HGDIOBJ)hFontTime); GetTextExtentPoint32(hdc, pwd->tbuff, (int)mir_wstrlen(pwd->tbuff), &size_t); pwd->time_height = size_t.cy; pwd->time_width = size_t.cx; } // titlebar height if (hFontFirstLine) SelectObject(hdc, (HGDIOBJ)hFontFirstLine); wchar_t *title = mir_wstrdup(pd->pwzTitle); GetTextExtentPoint32(hdc, title, (int)mir_wstrlen(title), &size); mir_free(title); pwd->tb_height = size.cy; if (options.time_layout == PT_LEFT || options.time_layout == PT_RIGHT) { if (pwd->tb_height < pwd->time_height) pwd->tb_height = pwd->time_height; } if (pwd->tb_height < 16) pwd->tb_height = 16; // avatar height if (options.av_layout != PAV_NONE && ServiceExists(MS_AV_DRAWAVATAR)) { AVATARCACHEENTRY *ace = (AVATARCACHEENTRY *)CallService(MS_AV_GETAVATARBITMAP, (WPARAM)pd->hContact, 0); if (ace && (ace->dwFlags & AVS_BITMAP_VALID) && !(ace->dwFlags & AVS_HIDEONCLIST)) { if (ace->bmHeight >= ace->bmWidth) { pwd->real_av_height = options.av_size; pwd->real_av_width = options.av_size * ace->bmWidth / ace->bmHeight; } else { pwd->real_av_height = options.av_size * ace->bmHeight / ace->bmWidth; pwd->real_av_width = options.av_size; } pwd->have_av = true; pwd->av_height = pwd->real_av_height; } } // text height if (pd->pwzText[0]) { RECT r; r.left = r.top = 0; r.right = options.win_width - 2 * options.padding - options.text_indent; if (pwd->have_av && options.time_layout == PT_WITHAV) r.right -= (max(options.av_size, pwd->time_width) + options.padding); else if (pwd->have_av) r.right -= (options.av_size + options.padding); else if (options.av_layout != PAV_NONE && options.time_layout == PT_WITHAV && pwd->time_height >= pwd->tb_height) r.right -= pwd->time_width + options.padding; if (hFontSecondLine) SelectObject(hdc, (HGDIOBJ)hFontSecondLine); wchar_t *text = mir_wstrdup(pd->pwzText); DrawText(hdc, text, -1, &r, DT_CALCRECT | DT_NOPREFIX | DT_WORDBREAK | DT_EXTERNALLEADING | DT_TOP | DT_LEFT | DT_WORD_ELLIPSIS); pwd->text_height = r.bottom; mir_free(text); } SelectObject(hdc, hOldFont); ReleaseDC(hwnd, hdc); if (options.time_layout == PT_WITHAV && options.av_layout != PAV_NONE) *pHeight = max(pwd->tb_height + pwd->text_height + 3 * options.padding, pwd->av_height + pwd->time_height + options.padding + 2 * options.av_padding); else *pHeight = max(pwd->tb_height + pwd->text_height + 3 * options.padding, pwd->av_height + 2 * options.av_padding); if (*pHeight > options.win_max_height) *pHeight = options.win_max_height; RECT r; GetWindowRect(hwnd, &r); if (r.right - r.left != options.win_width || r.bottom - r.top != *pHeight) { SetWindowPos(hwnd, nullptr, 0, 0, options.win_width, *pHeight, SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE); SendMessage(hwnd, PUM_UPDATERGN, 0, 0); InvalidateRect(hwnd, nullptr, TRUE); } } return TRUE; case PUM_GETOPAQUE: { void **pData = (void **)wParam; if (pd) *pData = pd->opaque; if (lParam) SetEvent((HANDLE)lParam); } return TRUE; case PUM_CHANGE: { KillTimer(hwnd, ID_CLOSETIMER); if (pd) { mir_free(pd->pwzTitle); mir_free(pd->pwzText); mir_free(pd); } pwd->pd = pd = (PopupData *)lParam; // make a really long timeout - say 7 days? ;) if (pd->timeout == -1) SetTimer(hwnd, ID_CLOSETIMER, 7 * 24 * 60 * 60 * 1000, nullptr); else if (pd->timeout == 0) SetTimer(hwnd, ID_CLOSETIMER, 7 * 1000, nullptr); else SetTimer(hwnd, ID_CLOSETIMER, pd->timeout * 1000, nullptr); InvalidateRect(hwnd, nullptr, TRUE); RepositionWindows(); } return TRUE; case PUM_SETNOTIFYH: pwd->hNotify = (HANDLE)wParam; return TRUE; case PUM_UPDATENOTIFY: if (pwd->hNotify == (HANDLE)wParam) { pd->colorBack = MNotifyGetDWord(pwd->hNotify, NFOPT_BACKCOLOR, colBg); pd->colorText = MNotifyGetDWord(pwd->hNotify, NFOPT_TEXTCOLOR, colSecondLine); pd->timeout = MNotifyGetDWord(pwd->hNotify, NFOPT_TIMEOUT, options.default_timeout); pd->hContact = (MCONTACT)MNotifyGetDWord(pwd->hNotify, NFOPT_CONTACT, 0); pd->hIcon = (HICON)MNotifyGetDWord(pwd->hNotify, NFOPT_ICON, 0); const wchar_t *swzName = MNotifyGetWString(pwd->hNotify, NFOPT_TITLEW, nullptr); mir_free(pd->pwzTitle); pd->pwzTitle = mir_wstrdup(swzName); const wchar_t *swzText = MNotifyGetWString(pwd->hNotify, NFOPT_TEXTW, nullptr); mir_free(pd->pwzText); pd->pwzText = mir_wstrdup(swzText); InvalidateRect(hwnd, nullptr, TRUE); RepositionWindows(); } return TRUE; case PUM_KILLNOTIFY: if (pwd->hNotify != (HANDLE)wParam) return TRUE; // drop through case UM_DESTROYPOPUP: PostMPMessage(MUM_DELETEPOPUP, 0, (LPARAM)hwnd); return TRUE; } if (pd && pd->windowProc) return CallWindowProc(pd->windowProc, hwnd, uMsg, wParam, lParam); // provide a way to close popups, if no PluginWindowProc is provided if (uMsg == WM_CONTEXTMENU) { SendMessage(hwnd, UM_DESTROYPOPUP, 0, 0); return TRUE; } return DefWindowProc(hwnd, uMsg, wParam, lParam); } void PopupData::SetIcon(HICON hNewIcon) { if (flags & PDF_ICOLIB) { IcoLib_ReleaseIcon(hIcon); flags &= ~PDF_ICOLIB; } hIcon = hNewIcon; if (IcoLib_IsManaged(hIcon)) { IcoLib_AddRef(hIcon); flags |= PDF_ICOLIB; } }