1
0
forked from Yadciel/c_tray

Compare commits

3 Commits

Author SHA1 Message Date
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
3 changed files with 53 additions and 19 deletions

View File

@@ -53,21 +53,23 @@ 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;
}; };
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 +78,23 @@ 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.
* 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

@@ -10,6 +10,8 @@ static WNDCLASSEX wc;
static NOTIFYICONDATA nid; static NOTIFYICONDATA 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 +23,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);
@@ -106,6 +114,7 @@ int tray_init(struct tray *tray) {
nid.uCallbackMessage = WM_TRAY_CALLBACK_MESSAGE; nid.uCallbackMessage = WM_TRAY_CALLBACK_MESSAGE;
Shell_NotifyIcon(NIM_ADD, &nid); Shell_NotifyIcon(NIM_ADD, &nid);
g_tray = tray;
tray_update(tray); tray_update(tray);
return 0; return 0;
} }
@@ -126,18 +135,29 @@ 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) {
ExtractIconEx(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)); strncpy(nid.szTip, tray->tooltip, sizeof(nid.szTip) - 1);
nid.szTip[sizeof(nid.szTip) - 1] = '\0';
nid.uFlags |= NIF_TIP; nid.uFlags |= NIF_TIP;
} }
Shell_NotifyIcon(NIM_MODIFY, &nid); Shell_NotifyIcon(NIM_MODIFY, &nid);
@@ -149,7 +169,7 @@ void tray_update(struct tray *tray) {
void tray_exit(void) { void tray_exit(void) {
Shell_NotifyIcon(NIM_DELETE, &nid); Shell_NotifyIcon(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) {