1
0
forked from Yadciel/c_tray

7 Commits

Author SHA1 Message Date
fc77dda643 Migriere Windows Tray zu expliziten Wide-Character APIs
Diese Änderung stellt sicher, dass alle relevanten Windows API-Aufrufe und Datenstrukturen die Wide-Character (W-) Versionen verwenden. Dies gewährleistet eine konsistente und robuste Behandlung von Unicode-Zeichen, insbesondere nach der Einführung der UTF-8-Unterstützung für Menütexte und Tooltips.

Zusätzlich wird die erzwungene statische Verlinkung der C-Laufzeitbibliothek (`/MT`) entfernt, um die Kompatibilität zu verbessern und die Binärgröße zu reduzieren, indem die standardmäßige dynamische Verlinkung ermöglicht wird.
2026-05-08 15:29:24 +02:00
8df4e27360 Aktualisiere README, um aktuelle API und Funktionen zu reflektieren
Die README wurde aktualisiert, um die Projektidentität als eigenständige Weiterentwicklung zu verdeutlichen. Sie dokumentiert nun auch die jüngsten API-Erweiterungen wie Linksklick-Callbacks und verbesserte UTF-8-Unterstützung unter Windows, sowie relevante interne Felder.
2026-05-08 10:46:25 +02:00
951dfb930d Füge UTF-8-Unterstützung für Windows Tray hinzu
Konvertiert Menütexte und Tooltips von UTF-8 zu UTF-16, um die korrekte Anzeige von Nicht-ASCII-Zeichen (z.B. Umlaute) im System-Tray sicherzustellen.
2026-05-08 10:36:06 +02:00
a71be4e1e3 Merge pull request 'Sync README with current public API' (#2) from gary/c_tray:docs/readme-api-sync into master
Reviewed-on: Yadciel/c_tray#2
2026-05-06 11:43:11 +02:00
e9f0c9b8c9 Sync README with current public API 2026-05-06 11:43:00 +02:00
9a8694d755 Merge pull request 'Add support for left-click actions' (#1) from gary/c_tray:local into master
Reviewed-on: Yadciel/c_tray#1
2026-05-06 11:30:33 +02:00
fa1e862259 Fügt Unterstützung für Linksklick-Aktionen hinzu
Ermöglicht die Ausführung einer Callback-Funktion beim Linksklick auf das Tray-Icon. Dies bietet erweiterte Interaktionsmöglichkeiten mit der Anwendung über das Tray-Icon. Zusätzlich wird das Problem behoben, dass das Icon nicht freigegeben wird und somit nicht korrekt zerstört wird.
2026-02-02 21:15:52 +01:00
4 changed files with 115 additions and 61 deletions

View File

@@ -31,7 +31,6 @@ add_library(tray STATIC ${SRCS})
set_property(TARGET tray PROPERTY C_STANDARD 99) set_property(TARGET tray PROPERTY C_STANDARD 99)
if(WIN32) if(WIN32)
target_compile_definitions(tray PRIVATE TRAY_WINAPI=1 WIN32_LEAN_AND_MEAN NOMINMAX) target_compile_definitions(tray PRIVATE TRAY_WINAPI=1 WIN32_LEAN_AND_MEAN NOMINMAX)
target_compile_options(tray PRIVATE "/MT$<$<CONFIG:Debug>:d>")
else() else()
if(UNIX) if(UNIX)
if(APPLE) if(APPLE)

View File

@@ -16,13 +16,17 @@ Works well on:
The code is C++ friendly and will compile fine in C++98 and up. The code is C++ friendly and will compile fine in C++98 and up.
This fork is intended to bring together the [original work of Serge Zaitsev](https://github.com/zserge/tray) and the most interesting forks and PRs of respectable contributors: This repository started as a fork of the [original work by Serge Zaitsev](https://github.com/zserge/tray), but it is now maintained as a separate continuation. It keeps the small public C API and cross-platform focus, while carrying its own fixes and behavior changes.
* [Only process messages coming from the tray window on Windows](https://github.com/zserge/tray/pull/18) Compared to the original upstream, this fork already includes and continues work around:
* [Become C++-friendly](https://github.com/zserge/tray/pull/16)
* [Fix all menu items have a check box](https://github.com/zserge/tray/pull/11) * improved Windows message handling
* [Add support for tooltip](https://github.com/zserge/tray/pull/11) * C++-friendly headers and usage
* Darwin implementation translated from C to Objective C adapted from [@trevex fork](https://github.com/trevex/tray) * tooltip support
* checkbox/menu state fixes
* Darwin/AppKit support adapted from the [@trevex fork](https://github.com/trevex/tray)
* support for left-click callbacks
* Windows UTF-8 tray text handling
## Prerequisites ## Prerequisites
@@ -53,21 +57,27 @@ Execute the `tray_example` application:
## API ## API
Tray structure defines an icon and a menu. A tray is defined by an icon, an optional tooltip and a menu.
Menu is a NULL-terminated array of items. The menu is a NULL-terminated array of items.
Menu item defines menu text, menu checked and disabled (grayed) flags and a Each menu item can be disabled, checked, represented as a checkbox and invoke a
callback with some optional context pointer. callback with an optional context pointer.
```c ```c
struct tray { struct tray {
char *icon; const char *icon;
char *tooltip;
struct tray_menu *menu; struct tray_menu *menu;
void *icon_handle;
int icon_is_shared;
void (*left_click_cb)(void *context);
void *left_click_context;
}; };
struct tray_menu { struct tray_menu {
char *text; const char *text;
int disabled; int disabled;
int checked; int checked;
int checkbox;
void (*cb)(struct tray_menu *); void (*cb)(struct tray_menu *);
void *context; void *context;
@@ -76,15 +86,25 @@ struct tray_menu {
}; };
``` ```
* `int tray_init(struct tray *)` - creates tray icon. Returns -1 if tray icon/menu can't be created. * `int tray_init(struct tray *)` - creates the tray icon and its menu. Returns `-1` if the tray icon or menu cannot be created.
* `void tray_update(struct tray *)` - updates tray icon and menu. * `void tray_update(struct tray *)` - updates the tray icon, tooltip and menu state.
* `int tray_loop(int blocking)` - runs one iteration of the UI loop. Returns -1 if `tray_exit()` has been called. * `int tray_loop(int blocking)` - runs one iteration of the UI loop. Returns `-1` after `tray_exit()` has been called.
* `void tray_exit()` - terminates UI loop. * `void tray_exit(void)` - terminates the UI loop and cleans up tray resources.
All functions are meant to be called from the UI thread only. All functions are meant to be called from the UI thread only.
Menu arrays must be terminated with a NULL item, e.g. the last item in the Menu arrays must be terminated with a NULL item, i.e. the last item in the
array must have text field set to NULL. array must have `text == NULL`.
### Notes
* `tooltip` is optional.
* `left_click_cb` and `left_click_context` are optional.
* `icon_handle` and `icon_is_shared` are backend-specific fields used internally by the Windows implementation and should usually be left untouched by callers.
* A separator is created by using `text = "-"`.
* Some behavior may depend on the underlying backend or desktop environment.
Keep the public API generic and treat platform-specific interaction details as
backend-specific implementation behavior.
## License ## License

4
tray.h
View File

@@ -12,6 +12,10 @@ struct tray {
const char *icon; const char *icon;
char *tooltip; char *tooltip;
struct tray_menu *menu; struct tray_menu *menu;
void *icon_handle;
int icon_is_shared;
void (*left_click_cb)(void *context);
void *left_click_context;
}; };
struct tray_menu { struct tray_menu {

View File

@@ -1,15 +1,19 @@
#include <windows.h> #include <windows.h>
#include <shellapi.h> #include <shellapi.h>
#include <stdlib.h>
#include <string.h>
#include "tray.h" #include "tray.h"
#define WM_TRAY_CALLBACK_MESSAGE (WM_USER + 1) #define WM_TRAY_CALLBACK_MESSAGE (WM_USER + 1)
#define WC_TRAY_CLASS_NAME "TRAY" #define WC_TRAY_CLASS_NAME L"TRAY"
#define ID_TRAY_FIRST 1000 #define ID_TRAY_FIRST 1000
static WNDCLASSEX wc; static WNDCLASSEXW wc;
static NOTIFYICONDATA nid; static NOTIFYICONDATAW nid;
static HWND hwnd; static HWND hwnd;
static HMENU hmenu = NULL; static HMENU hmenu = NULL;
static struct tray *g_tray = NULL;
static int g_icon_is_shared = 0;
static LRESULT CALLBACK _tray_wnd_proc(HWND hwnd, UINT msg, WPARAM wparam, static LRESULT CALLBACK _tray_wnd_proc(HWND hwnd, UINT msg, WPARAM wparam,
LPARAM lparam) { LPARAM lparam) {
@@ -21,7 +25,13 @@ static LRESULT CALLBACK _tray_wnd_proc(HWND hwnd, UINT msg, WPARAM wparam,
PostQuitMessage(0); PostQuitMessage(0);
return 0; return 0;
case WM_TRAY_CALLBACK_MESSAGE: case WM_TRAY_CALLBACK_MESSAGE:
if (lparam == WM_LBUTTONUP || lparam == WM_RBUTTONUP) { if (lparam == WM_LBUTTONUP) {
if (g_tray && g_tray->left_click_cb) {
g_tray->left_click_cb(g_tray->left_click_context);
}
return 0;
}
if (lparam == WM_RBUTTONUP) {
POINT p; POINT p;
GetCursorPos(&p); GetCursorPos(&p);
SetForegroundWindow(hwnd); SetForegroundWindow(hwnd);
@@ -34,10 +44,10 @@ static LRESULT CALLBACK _tray_wnd_proc(HWND hwnd, UINT msg, WPARAM wparam,
break; break;
case WM_COMMAND: case WM_COMMAND:
if (wparam >= ID_TRAY_FIRST) { if (wparam >= ID_TRAY_FIRST) {
MENUITEMINFO item = { MENUITEMINFOW item = {
.cbSize = sizeof(MENUITEMINFO), .fMask = MIIM_ID | MIIM_DATA, .cbSize = sizeof(MENUITEMINFOW), .fMask = MIIM_ID | MIIM_DATA,
}; };
if (GetMenuItemInfo(hmenu, wparam, FALSE, &item)) { if (GetMenuItemInfoW(hmenu, wparam, FALSE, &item)) {
struct tray_menu *menu = (struct tray_menu *)item.dwItemData; struct tray_menu *menu = (struct tray_menu *)item.dwItemData;
if (menu != NULL && menu->cb != NULL) { if (menu != NULL && menu->cb != NULL) {
menu->cb(menu); menu->cb(menu);
@@ -47,36 +57,46 @@ static LRESULT CALLBACK _tray_wnd_proc(HWND hwnd, UINT msg, WPARAM wparam,
} }
break; break;
} }
return DefWindowProc(hwnd, msg, wparam, lparam); return DefWindowProcW(hwnd, msg, wparam, lparam);
} }
static HMENU _tray_menu(struct tray_menu *m, UINT *id) { static HMENU _tray_menu(struct tray_menu *m, UINT *id) {
HMENU hmenu = CreatePopupMenu(); HMENU hmenu = CreatePopupMenu();
for (; m != NULL && m->text != NULL; m++, (*id)++) { for (; m != NULL && m->text != NULL; m++, (*id)++) {
if (strcmp(m->text, "-") == 0) { if (strcmp(m->text, "-") == 0) {
InsertMenu(hmenu, *id, MF_SEPARATOR, TRUE, ""); InsertMenuW(hmenu, *id, MF_BYPOSITION | MF_SEPARATOR, *id, L"");
} else { } else {
MENUITEMINFO item; // UTF-8 zu UTF-16 (WCHAR) konvertieren
memset(&item, 0, sizeof(item)); int len = MultiByteToWideChar(CP_UTF8, 0, m->text, -1, NULL, 0);
item.cbSize = sizeof(MENUITEMINFO); if (len > 0) {
item.fMask = MIIM_ID | MIIM_TYPE | MIIM_STATE | MIIM_DATA; wchar_t *wtext = (wchar_t*)malloc(len * sizeof(wchar_t));
item.fType = 0; if (wtext) {
item.fState = 0; MultiByteToWideChar(CP_UTF8, 0, m->text, -1, wtext, len);
if (m->submenu != NULL) {
item.fMask = item.fMask | MIIM_SUBMENU;
item.hSubMenu = _tray_menu(m->submenu, id);
}
if (m->disabled) {
item.fState |= MFS_DISABLED;
}
if (m->checked) {
item.fState |= MFS_CHECKED;
}
item.wID = *id;
item.dwTypeData = (LPSTR)m->text;
item.dwItemData = (ULONG_PTR)m;
InsertMenuItem(hmenu, *id, TRUE, &item); MENUITEMINFOW item;
memset(&item, 0, sizeof(item));
item.cbSize = sizeof(MENUITEMINFOW);
item.fMask = MIIM_ID | MIIM_TYPE | MIIM_STATE | MIIM_DATA;
item.fType = 0;
item.fState = 0;
if (m->submenu != NULL) {
item.fMask = item.fMask | MIIM_SUBMENU;
item.hSubMenu = _tray_menu(m->submenu, id);
}
if (m->disabled) {
item.fState |= MFS_DISABLED;
}
if (m->checked) {
item.fState |= MFS_CHECKED;
}
item.wID = *id;
item.dwTypeData = wtext;
item.dwItemData = (ULONG_PTR)m;
InsertMenuItemW(hmenu, *id, TRUE, &item);
free(wtext);
}
}
} }
} }
return hmenu; return hmenu;
@@ -84,15 +104,15 @@ static HMENU _tray_menu(struct tray_menu *m, UINT *id) {
int tray_init(struct tray *tray) { int tray_init(struct tray *tray) {
memset(&wc, 0, sizeof(wc)); memset(&wc, 0, sizeof(wc));
wc.cbSize = sizeof(WNDCLASSEX); wc.cbSize = sizeof(WNDCLASSEXW);
wc.lpfnWndProc = _tray_wnd_proc; wc.lpfnWndProc = _tray_wnd_proc;
wc.hInstance = GetModuleHandle(NULL); wc.hInstance = GetModuleHandle(NULL);
wc.lpszClassName = WC_TRAY_CLASS_NAME; wc.lpszClassName = WC_TRAY_CLASS_NAME;
if (!RegisterClassEx(&wc)) { if (!RegisterClassExW(&wc)) {
return -1; return -1;
} }
hwnd = CreateWindowEx(0, WC_TRAY_CLASS_NAME, NULL, 0, 0, 0, 0, 0, 0, 0, 0, 0); hwnd = CreateWindowExW(0, WC_TRAY_CLASS_NAME, NULL, 0, 0, 0, 0, 0, 0, 0, 0, 0);
if (hwnd == NULL) { if (hwnd == NULL) {
return -1; return -1;
} }
@@ -104,8 +124,9 @@ int tray_init(struct tray *tray) {
nid.uID = 0; nid.uID = 0;
nid.uFlags = NIF_ICON | NIF_MESSAGE; nid.uFlags = NIF_ICON | NIF_MESSAGE;
nid.uCallbackMessage = WM_TRAY_CALLBACK_MESSAGE; nid.uCallbackMessage = WM_TRAY_CALLBACK_MESSAGE;
Shell_NotifyIcon(NIM_ADD, &nid); Shell_NotifyIconW(NIM_ADD, &nid);
g_tray = tray;
tray_update(tray); tray_update(tray);
return 0; return 0;
} }
@@ -126,21 +147,31 @@ int tray_loop(int blocking) {
} }
void tray_update(struct tray *tray) { void tray_update(struct tray *tray) {
g_tray = tray;
HMENU prevmenu = hmenu; HMENU prevmenu = hmenu;
UINT id = ID_TRAY_FIRST; UINT id = ID_TRAY_FIRST;
hmenu = _tray_menu(tray->menu, &id); hmenu = _tray_menu(tray->menu, &id);
SendMessage(hwnd, WM_INITMENUPOPUP, (WPARAM)hmenu, 0); SendMessage(hwnd, WM_INITMENUPOPUP, (WPARAM)hmenu, 0);
HICON icon; HICON icon = NULL;
ExtractIconEx(tray->icon, 0, NULL, &icon, 1); int icon_is_shared = 0;
if (nid.hIcon) { if (tray->icon_handle) {
icon = (HICON)tray->icon_handle;
icon_is_shared = tray->icon_is_shared;
} else if (tray->icon) {
ExtractIconExA(tray->icon, 0, NULL, &icon, 1);
}
if (nid.hIcon && !g_icon_is_shared) {
DestroyIcon(nid.hIcon); DestroyIcon(nid.hIcon);
} }
nid.hIcon = icon; if (icon) {
nid.hIcon = icon;
g_icon_is_shared = icon_is_shared;
}
if(tray->tooltip != 0 && strlen(tray->tooltip) > 0) { if(tray->tooltip != 0 && strlen(tray->tooltip) > 0) {
strncpy(nid.szTip, tray->tooltip, sizeof(nid.szTip)); MultiByteToWideChar(CP_UTF8, 0, tray->tooltip, -1, nid.szTip, sizeof(nid.szTip)/sizeof(wchar_t));
nid.uFlags |= NIF_TIP; nid.uFlags |= NIF_TIP;
} }
Shell_NotifyIcon(NIM_MODIFY, &nid); Shell_NotifyIconW(NIM_MODIFY, &nid);
if (prevmenu != NULL) { if (prevmenu != NULL) {
DestroyMenu(prevmenu); DestroyMenu(prevmenu);
@@ -148,14 +179,14 @@ void tray_update(struct tray *tray) {
} }
void tray_exit(void) { void tray_exit(void) {
Shell_NotifyIcon(NIM_DELETE, &nid); Shell_NotifyIconW(NIM_DELETE, &nid);
if (nid.hIcon != 0) { if (nid.hIcon != 0 && !g_icon_is_shared) {
DestroyIcon(nid.hIcon); DestroyIcon(nid.hIcon);
} }
if (hmenu != 0) { if (hmenu != 0) {
DestroyMenu(hmenu); DestroyMenu(hmenu);
} }
PostQuitMessage(0); PostQuitMessage(0);
UnregisterClass(WC_TRAY_CLASS_NAME, GetModuleHandle(NULL)); UnregisterClassW(WC_TRAY_CLASS_NAME, GetModuleHandle(NULL));
} }