| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573 |
- /*
- Simple DirectMedia Layer
- Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
- This software is provided 'as-is', without any express or implied
- warranty. In no event will the authors be held liable for any damages
- arising from the use of this software.
- Permission is granted to anyone to use this software for any purpose,
- including commercial applications, and to alter it and redistribute it
- freely, subject to the following restrictions:
- 1. The origin of this software must not be misrepresented; you must not
- claim that you wrote the original software. If you use this software
- in a product, an acknowledgment in the product documentation would be
- appreciated but is not required.
- 2. Altered source versions must be plainly marked as such, and must not be
- misrepresented as being the original software.
- 3. This notice may not be removed or altered from any source distribution.
- */
- #include "SDL_internal.h"
- #include "../SDL_tray_utils.h"
- #include "../../core/windows/SDL_windows.h"
- #include <windowsx.h>
- #include <shellapi.h>
- #include "../../video/windows/SDL_surface_utils.h"
- #ifndef NOTIFYICON_VERSION_4
- #define NOTIFYICON_VERSION_4 4
- #endif
- #ifndef NIF_SHOWTIP
- #define NIF_SHOWTIP 0x00000080
- #endif
- #define WM_TRAYICON (WM_USER + 1)
- struct SDL_TrayMenu {
- HMENU hMenu;
- size_t nEntries;
- SDL_TrayEntry **entries;
- SDL_Tray *parent_tray;
- SDL_TrayEntry *parent_entry;
- };
- struct SDL_TrayEntry {
- SDL_TrayMenu *parent;
- UINT_PTR id;
- char label_cache[4096];
- SDL_TrayEntryFlags flags;
- SDL_TrayCallback callback;
- void *userdata;
- SDL_TrayMenu *submenu;
- };
- struct SDL_Tray {
- NOTIFYICONDATAW nid;
- HWND hwnd;
- HICON icon;
- SDL_TrayMenu *menu;
- };
- static UINT_PTR get_next_id(void)
- {
- static UINT_PTR next_id = 0;
- return ++next_id;
- }
- static SDL_TrayEntry *find_entry_in_menu(SDL_TrayMenu *menu, UINT_PTR id)
- {
- for (size_t i = 0; i < menu->nEntries; i++) {
- SDL_TrayEntry *entry = menu->entries[i];
- if (entry->id == id) {
- return entry;
- }
- if (entry->submenu) {
- SDL_TrayEntry *e = find_entry_in_menu(entry->submenu, id);
- if (e) {
- return e;
- }
- }
- }
- return NULL;
- }
- static SDL_TrayEntry *find_entry_with_id(SDL_Tray *tray, UINT_PTR id)
- {
- if (!tray->menu) {
- return NULL;
- }
- return find_entry_in_menu(tray->menu, id);
- }
- LRESULT CALLBACK TrayWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
- SDL_Tray *tray = (SDL_Tray *) GetWindowLongPtr(hwnd, GWLP_USERDATA);
- SDL_TrayEntry *entry = NULL;
- if (!tray) {
- return DefWindowProc(hwnd, uMsg, wParam, lParam);
- }
- switch (uMsg) {
- case WM_TRAYICON:
- if (LOWORD(lParam) == WM_CONTEXTMENU || LOWORD(lParam) == WM_LBUTTONUP) {
- SetForegroundWindow(hwnd);
- TrackPopupMenu(tray->menu->hMenu, TPM_BOTTOMALIGN | TPM_RIGHTALIGN, GET_X_LPARAM(wParam), GET_Y_LPARAM(wParam), 0, hwnd, NULL);
- }
- break;
- case WM_COMMAND:
- entry = find_entry_with_id(tray, LOWORD(wParam));
- if (entry && (entry->flags & SDL_TRAYENTRY_CHECKBOX)) {
- SDL_SetTrayEntryChecked(entry, !SDL_GetTrayEntryChecked(entry));
- }
- if (entry && entry->callback) {
- entry->callback(entry->userdata, entry);
- }
- break;
- default:
- return DefWindowProc(hwnd, uMsg, wParam, lParam);
- }
- return 0;
- }
- static void DestroySDLMenu(SDL_TrayMenu *menu)
- {
- for (size_t i = 0; i < menu->nEntries; i++) {
- if (menu->entries[i] && menu->entries[i]->submenu) {
- DestroySDLMenu(menu->entries[i]->submenu);
- }
- SDL_free(menu->entries[i]);
- }
- SDL_free(menu->entries);
- DestroyMenu(menu->hMenu);
- SDL_free(menu);
- }
- static wchar_t *escape_label(const char *in)
- {
- const char *c;
- char *c2;
- int len = 0;
- for (c = in; *c; c++) {
- len += (*c == '&') ? 2 : 1;
- }
- char *escaped = SDL_malloc(SDL_strlen(in) + len + 1);
- if (!escaped) {
- return NULL;
- }
- for (c = in, c2 = escaped; *c;) {
- if (*c == '&') {
- *c2++ = *c;
- }
- *c2++ = *c++;
- }
- *c2 = '\0';
- wchar_t *out = WIN_UTF8ToStringW(escaped);
- SDL_free(escaped);
- return out;
- }
- SDL_Tray *SDL_CreateTray(SDL_Surface *icon, const char *tooltip)
- {
- SDL_Tray *tray = SDL_malloc(sizeof(SDL_Tray));
- if (!tray) {
- return NULL;
- }
- tray->menu = NULL;
- tray->hwnd = CreateWindowEx(0, TEXT("Message"), NULL, 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, NULL);
- SetWindowLongPtr(tray->hwnd, GWLP_WNDPROC, (LONG_PTR) TrayWindowProc);
- SDL_zero(tray->nid);
- tray->nid.cbSize = sizeof(NOTIFYICONDATAW);
- tray->nid.hWnd = tray->hwnd;
- tray->nid.uID = (UINT) get_next_id();
- tray->nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP | NIF_SHOWTIP;
- tray->nid.uCallbackMessage = WM_TRAYICON;
- tray->nid.uVersion = NOTIFYICON_VERSION_4;
- wchar_t *tooltipw = WIN_UTF8ToStringW(tooltip);
- SDL_wcslcpy(tray->nid.szTip, tooltipw, sizeof(tray->nid.szTip) / sizeof(*tray->nid.szTip));
- SDL_free(tooltipw);
- if (icon) {
- tray->nid.hIcon = CreateIconFromSurface(icon);
- if (!tray->nid.hIcon) {
- tray->nid.hIcon = LoadIcon(NULL, IDI_APPLICATION);
- }
- tray->icon = tray->nid.hIcon;
- } else {
- tray->nid.hIcon = LoadIcon(NULL, IDI_APPLICATION);
- tray->icon = tray->nid.hIcon;
- }
- Shell_NotifyIconW(NIM_ADD, &tray->nid);
- Shell_NotifyIconW(NIM_SETVERSION, &tray->nid);
- SetWindowLongPtr(tray->hwnd, GWLP_USERDATA, (LONG_PTR) tray);
- SDL_IncrementTrayCount();
- return tray;
- }
- void SDL_SetTrayIcon(SDL_Tray *tray, SDL_Surface *icon)
- {
- if (tray->icon) {
- DestroyIcon(tray->icon);
- }
- if (icon) {
- tray->nid.hIcon = CreateIconFromSurface(icon);
- if (!tray->nid.hIcon) {
- tray->nid.hIcon = LoadIcon(NULL, IDI_APPLICATION);
- }
- tray->icon = tray->nid.hIcon;
- } else {
- tray->nid.hIcon = LoadIcon(NULL, IDI_APPLICATION);
- tray->icon = tray->nid.hIcon;
- }
- Shell_NotifyIconW(NIM_MODIFY, &tray->nid);
- }
- void SDL_SetTrayTooltip(SDL_Tray *tray, const char *tooltip)
- {
- if (tooltip) {
- wchar_t *tooltipw = WIN_UTF8ToStringW(tooltip);
- SDL_wcslcpy(tray->nid.szTip, tooltipw, sizeof(tray->nid.szTip) / sizeof(*tray->nid.szTip));
- SDL_free(tooltipw);
- } else {
- tray->nid.szTip[0] = '\0';
- }
- Shell_NotifyIconW(NIM_MODIFY, &tray->nid);
- }
- SDL_TrayMenu *SDL_CreateTrayMenu(SDL_Tray *tray)
- {
- tray->menu = SDL_malloc(sizeof(SDL_TrayMenu));
- if (!tray->menu) {
- return NULL;
- }
- SDL_memset((void *) tray->menu, 0, sizeof(*tray->menu));
- tray->menu->hMenu = CreatePopupMenu();
- tray->menu->parent_tray = tray;
- return tray->menu;
- }
- SDL_TrayMenu *SDL_GetTrayMenu(SDL_Tray *tray)
- {
- return tray->menu;
- }
- SDL_TrayMenu *SDL_CreateTraySubmenu(SDL_TrayEntry *entry)
- {
- if (!entry->submenu) {
- SDL_SetError("Cannot create submenu for entry not created with SDL_TRAYENTRY_SUBMENU");
- }
- return entry->submenu;
- }
- SDL_TrayMenu *SDL_GetTraySubmenu(SDL_TrayEntry *entry)
- {
- return entry->submenu;
- }
- const SDL_TrayEntry **SDL_GetTrayEntries(SDL_TrayMenu *menu, int *size)
- {
- if (size) {
- *size = (int) menu->nEntries;
- }
- return (const SDL_TrayEntry **) menu->entries;
- }
- void SDL_RemoveTrayEntry(SDL_TrayEntry *entry)
- {
- if (!entry) {
- return;
- }
- SDL_TrayMenu *menu = entry->parent;
- bool found = false;
- for (size_t i = 0; i < menu->nEntries - 1; i++) {
- if (menu->entries[i] == entry) {
- found = true;
- }
- if (found) {
- menu->entries[i] = menu->entries[i + 1];
- }
- }
- if (entry->submenu) {
- DestroySDLMenu(entry->submenu);
- }
- menu->nEntries--;
- SDL_TrayEntry ** new_entries = SDL_realloc(menu->entries, (menu->nEntries + 1) * sizeof(SDL_TrayEntry *));
- /* Not sure why shrinking would fail, but even if it does, we can live with a "too big" array */
- if (new_entries) {
- menu->entries = new_entries;
- menu->entries[menu->nEntries] = NULL;
- }
- if (!DeleteMenu(menu->hMenu, (UINT) entry->id, MF_BYCOMMAND)) {
- /* This is somewhat useless since we don't return anything, but might help with eventual bugs */
- SDL_SetError("Couldn't destroy tray entry");
- }
- SDL_free(entry);
- }
- SDL_TrayEntry *SDL_InsertTrayEntryAt(SDL_TrayMenu *menu, int pos, const char *label, SDL_TrayEntryFlags flags)
- {
- if (pos < -1 || pos > (int) menu->nEntries) {
- SDL_InvalidParamError("pos");
- return NULL;
- }
- int windows_compatible_pos = pos;
- if (pos == -1) {
- pos = (int) menu->nEntries;
- } else if (pos == menu->nEntries) {
- windows_compatible_pos = -1;
- }
- SDL_TrayEntry *entry = SDL_malloc(sizeof(SDL_TrayEntry));
- if (!entry) {
- return NULL;
- }
- wchar_t *label_w = NULL;
- if (label && !(label_w = escape_label(label))) {
- SDL_free(entry);
- return NULL;
- }
- entry->parent = menu;
- entry->flags = flags;
- entry->callback = NULL;
- entry->userdata = NULL;
- entry->submenu = NULL;
- SDL_snprintf(entry->label_cache, sizeof(entry->label_cache), "%s", label ? label : "");
- if (label != NULL && flags & SDL_TRAYENTRY_SUBMENU) {
- entry->submenu = SDL_malloc(sizeof(SDL_TrayMenu));
- if (!entry->submenu) {
- SDL_free(entry);
- SDL_free(label_w);
- return NULL;
- }
- entry->submenu->hMenu = CreatePopupMenu();
- entry->submenu->nEntries = 0;
- entry->submenu->entries = NULL;
- entry->id = (UINT_PTR) entry->submenu->hMenu;
- } else {
- entry->id = get_next_id();
- }
- SDL_TrayEntry **new_entries = (SDL_TrayEntry **) SDL_realloc(menu->entries, (menu->nEntries + 2) * sizeof(SDL_TrayEntry **));
- if (!new_entries) {
- SDL_free(entry);
- SDL_free(label_w);
- if (entry->submenu) {
- DestroyMenu(entry->submenu->hMenu);
- SDL_free(entry->submenu);
- }
- return NULL;
- }
- menu->entries = new_entries;
- menu->nEntries++;
- for (int i = (int) menu->nEntries - 1; i > pos; i--) {
- menu->entries[i] = menu->entries[i - 1];
- }
- new_entries[pos] = entry;
- new_entries[menu->nEntries] = NULL;
- if (label == NULL) {
- InsertMenuW(menu->hMenu, windows_compatible_pos, MF_SEPARATOR | MF_BYPOSITION, entry->id, NULL);
- } else {
- UINT mf = MF_STRING | MF_BYPOSITION;
- if (flags & SDL_TRAYENTRY_SUBMENU) {
- mf = MF_POPUP;
- }
- if (flags & SDL_TRAYENTRY_DISABLED) {
- mf |= MF_DISABLED | MF_GRAYED;
- }
- if (flags & SDL_TRAYENTRY_CHECKED) {
- mf |= MF_CHECKED;
- }
- InsertMenuW(menu->hMenu, windows_compatible_pos, mf, entry->id, label_w);
- SDL_free(label_w);
- }
- return entry;
- }
- void SDL_SetTrayEntryLabel(SDL_TrayEntry *entry, const char *label)
- {
- SDL_snprintf(entry->label_cache, sizeof(entry->label_cache), "%s", label);
- wchar_t *label_w = escape_label(label);
- if (!label_w) {
- return;
- }
- MENUITEMINFOW mii;
- mii.cbSize = sizeof(MENUITEMINFOW);
- mii.fMask = MIIM_STRING;
- mii.dwTypeData = label_w;
- mii.cch = (UINT) SDL_wcslen(label_w);
- if (!SetMenuItemInfoW(entry->parent->hMenu, (UINT) entry->id, TRUE, &mii)) {
- SDL_SetError("Couldn't update tray entry label");
- }
- SDL_free(label_w);
- }
- const char *SDL_GetTrayEntryLabel(SDL_TrayEntry *entry)
- {
- return entry->label_cache;
- }
- void SDL_SetTrayEntryChecked(SDL_TrayEntry *entry, bool checked)
- {
- if (!(entry->flags & SDL_TRAYENTRY_CHECKBOX)) {
- SDL_SetError("Can't check/uncheck tray entry not created with SDL_TRAYENTRY_CHECKBOX");
- return;
- }
- CheckMenuItem(entry->parent->hMenu, (UINT) entry->id, checked ? MF_CHECKED : MF_UNCHECKED);
- }
- bool SDL_GetTrayEntryChecked(SDL_TrayEntry *entry)
- {
- if (!(entry->flags & SDL_TRAYENTRY_CHECKBOX)) {
- SDL_SetError("Can't get check status of tray entry not created with SDL_TRAYENTRY_CHECKBOX");
- return false;
- }
- MENUITEMINFOW mii;
- mii.cbSize = sizeof(MENUITEMINFOW);
- mii.fMask = MIIM_STATE;
- GetMenuItemInfoW(entry->parent->hMenu, (UINT) entry->id, FALSE, &mii);
- return !!(mii.fState & MFS_CHECKED);
- }
- void SDL_SetTrayEntryEnabled(SDL_TrayEntry *entry, bool enabled)
- {
- if (!(entry->flags & SDL_TRAYENTRY_CHECKBOX)) {
- SDL_SetError("Cannot update check for entry not created with SDL_TRAYENTRY_CHECKBOX");
- return;
- }
- EnableMenuItem(entry->parent->hMenu, (UINT) entry->id, MF_BYCOMMAND | (enabled ? MF_ENABLED : (MF_DISABLED | MF_GRAYED)));
- }
- bool SDL_GetTrayEntryEnabled(SDL_TrayEntry *entry)
- {
- if (!(entry->flags & SDL_TRAYENTRY_CHECKBOX)) {
- SDL_SetError("Cannot fetch check for entry not created with SDL_TRAYENTRY_CHECKBOX");
- return false;
- }
- MENUITEMINFOW mii;
- mii.cbSize = sizeof(MENUITEMINFOW);
- mii.fMask = MIIM_STATE;
- GetMenuItemInfoW(entry->parent->hMenu, (UINT) entry->id, FALSE, &mii);
- return !!(mii.fState & MFS_ENABLED);
- }
- void SDL_SetTrayEntryCallback(SDL_TrayEntry *entry, SDL_TrayCallback callback, void *userdata)
- {
- entry->callback = callback;
- entry->userdata = userdata;
- }
- SDL_TrayMenu *SDL_GetTrayEntryParent(SDL_TrayEntry *entry)
- {
- return entry->parent;
- }
- SDL_TrayEntry *SDL_GetTrayMenuParentEntry(SDL_TrayMenu *menu)
- {
- return menu->parent_entry;
- }
- SDL_Tray *SDL_GetTrayMenuParentTray(SDL_TrayMenu *menu)
- {
- return menu->parent_tray;
- }
- void SDL_DestroyTray(SDL_Tray *tray)
- {
- if (!tray) {
- return;
- }
- Shell_NotifyIconW(NIM_DELETE, &tray->nid);
- if (tray->menu) {
- DestroySDLMenu(tray->menu);
- }
- if (tray->icon) {
- DestroyIcon(tray->icon);
- }
- if (tray->hwnd) {
- DestroyWindow(tray->hwnd);
- }
- SDL_free(tray);
- SDL_DecrementTrayCount();
- }
|