diff --git a/main.c b/main.c index 338acc3f..007e99e4 100644 --- a/main.c +++ b/main.c @@ -502,7 +502,7 @@ HandleCopyDataMessage(const COPYDATASTRUCT *copy_data) } else if (copy_data->dwData == WM_OVPN_RESCAN) { - OnNotifyTray(WM_OVPN_RESCAN); + OnNotifyTray(0, WM_OVPN_RESCAN); } else { @@ -641,7 +641,7 @@ WindowProcedure(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) break; case WM_NOTIFYICONTRAY: - OnNotifyTray(lParam); /* Manages message from tray */ + OnNotifyTray(wParam, lParam); /* Manages message from tray */ break; case WM_COPYDATA: /* custom messages with data from other processes */ diff --git a/tray.c b/tray.c index a5e48900..a9c47e4f 100644 --- a/tray.c +++ b/tray.c @@ -28,6 +28,7 @@ #include #include #include +#include #include "tray.h" #include "main.h" @@ -47,6 +48,9 @@ int hmenu_size = 0; /* allocated size of hMenuConn array */ HBITMAP hbmpConnecting; NOTIFYICONDATA ni; +HWND traytip; /* handle of tooltip window for tray icon */ +TOOLINFO ti; /* global tool info structure for tool tip of tray icon*/ + extern options_t o; #define USE_NESTED_CONFIG_MENU ((o.config_menu_view == CONFIG_VIEW_AUTO && o.num_configs > 25) \ @@ -351,15 +355,43 @@ RecreatePopupMenus(void) CreatePopupMenus(); } +/* + * Position tool tip window so that it does not overlap with the mouse position + * and does not spill out of the screen. If mouse location overlaps, NIM_POPUPCLOSE + * and NIM_POPUPOPEN will trigger continuously. + * Account for the possibility that the taskbar could be at the bottom, right, top + * or left edge of the screen. + * + * On input (x, y) is the mouse position that triggered the display of tooltip + */ +static void +PositionTrayToolTip(LONG x, LONG y) +{ + RECT r; + LONG cxmax = GetSystemMetrics(SM_CXSCREEN); + GetWindowRect(traytip, &r); + LONG w = r.right - r.left; + LONG h = r.bottom - r.top; + /* - vertically, position the bottom of the window 10 pixels above y or top of the window + * 10 pixels below y depending on whether we are closer to the bottom or top of the screen. + * - horizontally, try to centre around x adjusting for overflow to the right or left + */ + r.left = (x < w/2) ? 0 : ((x + w/2 < cxmax) ? x - w/2 : cxmax - w); + r.top = (y > h + 10) ? y - (h + 10) : y + 10; + SendMessageW(traytip, TTM_TRACKPOSITION, 0, MAKELONG(r.left, r.top)); + SetWindowPos(traytip, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE|SWP_NOMOVE); +} + /* * Handle mouse clicks on tray icon */ void -OnNotifyTray(LPARAM lParam) +OnNotifyTray(WPARAM wParam, LPARAM lParam) { POINT pt; - switch (lParam) + /* Use LOWORD(lParam) as HIWORD() contains the icon id if uVersion >= 4 */ + switch (LOWORD(lParam)) { case WM_RBUTTONUP: RecreatePopupMenus(); @@ -401,6 +433,22 @@ OnNotifyTray(LPARAM lParam) } break; + /* handle messages when mouse enters and leaves the icon -- we show the custom tooltip window */ + case NIN_POPUPOPEN: + if (traytip) + { + SendMessageW(traytip, TTM_TRACKACTIVATE, (WPARAM)TRUE, (LPARAM) &ti); + PositionTrayToolTip(LOWORD(wParam), HIWORD(wParam)); /* wParam has mouse position */ + } + break; + + case NIN_POPUPCLOSE: + if (traytip) + { + SendMessageW(traytip, TTM_TRACKACTIVATE, (WPARAM)FALSE, 0); + } + break; + case WM_OVPN_RESCAN: /* Rescan config folders and recreate popup menus */ RecreatePopupMenus(); @@ -416,6 +464,11 @@ RemoveTrayIcon() Shell_NotifyIcon(NIM_DELETE, &ni); CLEAR(ni); } + if (traytip) + { + DestroyWindow(traytip); + traytip = NULL; + } } void @@ -435,21 +488,48 @@ ShowTrayIcon() ni.uCallbackMessage = WM_NOTIFYICONTRAY; ni.hIcon = LoadLocalizedSmallIcon(ID_ICO_DISCONNECTED); _tcsncpy(ni.szTip, _T(PACKAGE_NAME), _countof(_T(PACKAGE_NAME))); + ni.uVersion = NOTIFYICON_VERSION_4; Shell_NotifyIcon(NIM_ADD, &ni); + + /* try to set version 4 and a custom tooltip window */ + if (Shell_NotifyIcon(NIM_SETVERSION, &ni)) + { + /* create a custom tooltip for the tray */ + traytip = CreateWindowEx(0, TOOLTIPS_CLASS, NULL, WS_POPUP |TTS_ALWAYSTIP, + CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, + o.hWnd, NULL, o.hInstance, NULL); + if (!traytip) /* revert the version back so that we can use legacy ni.szTip for tip text */ + { + ni.uVersion = 0; + Shell_NotifyIcon(NIM_SETVERSION, &ni); + } + } + + if (traytip) + { + LONG cx = GetSystemMetrics(SM_CXSCREEN)/4; /* max width of tray tooltip = 25% of screen */ + ti.cbSize = sizeof(ti); + ti.uId = (UINT_PTR) traytip; + ti.uFlags = TTF_ABSOLUTE|TTF_TRACK|TTF_IDISHWND; + ti.hwnd = o.hWnd; + ti.lpszText = L""; + SendMessage(traytip, TTM_ADDTOOL, 0, (LPARAM)&ti); + SendMessage(traytip, TTM_SETTITLE, TTI_NONE, (LPARAM) _T(PACKAGE_NAME)); + SendMessage(traytip, TTM_SETMAXTIPWIDTH, 0, (LPARAM) cx); + } } void SetTrayIcon(conn_state_t state) { - TCHAR msg[500]; + TCHAR msg[500] = L""; TCHAR msg_connected[100]; TCHAR msg_connecting[100]; BOOL first_conn; UINT icon_id; connection_t *cc = NULL; /* a connected config */ - _tcsncpy(msg, _T(PACKAGE_NAME), _countof(_T(PACKAGE_NAME))); _tcsncpy(msg_connected, LoadLocalizedString(IDS_TIP_CONNECTED), _countof(msg_connected)); _tcsncpy(msg_connecting, LoadLocalizedString(IDS_TIP_CONNECTING), _countof(msg_connecting)); @@ -482,13 +562,23 @@ SetTrayIcon(conn_state_t state) { /* Append "Connected since and assigned IP" to message */ TCHAR time[50]; + WCHAR ip[64]; + + /* If there is not enough space in the message to add time and IP, truncate it. + * This works only if custom tooltip window is in use. + * Include about 50 characters for "Connected since:" and "Assigned IP:" prefixes. + */ + size_t max_msglen = _countof(msg) - (_countof(time) + _countof(ip) + 50); + if (_tcslen(msg) > max_msglen && traytip) + { + wcsncpy(&msg[max_msglen-1], L"…", 1); + } LocalizedTime(cc->connected_since, time, _countof(time)); _tcsncat(msg, LoadLocalizedString(IDS_TIP_CONNECTED_SINCE), _countof(msg) - _tcslen(msg) - 1); _tcsncat(msg, time, _countof(msg) - _tcslen(msg) - 1); /* concatenate ipv4 and ipv6 addresses into one string */ - WCHAR ip[64]; wcs_concat2(ip, _countof(ip), cc->ip, cc->ipv6, L", "); WCHAR *assigned_ip = LoadLocalizedString(IDS_TIP_ASSIGNED_IP, ip); _tcsncat(msg, assigned_ip, _countof(msg) - _tcslen(msg) - 1); @@ -508,9 +598,23 @@ SetTrayIcon(conn_state_t state) ni.uID = 0; ni.hWnd = o.hWnd; ni.hIcon = LoadLocalizedSmallIcon(icon_id); - ni.uFlags = NIF_MESSAGE | NIF_TIP | NIF_ICON; + ni.uFlags = NIF_MESSAGE | NIF_ICON; ni.uCallbackMessage = WM_NOTIFYICONTRAY; - _tcsncpy(ni.szTip, msg, _countof(ni.szTip)); + + if (traytip) + { + /* Set msg as the tool tip text -- skip the leading "\n" */ + WCHAR *msgp = msg; + msgp += (msgp[0] == L'\n') ? 1 : 0; + /* tool tip window needs a non-empty text to show */ + ti.lpszText = wcslen(msgp) ? msgp : L" "; + SendMessage(traytip, TTM_UPDATETIPTEXT, 0, (LPARAM) &ti); + } + else + { + _sntprintf_0(ni.szTip, L"%ls%ls", _T(PACKAGE_NAME), msg); + ni.uFlags |= NIF_TIP; + } Shell_NotifyIcon(NIM_MODIFY, &ni); } diff --git a/tray.h b/tray.h index 199d3c39..2cbfa66f 100644 --- a/tray.h +++ b/tray.h @@ -49,7 +49,7 @@ void RecreatePopupMenus(void); void CreatePopupMenus(); -void OnNotifyTray(LPARAM); +void OnNotifyTray(WPARAM, LPARAM); void OnDestroyTray(void);