diff options
Diffstat (limited to 'dwl-patches/patches/bar-systray/bar-systray-0.7.patch')
-rw-r--r-- | dwl-patches/patches/bar-systray/bar-systray-0.7.patch | 3023 |
1 files changed, 3023 insertions, 0 deletions
diff --git a/dwl-patches/patches/bar-systray/bar-systray-0.7.patch b/dwl-patches/patches/bar-systray/bar-systray-0.7.patch new file mode 100644 index 0000000..1dda4c6 --- /dev/null +++ b/dwl-patches/patches/bar-systray/bar-systray-0.7.patch @@ -0,0 +1,3023 @@ +From cf228147250f4616d150fbe5276088c5f9969bba Mon Sep 17 00:00:00 2001 +From: vetu104 <vetu104@proton.me> +Date: Sat, 29 Mar 2025 19:22:37 +0200 +Subject: [PATCH] Add a system tray next to sewn's bar + +--- + Makefile | 23 +- + config.def.h | 5 + + dbus.c | 242 +++++++++++++++ + dbus.h | 10 + + dwl.c | 107 ++++++- + systray/helpers.c | 43 +++ + systray/helpers.h | 12 + + systray/icon.c | 149 +++++++++ + systray/icon.h | 26 ++ + systray/item.c | 403 ++++++++++++++++++++++++ + systray/item.h | 46 +++ + systray/menu.c | 757 ++++++++++++++++++++++++++++++++++++++++++++++ + systray/menu.h | 11 + + systray/tray.c | 237 +++++++++++++++ + systray/tray.h | 37 +++ + systray/watcher.c | 551 +++++++++++++++++++++++++++++++++ + systray/watcher.h | 35 +++ + 17 files changed, 2681 insertions(+), 13 deletions(-) + create mode 100644 dbus.c + create mode 100644 dbus.h + create mode 100644 systray/helpers.c + create mode 100644 systray/helpers.h + create mode 100644 systray/icon.c + create mode 100644 systray/icon.h + create mode 100644 systray/item.c + create mode 100644 systray/item.h + create mode 100644 systray/menu.c + create mode 100644 systray/menu.h + create mode 100644 systray/tray.c + create mode 100644 systray/tray.h + create mode 100644 systray/watcher.c + create mode 100644 systray/watcher.h + +diff --git a/Makefile b/Makefile +index 9bc67db..9d50189 100644 +--- a/Makefile ++++ b/Makefile +@@ -12,17 +12,28 @@ DWLDEVCFLAGS = -g -pedantic -Wall -Wextra -Wdeclaration-after-statement \ + -Wfloat-conversion + + # CFLAGS / LDFLAGS +-PKGS = wlroots-0.18 wayland-server xkbcommon libinput pixman-1 fcft $(XLIBS) ++PKGS = wlroots-0.18 wayland-server xkbcommon libinput pixman-1 fcft $(XLIBS) dbus-1 + DWLCFLAGS = `$(PKG_CONFIG) --cflags $(PKGS)` $(DWLCPPFLAGS) $(DWLDEVCFLAGS) $(CFLAGS) + LDLIBS = `$(PKG_CONFIG) --libs $(PKGS)` -lm $(LIBS) + ++TRAYOBJS = systray/watcher.o systray/tray.o systray/item.o systray/icon.o systray/menu.o systray/helpers.o ++TRAYDEPS = systray/watcher.h systray/tray.h systray/item.h systray/icon.h systray/menu.h systray/helpers.h ++ + all: dwl +-dwl: dwl.o util.o +- $(CC) dwl.o util.o $(DWLCFLAGS) $(LDFLAGS) $(LDLIBS) -o $@ +-dwl.o: dwl.c client.h config.h config.mk cursor-shape-v1-protocol.h \ ++dwl: dwl.o util.o dbus.o $(TRAYOBJS) $(TRAYDEPS) ++ $(CC) dwl.o util.o dbus.o $(TRAYOBJS) $(DWLCFLAGS) $(LDFLAGS) $(LDLIBS) -o $@ ++dwl.o: dwl.c client.h dbus.h config.h config.mk cursor-shape-v1-protocol.h \ + pointer-constraints-unstable-v1-protocol.h wlr-layer-shell-unstable-v1-protocol.h \ +- wlr-output-power-management-unstable-v1-protocol.h xdg-shell-protocol.h ++ wlr-output-power-management-unstable-v1-protocol.h xdg-shell-protocol.h \ ++ $(TRAYDEPS) + util.o: util.c util.h ++dbus.o: dbus.c dbus.h ++systray/watcher.o: systray/watcher.c $(TRAYDEPS) ++systray/tray.o: systray/tray.c $(TRAYDEPS) ++systray/item.o: systray/item.c $(TRAYDEPS) ++systray/icon.o: systray/icon.c $(TRAYDEPS) ++systray/menu.o: systray/menu.c $(TRAYDEPS) ++systray/helpers.o: systray/helpers.c $(TRAYDEPS) + + # wayland-scanner is a tool which generates C headers and rigging for Wayland + # protocols, which are specified in XML. wlroots requires you to rig these up +@@ -49,7 +60,7 @@ xdg-shell-protocol.h: + config.h: + cp config.def.h $@ + clean: +- rm -f dwl *.o *-protocol.h ++ rm -f dwl *.o *-protocol.h systray/*.o + + dist: clean + mkdir -p dwl-$(VERSION) +diff --git a/config.def.h b/config.def.h +index 5d1dc2b..451643e 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -7,6 +7,8 @@ + static const int sloppyfocus = 1; /* focus follows mouse */ + static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */ + static const unsigned int borderpx = 1; /* border pixel of windows */ ++static const unsigned int systrayspacing = 2; /* systray spacing */ ++static const int showsystray = 1; /* 0 means no systray */ + static const int showbar = 1; /* 0 means no bar */ + static const int topbar = 1; /* 0 means bottom bar */ + static const char *fonts[] = {"monospace:size=10"}; +@@ -127,6 +129,7 @@ static const enum libinput_config_tap_button_map button_map = LIBINPUT_CONFIG_TA + /* commands */ + static const char *termcmd[] = { "foot", NULL }; + static const char *menucmd[] = { "wmenu-run", NULL }; ++static const char *dmenucmd[] = { "wmenu", NULL }; + + static const Key keys[] = { + /* Note that Shift changes certain key codes: c -> C, 2 -> at, etc. */ +@@ -188,4 +191,6 @@ static const Button buttons[] = { + { ClkTagBar, 0, BTN_RIGHT, toggleview, {0} }, + { ClkTagBar, MODKEY, BTN_LEFT, tag, {0} }, + { ClkTagBar, MODKEY, BTN_RIGHT, toggletag, {0} }, ++ { ClkTray, 0, BTN_LEFT, trayactivate, {0} }, ++ { ClkTray, 0, BTN_RIGHT, traymenu, {0} }, + }; +diff --git a/dbus.c b/dbus.c +new file mode 100644 +index 0000000..125312c +--- /dev/null ++++ b/dbus.c +@@ -0,0 +1,242 @@ ++#include "dbus.h" ++ ++#include "util.h" ++ ++#include <dbus/dbus.h> ++#include <stdlib.h> ++#include <wayland-server-core.h> ++ ++#include <fcntl.h> ++#include <stddef.h> ++#include <stdint.h> ++#include <stdio.h> ++#include <unistd.h> ++ ++static void ++close_pipe(void *data) ++{ ++ int *pipefd = data; ++ ++ close(pipefd[0]); ++ close(pipefd[1]); ++ free(pipefd); ++} ++ ++static int ++dwl_dbus_dispatch(int fd, unsigned int mask, void *data) ++{ ++ DBusConnection *conn = data; ++ ++ int pending; ++ DBusDispatchStatus oldstatus, newstatus; ++ ++ oldstatus = dbus_connection_get_dispatch_status(conn); ++ newstatus = dbus_connection_dispatch(conn); ++ ++ /* Don't clear pending flag if status didn't change */ ++ if (oldstatus == newstatus) ++ return 0; ++ ++ if (read(fd, &pending, sizeof(int)) < 0) { ++ perror("read"); ++ die("Error in dbus dispatch"); ++ } ++ ++ return 0; ++} ++ ++static int ++dwl_dbus_watch_handle(int fd, uint32_t mask, void *data) ++{ ++ DBusWatch *watch = data; ++ ++ uint32_t flags = 0; ++ ++ if (!dbus_watch_get_enabled(watch)) ++ return 0; ++ ++ if (mask & WL_EVENT_READABLE) ++ flags |= DBUS_WATCH_READABLE; ++ if (mask & WL_EVENT_WRITABLE) ++ flags |= DBUS_WATCH_WRITABLE; ++ if (mask & WL_EVENT_HANGUP) ++ flags |= DBUS_WATCH_HANGUP; ++ if (mask & WL_EVENT_ERROR) ++ flags |= DBUS_WATCH_ERROR; ++ ++ dbus_watch_handle(watch, flags); ++ ++ return 0; ++} ++ ++static dbus_bool_t ++dwl_dbus_add_watch(DBusWatch *watch, void *data) ++{ ++ struct wl_event_loop *loop = data; ++ ++ int fd; ++ struct wl_event_source *watch_source; ++ uint32_t mask = 0, flags; ++ ++ if (!dbus_watch_get_enabled(watch)) ++ return TRUE; ++ ++ flags = dbus_watch_get_flags(watch); ++ if (flags & DBUS_WATCH_READABLE) ++ mask |= WL_EVENT_READABLE; ++ if (flags & DBUS_WATCH_WRITABLE) ++ mask |= WL_EVENT_WRITABLE; ++ ++ fd = dbus_watch_get_unix_fd(watch); ++ watch_source = wl_event_loop_add_fd(loop, fd, mask, ++ dwl_dbus_watch_handle, watch); ++ ++ dbus_watch_set_data(watch, watch_source, NULL); ++ ++ return TRUE; ++} ++ ++static void ++dwl_dbus_remove_watch(DBusWatch *watch, void *data) ++{ ++ struct wl_event_source *watch_source = dbus_watch_get_data(watch); ++ ++ if (watch_source) ++ wl_event_source_remove(watch_source); ++} ++ ++static int ++dwl_dbus_timeout_handle(void *data) ++{ ++ DBusTimeout *timeout = data; ++ ++ if (dbus_timeout_get_enabled(timeout)) ++ dbus_timeout_handle(timeout); ++ ++ return 0; ++} ++ ++static dbus_bool_t ++dwl_dbus_add_timeout(DBusTimeout *timeout, void *data) ++{ ++ struct wl_event_loop *loop = data; ++ ++ int r, interval; ++ struct wl_event_source *timeout_source; ++ ++ if (!dbus_timeout_get_enabled(timeout)) ++ return TRUE; ++ ++ interval = dbus_timeout_get_interval(timeout); ++ ++ timeout_source = ++ wl_event_loop_add_timer(loop, dwl_dbus_timeout_handle, timeout); ++ ++ r = wl_event_source_timer_update(timeout_source, interval); ++ if (r < 0) { ++ wl_event_source_remove(timeout_source); ++ return FALSE; ++ } ++ ++ dbus_timeout_set_data(timeout, timeout_source, NULL); ++ ++ return TRUE; ++} ++ ++static void ++dwl_dbus_remove_timeout(DBusTimeout *timeout, void *data) ++{ ++ struct wl_event_source *timeout_source; ++ ++ timeout_source = dbus_timeout_get_data(timeout); ++ ++ if (timeout_source) { ++ wl_event_source_timer_update(timeout_source, 0); ++ wl_event_source_remove(timeout_source); ++ } ++} ++ ++static void ++dwl_dbus_dispatch_status(DBusConnection *conn, DBusDispatchStatus status, ++ void *data) ++{ ++ int *pipefd = data; ++ ++ if (status != DBUS_DISPATCH_COMPLETE) { ++ int pending = 1; ++ if (write(pipefd[1], &pending, sizeof(int)) < 0) { ++ perror("write"); ++ die("Error in dispatch status"); ++ } ++ } ++} ++ ++struct wl_event_source * ++startbus(DBusConnection *conn, struct wl_event_loop *loop) ++{ ++ int *pipefd; ++ int pending = 1, flags; ++ struct wl_event_source *bus_source = NULL; ++ ++ pipefd = ecalloc(2, sizeof(int)); ++ ++ /* ++ * Libdbus forbids calling dbus_connection_dipatch from the ++ * DBusDispatchStatusFunction directly. Notify the event loop of ++ * updates via a self-pipe. ++ */ ++ if (pipe(pipefd) < 0) ++ goto fail; ++ if (((flags = fcntl(pipefd[0], F_GETFD)) < 0) || ++ fcntl(pipefd[0], F_SETFD, flags | FD_CLOEXEC) < 0 || ++ ((flags = fcntl(pipefd[1], F_GETFD)) < 0) || ++ fcntl(pipefd[1], F_SETFD, flags | FD_CLOEXEC) < 0) { ++ goto fail; ++ } ++ ++ dbus_connection_set_exit_on_disconnect(conn, FALSE); ++ ++ bus_source = wl_event_loop_add_fd(loop, pipefd[0], WL_EVENT_READABLE, ++ dwl_dbus_dispatch, conn); ++ if (!bus_source) ++ goto fail; ++ ++ dbus_connection_set_dispatch_status_function(conn, ++ dwl_dbus_dispatch_status, ++ pipefd, close_pipe); ++ if (!dbus_connection_set_watch_functions(conn, dwl_dbus_add_watch, ++ dwl_dbus_remove_watch, NULL, ++ loop, NULL)) { ++ goto fail; ++ } ++ if (!dbus_connection_set_timeout_functions(conn, dwl_dbus_add_timeout, ++ dwl_dbus_remove_timeout, ++ NULL, loop, NULL)) { ++ goto fail; ++ } ++ if (dbus_connection_get_dispatch_status(conn) != DBUS_DISPATCH_COMPLETE) ++ if (write(pipefd[1], &pending, sizeof(int)) < 0) ++ goto fail; ++ ++ return bus_source; ++ ++fail: ++ if (bus_source) ++ wl_event_source_remove(bus_source); ++ dbus_connection_set_timeout_functions(conn, NULL, NULL, NULL, NULL, ++ NULL); ++ dbus_connection_set_watch_functions(conn, NULL, NULL, NULL, NULL, NULL); ++ dbus_connection_set_dispatch_status_function(conn, NULL, NULL, NULL); ++ ++ return NULL; ++} ++ ++void ++stopbus(DBusConnection *conn, struct wl_event_source *bus_source) ++{ ++ wl_event_source_remove(bus_source); ++ dbus_connection_set_watch_functions(conn, NULL, NULL, NULL, NULL, NULL); ++ dbus_connection_set_timeout_functions(conn, NULL, NULL, NULL, NULL, ++ NULL); ++ dbus_connection_set_dispatch_status_function(conn, NULL, NULL, NULL); ++} +diff --git a/dbus.h b/dbus.h +new file mode 100644 +index 0000000..b374b98 +--- /dev/null ++++ b/dbus.h +@@ -0,0 +1,10 @@ ++#ifndef DWLDBUS_H ++#define DWLDBUS_H ++ ++#include <dbus/dbus.h> ++#include <wayland-server-core.h> ++ ++struct wl_event_source* startbus (DBusConnection *conn, struct wl_event_loop *loop); ++void stopbus (DBusConnection *conn, struct wl_event_source *bus_source); ++ ++#endif /* DWLDBUS_H */ +diff --git a/dwl.c b/dwl.c +index ece537a..7753ef6 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -1,6 +1,7 @@ + /* + * See LICENSE file for copyright and license details. + */ ++#include <dbus/dbus.h> + #include <getopt.h> + #include <libinput.h> + #include <linux/input-event-codes.h> +@@ -71,6 +72,9 @@ + + #include "util.h" + #include "drwl.h" ++#include "dbus.h" ++#include "systray/tray.h" ++#include "systray/watcher.h" + + /* macros */ + #define MAX(A, B) ((A) > (B) ? (A) : (B)) +@@ -89,7 +93,7 @@ enum { SchemeNorm, SchemeSel, SchemeUrg }; /* color schemes */ + enum { CurNormal, CurPressed, CurMove, CurResize }; /* cursor */ + enum { XDGShell, LayerShell, X11 }; /* client types */ + enum { LyrBg, LyrBottom, LyrTile, LyrFloat, LyrTop, LyrFS, LyrOverlay, LyrBlock, NUM_LAYERS }; /* scene layers */ +-enum { ClkTagBar, ClkLtSymbol, ClkStatus, ClkTitle, ClkClient, ClkRoot }; /* clicks */ ++enum { ClkTagBar, ClkLtSymbol, ClkStatus, ClkTitle, ClkClient, ClkRoot, ClkTray }; /* clicks */ + #ifdef XWAYLAND + enum { NetWMWindowTypeDialog, NetWMWindowTypeSplash, NetWMWindowTypeToolbar, + NetWMWindowTypeUtility, NetLast }; /* EWMH atoms */ +@@ -218,6 +222,7 @@ struct Monitor { + int real_width, real_height; /* non-scaled */ + float scale; + } b; /* bar area */ ++ Tray *tray; + struct wlr_box w; /* window area, layout-relative */ + struct wl_list layers[4]; /* LayerSurface.link */ + const Layout *lt[2]; +@@ -376,6 +381,9 @@ static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); + static void toggletag(const Arg *arg); + static void toggleview(const Arg *arg); ++static void trayactivate(const Arg *arg); ++static void traymenu(const Arg *arg); ++static void traynotify(void *data); + static void unlocksession(struct wl_listener *listener, void *data); + static void unmaplayersurfacenotify(struct wl_listener *listener, void *data); + static void unmapnotify(struct wl_listener *listener, void *data); +@@ -451,6 +459,10 @@ static Monitor *selmon; + static char stext[256]; + static struct wl_event_source *status_event_source; + ++static DBusConnection *bus_conn; ++static struct wl_event_source *bus_source; ++static Watcher watcher = {.running = 0}; ++ + static const struct wlr_buffer_impl buffer_impl = { + .destroy = bufdestroy, + .begin_data_ptr_access = bufdatabegin, +@@ -721,8 +733,8 @@ bufrelease(struct wl_listener *listener, void *data) + void + buttonpress(struct wl_listener *listener, void *data) + { +- unsigned int i = 0, x = 0; +- double cx; ++ unsigned int i = 0, x = 0, ti = 0; ++ double cx, tx = 0; + unsigned int click; + struct wlr_pointer_button_event *event = data; + struct wlr_keyboard *keyboard; +@@ -732,6 +744,7 @@ buttonpress(struct wl_listener *listener, void *data) + Arg arg = {0}; + Client *c; + const Button *b; ++ int traywidth; + + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); + +@@ -751,6 +764,8 @@ buttonpress(struct wl_listener *listener, void *data) + (node = wlr_scene_node_at(&layers[LyrBottom]->node, cursor->x, cursor->y, NULL, NULL)) && + (buffer = wlr_scene_buffer_from_node(node)) && buffer == selmon->scene_buffer) { + cx = (cursor->x - selmon->m.x) * selmon->wlr_output->scale; ++ traywidth = tray_get_width(selmon->tray); ++ + do + x += TEXTW(selmon, tags[i]); + while (cx >= x && ++i < LENGTH(tags)); +@@ -759,8 +774,16 @@ buttonpress(struct wl_listener *listener, void *data) + arg.ui = 1 << i; + } else if (cx < x + TEXTW(selmon, selmon->ltsymbol)) + click = ClkLtSymbol; +- else if (cx > selmon->b.width - (TEXTW(selmon, stext) - selmon->lrpad + 2)) { ++ else if (cx > selmon->b.width - (TEXTW(selmon, stext) - selmon->lrpad + 2) && cx < selmon->b.width - traywidth) { + click = ClkStatus; ++ } else if (cx > selmon->b.width - (TEXTW(selmon, stext) - selmon->lrpad + 2)) { ++ unsigned int tray_n_items = watcher_get_n_items(&watcher); ++ tx = selmon->b.width - traywidth; ++ do ++ tx += tray_n_items ? (int)(traywidth / tray_n_items) : 0; ++ while (cx >= tx && ++ti < tray_n_items); ++ click = ClkTray; ++ arg.ui = ti; + } else + click = ClkTitle; + } +@@ -774,7 +797,12 @@ buttonpress(struct wl_listener *listener, void *data) + mods = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0; + for (b = buttons; b < END(buttons); b++) { + if (CLEANMASK(mods) == CLEANMASK(b->mod) && event->button == b->button && click == b->click && b->func) { +- b->func(click == ClkTagBar && b->arg.i == 0 ? &arg : &b->arg); ++ if (click == ClkTagBar && b->arg.i == 0) ++ b->func(&arg); ++ else if (click == ClkTray && b->arg.i == 0) ++ b->func(&arg); ++ else ++ b->func(&b->arg); + return; + } + } +@@ -840,6 +868,14 @@ cleanup(void) + + destroykeyboardgroup(&kb_group->destroy, NULL); + ++ if (watcher.running) ++ watcher_stop(&watcher); ++ ++ if (showbar && showsystray) { ++ stopbus(bus_conn, bus_source); ++ dbus_connection_unref(bus_conn); ++ } ++ + /* If it's not destroyed manually it will cause a use-after-free of wlr_seat. + * Destroy it until it's fixed in the wlroots side */ + wlr_backend_destroy(backend); +@@ -868,6 +904,9 @@ cleanupmon(struct wl_listener *listener, void *data) + for (i = 0; i < LENGTH(m->pool); i++) + wlr_buffer_drop(&m->pool[i]->base); + ++ if (showsystray) ++ destroytray(m->tray); ++ + drwl_setimage(m->drw, NULL); + drwl_destroy(m->drw); + +@@ -1506,6 +1545,7 @@ dirtomon(enum wlr_direction dir) + void + drawbar(Monitor *m) + { ++ int traywidth = 0; + int x, w, tw = 0; + int boxs = m->drw->font->height / 9; + int boxw = m->drw->font->height / 6 + 2; +@@ -1518,11 +1558,13 @@ drawbar(Monitor *m) + if (!(buf = bufmon(m))) + return; + ++ traywidth = tray_get_width(m->tray); ++ + /* draw status first so it can be overdrawn by tags later */ + if (m == selmon) { /* status is only drawn on selected monitor */ + drwl_setscheme(m->drw, colors[SchemeNorm]); + tw = TEXTW(m, stext) - m->lrpad + 2; /* 2px right padding */ +- drwl_text(m->drw, m->b.width - tw, 0, tw, m->b.height, 0, stext, 0); ++ drwl_text(m->drw, m->b.width - (tw + traywidth), 0, tw, m->b.height, 0, stext, 0); + } + + wl_list_for_each(c, &clients, link) { +@@ -1548,7 +1590,7 @@ drawbar(Monitor *m) + drwl_setscheme(m->drw, colors[SchemeNorm]); + x = drwl_text(m->drw, x, 0, w, m->b.height, m->lrpad / 2, m->ltsymbol, 0); + +- if ((w = m->b.width - tw - x) > m->b.height) { ++ if ((w = m->b.width - (tw + x + traywidth)) > m->b.height) { + if (c) { + drwl_setscheme(m->drw, colors[m == selmon ? SchemeSel : SchemeNorm]); + drwl_text(m->drw, x, 0, w, m->b.height, m->lrpad / 2, client_get_title(c), 0); +@@ -1560,6 +1602,15 @@ drawbar(Monitor *m) + } + } + ++ if (traywidth > 0) { ++ pixman_image_composite32(PIXMAN_OP_SRC, ++ m->tray->image, NULL, m->drw->image, ++ 0, 0, ++ 0, 0, ++ m->b.width - traywidth, 0, ++ traywidth, m->b.height); ++ } ++ + wlr_scene_buffer_set_dest_size(m->scene_buffer, + m->b.real_width, m->b.real_height); + wlr_scene_node_set_position(&m->scene_buffer->node, m->m.x, +@@ -1568,6 +1619,26 @@ drawbar(Monitor *m) + wlr_buffer_unlock(&buf->base); + } + ++void ++traynotify(void *data) ++{ ++ Monitor *m = data; ++ ++ drawbar(m); ++} ++ ++void ++trayactivate(const Arg *arg) ++{ ++ tray_leftclicked(selmon->tray, arg->ui); ++} ++ ++void ++traymenu(const Arg *arg) ++{ ++ tray_rightclicked(selmon->tray, arg->ui, dmenucmd); ++} ++ + void + drawbars(void) + { +@@ -2818,6 +2889,15 @@ setup(void) + status_event_source = wl_event_loop_add_fd(wl_display_get_event_loop(dpy), + STDIN_FILENO, WL_EVENT_READABLE, statusin, NULL); + ++ bus_conn = dbus_bus_get(DBUS_BUS_SESSION, NULL); ++ if (!bus_conn) ++ die("Failed to connect to bus"); ++ bus_source = startbus(bus_conn, event_loop); ++ if (!bus_source) ++ die("Failed to start listening to bus events"); ++ if (showbar && showsystray) ++ watcher_start(&watcher, bus_conn, event_loop); ++ + /* Make sure XWayland clients don't connect to the parent X server, + * e.g when running in the x11 backend or the wayland backend and the + * compositor has Xwayland support */ +@@ -3160,6 +3240,7 @@ updatebar(Monitor *m) + size_t i; + int rw, rh; + char fontattrs[12]; ++ Tray *tray; + + wlr_output_transformed_resolution(m->wlr_output, &rw, &rh); + m->b.width = rw; +@@ -3185,6 +3266,18 @@ updatebar(Monitor *m) + m->lrpad = m->drw->font->height; + m->b.height = m->drw->font->height + 2; + m->b.real_height = (int)((float)m->b.height / m->wlr_output->scale); ++ ++ if (showsystray) { ++ if (m->tray) ++ destroytray(m->tray); ++ tray = createtray(m, ++ m->b.height, systrayspacing, colors[SchemeNorm], fonts, fontattrs, ++ &traynotify, &watcher); ++ if (!tray) ++ die("Couldn't create tray for monitor"); ++ m->tray = tray; ++ wl_list_insert(&watcher.trays, &tray->link); ++ } + } + + void +diff --git a/systray/helpers.c b/systray/helpers.c +new file mode 100644 +index 0000000..d1af9f8 +--- /dev/null ++++ b/systray/helpers.c +@@ -0,0 +1,43 @@ ++#include "helpers.h" ++ ++#include <dbus/dbus.h> ++ ++#include <errno.h> ++#include <stddef.h> ++ ++// IWYU pragma: no_include "dbus/dbus-protocol.h" ++// IWYU pragma: no_include "dbus/dbus-shared.h" ++ ++int ++request_property(DBusConnection *conn, const char *busname, const char *busobj, ++ const char *prop, const char *iface, PropHandler handler, ++ void *data) ++{ ++ DBusMessage *msg = NULL; ++ DBusPendingCall *pending = NULL; ++ int r; ++ ++ if (!(msg = dbus_message_new_method_call(busname, busobj, ++ DBUS_INTERFACE_PROPERTIES, ++ "Get")) || ++ !dbus_message_append_args(msg, DBUS_TYPE_STRING, &iface, ++ DBUS_TYPE_STRING, &prop, ++ DBUS_TYPE_INVALID) || ++ !dbus_connection_send_with_reply(conn, msg, &pending, -1) || ++ !dbus_pending_call_set_notify(pending, handler, data, NULL)) { ++ r = -ENOMEM; ++ goto fail; ++ } ++ ++ dbus_message_unref(msg); ++ return 0; ++ ++fail: ++ if (pending) { ++ dbus_pending_call_cancel(pending); ++ dbus_pending_call_unref(pending); ++ } ++ if (msg) ++ dbus_message_unref(msg); ++ return r; ++} +diff --git a/systray/helpers.h b/systray/helpers.h +new file mode 100644 +index 0000000..2c592e0 +--- /dev/null ++++ b/systray/helpers.h +@@ -0,0 +1,12 @@ ++#ifndef HELPERS_H ++#define HELPERS_H ++ ++#include <dbus/dbus.h> ++ ++typedef void (*PropHandler)(DBusPendingCall *pcall, void *data); ++ ++int request_property (DBusConnection *conn, const char *busname, ++ const char *busobj, const char *prop, const char *iface, ++ PropHandler handler, void *data); ++ ++#endif /* HELPERS_H */ +diff --git a/systray/icon.c b/systray/icon.c +new file mode 100644 +index 0000000..1b97866 +--- /dev/null ++++ b/systray/icon.c +@@ -0,0 +1,149 @@ ++#include "icon.h" ++ ++#include <fcft/fcft.h> ++#include <pixman.h> ++ ++#include <ctype.h> ++#include <stdint.h> ++#include <stdlib.h> ++#include <string.h> ++ ++#define PREMUL_ALPHA(chan, alpha) (chan * alpha + 127) / 255 ++ ++/* ++ * Converts pixels from uint8_t[4] to uint32_t and ++ * straight alpha to premultiplied alpha. ++ */ ++static uint32_t * ++to_pixman(const uint8_t *src, int n_pixels, size_t *pix_size) ++{ ++ uint32_t *dest = NULL; ++ ++ *pix_size = n_pixels * sizeof(uint32_t); ++ dest = malloc(*pix_size); ++ if (!dest) ++ return NULL; ++ ++ for (int i = 0; i < n_pixels; i++) { ++ uint8_t a = src[i * 4 + 0]; ++ uint8_t r = src[i * 4 + 1]; ++ uint8_t g = src[i * 4 + 2]; ++ uint8_t b = src[i * 4 + 3]; ++ ++ /* ++ * Skip premultiplying fully opaque and fully transparent ++ * pixels. ++ */ ++ if (a == 0) { ++ dest[i] = 0; ++ ++ } else if (a == 255) { ++ dest[i] = ((uint32_t)a << 24) | ((uint32_t)r << 16) | ++ ((uint32_t)g << 8) | ((uint32_t)b); ++ ++ } else { ++ dest[i] = ((uint32_t)a << 24) | ++ ((uint32_t)PREMUL_ALPHA(r, a) << 16) | ++ ((uint32_t)PREMUL_ALPHA(g, a) << 8) | ++ ((uint32_t)PREMUL_ALPHA(b, a)); ++ } ++ } ++ ++ return dest; ++} ++ ++Icon * ++createicon(const uint8_t *buf, int width, int height, int size) ++{ ++ Icon *icon = NULL; ++ ++ int n_pixels; ++ pixman_image_t *img = NULL; ++ size_t pixbuf_size; ++ uint32_t *buf_pixman = NULL; ++ uint8_t *buf_orig = NULL; ++ ++ n_pixels = size / 4; ++ ++ icon = calloc(1, sizeof(Icon)); ++ buf_orig = malloc(size); ++ buf_pixman = to_pixman(buf, n_pixels, &pixbuf_size); ++ if (!icon || !buf_orig || !buf_pixman) ++ goto fail; ++ ++ img = pixman_image_create_bits(PIXMAN_a8r8g8b8, width, height, ++ buf_pixman, width * 4); ++ if (!img) ++ goto fail; ++ ++ memcpy(buf_orig, buf, size); ++ ++ icon->buf_orig = buf_orig; ++ icon->buf_pixman = buf_pixman; ++ icon->img = img; ++ icon->size_orig = size; ++ icon->size_pixman = pixbuf_size; ++ ++ return icon; ++ ++fail: ++ free(buf_orig); ++ if (img) ++ pixman_image_unref(img); ++ free(buf_pixman); ++ free(icon); ++ return NULL; ++} ++ ++void ++destroyicon(Icon *icon) ++{ ++ if (icon->img) ++ pixman_image_unref(icon->img); ++ free(icon->buf_orig); ++ free(icon->buf_pixman); ++ free(icon); ++} ++ ++FallbackIcon * ++createfallbackicon(const char *appname, int fgcolor, struct fcft_font *font) ++{ ++ const struct fcft_glyph *glyph; ++ char initial; ++ ++ if ((unsigned char)appname[0] > 127) { ++ /* first character is not ascii */ ++ initial = '?'; ++ } else { ++ initial = toupper(*appname); ++ } ++ ++ glyph = fcft_rasterize_char_utf32(font, initial, FCFT_SUBPIXEL_DEFAULT); ++ if (!glyph) ++ return NULL; ++ ++ return glyph; ++} ++ ++int ++resize_image(pixman_image_t *image, int new_width, int new_height) ++{ ++ int src_width = pixman_image_get_width(image); ++ int src_height = pixman_image_get_height(image); ++ pixman_transform_t transform; ++ pixman_fixed_t scale_x, scale_y; ++ ++ if (src_width == new_width && src_height == new_height) ++ return 0; ++ ++ scale_x = pixman_double_to_fixed((double)src_width / new_width); ++ scale_y = pixman_double_to_fixed((double)src_height / new_height); ++ ++ pixman_transform_init_scale(&transform, scale_x, scale_y); ++ if (!pixman_image_set_filter(image, PIXMAN_FILTER_BEST, NULL, 0) || ++ !pixman_image_set_transform(image, &transform)) { ++ return -1; ++ } ++ ++ return 0; ++} +diff --git a/systray/icon.h b/systray/icon.h +new file mode 100644 +index 0000000..20f281b +--- /dev/null ++++ b/systray/icon.h +@@ -0,0 +1,26 @@ ++#ifndef ICON_H ++#define ICON_H ++ ++#include <fcft/fcft.h> ++#include <pixman.h> ++ ++#include <stddef.h> ++#include <stdint.h> ++ ++typedef const struct fcft_glyph FallbackIcon; ++ ++typedef struct { ++ pixman_image_t *img; ++ uint32_t *buf_pixman; ++ uint8_t *buf_orig; ++ size_t size_orig; ++ size_t size_pixman; ++} Icon; ++ ++Icon *createicon (const uint8_t *buf, int width, int height, int size); ++FallbackIcon *createfallbackicon (const char *appname, int fgcolor, ++ struct fcft_font *font); ++void destroyicon (Icon *icon); ++int resize_image (pixman_image_t *orig, int new_width, int new_height); ++ ++#endif /* ICON_H */ +diff --git a/systray/item.c b/systray/item.c +new file mode 100644 +index 0000000..4359a28 +--- /dev/null ++++ b/systray/item.c +@@ -0,0 +1,403 @@ ++#include "item.h" ++ ++#include "helpers.h" ++#include "icon.h" ++#include "watcher.h" ++ ++#include <dbus/dbus.h> ++ ++#include <stdint.h> ++#include <stdio.h> ++#include <stdlib.h> ++#include <string.h> ++ ++// IWYU pragma: no_include "dbus/dbus-protocol.h" ++// IWYU pragma: no_include "dbus/dbus-shared.h" ++ ++#define RULEBSIZE 256 ++#define MIN(A, B) ((A) < (B) ? (A) : (B)) ++ ++static const char *match_string = ++ "type='signal'," ++ "sender='%s'," ++ "interface='" SNI_NAME ++ "'," ++ "member='NewIcon'"; ++ ++static Watcher * ++item_get_watcher(const Item *item) ++{ ++ if (!item) ++ return NULL; ++ ++ return item->watcher; ++} ++ ++static DBusConnection * ++item_get_connection(const Item *item) ++{ ++ if (!item || !item->watcher) ++ return NULL; ++ ++ return item->watcher->conn; ++} ++ ++static const uint8_t * ++extract_image(DBusMessageIter *iter, dbus_int32_t *width, dbus_int32_t *height, ++ int *size) ++{ ++ DBusMessageIter vals, bytes; ++ const uint8_t *buf; ++ ++ dbus_message_iter_recurse(iter, &vals); ++ if (dbus_message_iter_get_arg_type(&vals) != DBUS_TYPE_INT32) ++ goto fail; ++ dbus_message_iter_get_basic(&vals, width); ++ ++ dbus_message_iter_next(&vals); ++ if (dbus_message_iter_get_arg_type(&vals) != DBUS_TYPE_INT32) ++ goto fail; ++ dbus_message_iter_get_basic(&vals, height); ++ ++ dbus_message_iter_next(&vals); ++ if (dbus_message_iter_get_arg_type(&vals) != DBUS_TYPE_ARRAY) ++ goto fail; ++ dbus_message_iter_recurse(&vals, &bytes); ++ if (dbus_message_iter_get_arg_type(&bytes) != DBUS_TYPE_BYTE) ++ goto fail; ++ dbus_message_iter_get_fixed_array(&bytes, &buf, size); ++ if (size == 0) ++ goto fail; ++ ++ return buf; ++ ++fail: ++ return NULL; ++} ++ ++static int ++select_image(DBusMessageIter *iter, int target_width) ++{ ++ DBusMessageIter vals; ++ dbus_int32_t cur_width; ++ int i = 0; ++ ++ do { ++ dbus_message_iter_recurse(iter, &vals); ++ if (dbus_message_iter_get_arg_type(&vals) != DBUS_TYPE_INT32) ++ return -1; ++ dbus_message_iter_get_basic(&vals, &cur_width); ++ if (cur_width >= target_width) ++ return i; ++ ++ i++; ++ } while (dbus_message_iter_next(iter)); ++ ++ /* return last index if desired not found */ ++ return --i; ++} ++ ++static void ++menupath_ready_handler(DBusPendingCall *pending, void *data) ++{ ++ Item *item = data; ++ ++ DBusError err = DBUS_ERROR_INIT; ++ DBusMessage *reply = NULL; ++ DBusMessageIter iter, opath; ++ char *path_dup = NULL; ++ const char *path; ++ ++ reply = dbus_pending_call_steal_reply(pending); ++ if (!reply) ++ goto fail; ++ ++ if (dbus_set_error_from_message(&err, reply)) { ++ fprintf(stderr, "DBus Error: %s - %s: Couldn't get menupath\n", ++ err.name, err.message); ++ goto fail; ++ } ++ ++ dbus_message_iter_init(reply, &iter); ++ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) ++ goto fail; ++ dbus_message_iter_recurse(&iter, &opath); ++ if (dbus_message_iter_get_arg_type(&opath) != DBUS_TYPE_OBJECT_PATH) ++ goto fail; ++ dbus_message_iter_get_basic(&opath, &path); ++ ++ path_dup = strdup(path); ++ if (!path_dup) ++ goto fail; ++ ++ item->menu_busobj = path_dup; ++ ++ dbus_message_unref(reply); ++ dbus_pending_call_unref(pending); ++ return; ++ ++fail: ++ free(path_dup); ++ dbus_error_free(&err); ++ if (reply) ++ dbus_message_unref(reply); ++ if (pending) ++ dbus_pending_call_unref(pending); ++} ++ ++/* ++ * Gets the Id dbus property, which is the name of the application, ++ * most of the time... ++ * The initial letter will be used as a fallback icon ++ */ ++static void ++id_ready_handler(DBusPendingCall *pending, void *data) ++{ ++ Item *item = data; ++ ++ DBusError err = DBUS_ERROR_INIT; ++ DBusMessage *reply = NULL; ++ DBusMessageIter iter, string; ++ Watcher *watcher; ++ char *id_dup = NULL; ++ const char *id; ++ ++ watcher = item_get_watcher(item); ++ ++ reply = dbus_pending_call_steal_reply(pending); ++ if (!reply) ++ goto fail; ++ ++ if (dbus_set_error_from_message(&err, reply)) { ++ fprintf(stderr, "DBus Error: %s - %s: Couldn't get appid\n", ++ err.name, err.message); ++ goto fail; ++ } ++ ++ dbus_message_iter_init(reply, &iter); ++ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) ++ goto fail; ++ dbus_message_iter_recurse(&iter, &string); ++ if (dbus_message_iter_get_arg_type(&string) != DBUS_TYPE_STRING) ++ goto fail; ++ dbus_message_iter_get_basic(&string, &id); ++ ++ id_dup = strdup(id); ++ if (!id_dup) ++ goto fail; ++ item->appid = id_dup; ++ ++ /* Don't trigger update if this item already has a real icon */ ++ if (!item->icon) ++ watcher_update_trays(watcher); ++ ++ dbus_message_unref(reply); ++ dbus_pending_call_unref(pending); ++ return; ++ ++fail: ++ dbus_error_free(&err); ++ if (id_dup) ++ free(id_dup); ++ if (reply) ++ dbus_message_unref(reply); ++ if (pending) ++ dbus_pending_call_unref(pending); ++} ++ ++static void ++pixmap_ready_handler(DBusPendingCall *pending, void *data) ++{ ++ Item *item = data; ++ ++ DBusMessage *reply = NULL; ++ DBusMessageIter iter, array, select, strct; ++ Icon *icon = NULL; ++ Watcher *watcher; ++ dbus_int32_t width, height; ++ int selected_index, size; ++ const uint8_t *buf; ++ ++ watcher = item_get_watcher(item); ++ ++ reply = dbus_pending_call_steal_reply(pending); ++ if (!reply || dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR) ++ goto fail; ++ dbus_message_iter_init(reply, &iter); ++ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) ++ goto fail; ++ dbus_message_iter_recurse(&iter, &array); ++ if (dbus_message_iter_get_arg_type(&array) != DBUS_TYPE_ARRAY) ++ goto fail; ++ dbus_message_iter_recurse(&array, &select); ++ if (dbus_message_iter_get_arg_type(&select) != DBUS_TYPE_STRUCT) ++ goto fail; ++ selected_index = select_image(&select, 22); // Get the 22*22 image ++ if (selected_index < 0) ++ goto fail; ++ ++ dbus_message_iter_recurse(&array, &strct); ++ if (dbus_message_iter_get_arg_type(&strct) != DBUS_TYPE_STRUCT) ++ goto fail; ++ for (int i = 0; i < selected_index; i++) ++ dbus_message_iter_next(&strct); ++ buf = extract_image(&strct, &width, &height, &size); ++ if (!buf) ++ goto fail; ++ ++ if (!item->icon) { ++ /* First icon */ ++ icon = createicon(buf, width, height, size); ++ if (!icon) ++ goto fail; ++ item->icon = icon; ++ watcher_update_trays(watcher); ++ ++ } else if (memcmp(item->icon->buf_orig, buf, ++ MIN(item->icon->size_orig, (size_t)size)) != 0) { ++ /* New icon */ ++ destroyicon(item->icon); ++ item->icon = NULL; ++ icon = createicon(buf, width, height, size); ++ if (!icon) ++ goto fail; ++ item->icon = icon; ++ watcher_update_trays(watcher); ++ ++ } else { ++ /* Icon didn't change */ ++ } ++ ++ dbus_message_unref(reply); ++ dbus_pending_call_unref(pending); ++ return; ++ ++fail: ++ if (icon) ++ destroyicon(icon); ++ if (reply) ++ dbus_message_unref(reply); ++ if (pending) ++ dbus_pending_call_unref(pending); ++} ++ ++static DBusHandlerResult ++handle_newicon(Item *item, DBusConnection *conn, DBusMessage *msg) ++{ ++ const char *sender = dbus_message_get_sender(msg); ++ ++ if (sender && strcmp(sender, item->busname) == 0) { ++ request_property(conn, item->busname, item->busobj, ++ "IconPixmap", SNI_IFACE, pixmap_ready_handler, ++ item); ++ ++ return DBUS_HANDLER_RESULT_HANDLED; ++ ++ } else { ++ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; ++ } ++} ++ ++static DBusHandlerResult ++filter_bus(DBusConnection *conn, DBusMessage *msg, void *data) ++{ ++ Item *item = data; ++ ++ if (dbus_message_is_signal(msg, SNI_IFACE, "NewIcon")) ++ return handle_newicon(item, conn, msg); ++ else ++ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; ++} ++ ++Item * ++createitem(const char *busname, const char *busobj, Watcher *watcher) ++{ ++ DBusConnection *conn; ++ Item *item; ++ char *busname_dup = NULL; ++ char *busobj_dup = NULL; ++ char match_rule[RULEBSIZE]; ++ ++ item = calloc(1, sizeof(Item)); ++ busname_dup = strdup(busname); ++ busobj_dup = strdup(busobj); ++ if (!item || !busname_dup || !busobj_dup) ++ goto fail; ++ ++ conn = watcher->conn; ++ item->busname = busname_dup; ++ item->busobj = busobj_dup; ++ item->watcher = watcher; ++ ++ request_property(conn, busname, busobj, "IconPixmap", SNI_IFACE, ++ pixmap_ready_handler, item); ++ ++ request_property(conn, busname, busobj, "Id", SNI_IFACE, ++ id_ready_handler, item); ++ ++ request_property(conn, busname, busobj, "Menu", SNI_IFACE, ++ menupath_ready_handler, item); ++ ++ if (snprintf(match_rule, sizeof(match_rule), match_string, busname) >= ++ RULEBSIZE) { ++ goto fail; ++ } ++ ++ if (!dbus_connection_add_filter(conn, filter_bus, item, NULL)) ++ goto fail; ++ dbus_bus_add_match(conn, match_rule, NULL); ++ ++ return item; ++ ++fail: ++ free(busname_dup); ++ free(busobj_dup); ++ return NULL; ++} ++ ++void ++destroyitem(Item *item) ++{ ++ DBusConnection *conn; ++ char match_rule[RULEBSIZE]; ++ ++ conn = item_get_connection(item); ++ ++ if (snprintf(match_rule, sizeof(match_rule), match_string, ++ item->busname) < RULEBSIZE) { ++ dbus_bus_remove_match(conn, match_rule, NULL); ++ dbus_connection_remove_filter(conn, filter_bus, item); ++ } ++ if (item->icon) ++ destroyicon(item->icon); ++ free(item->menu_busobj); ++ free(item->busname); ++ free(item->busobj); ++ free(item->appid); ++ free(item); ++} ++ ++void ++item_activate(Item *item) ++{ ++ DBusConnection *conn; ++ DBusMessage *msg = NULL; ++ dbus_int32_t x = 0, y = 0; ++ ++ conn = item_get_connection(item); ++ ++ if (!(msg = dbus_message_new_method_call(item->busname, item->busobj, ++ SNI_IFACE, "Activate")) || ++ !dbus_message_append_args(msg, DBUS_TYPE_INT32, &x, DBUS_TYPE_INT32, ++ &y, DBUS_TYPE_INVALID) || ++ !dbus_connection_send_with_reply(conn, msg, NULL, -1)) { ++ goto fail; ++ } ++ ++ dbus_message_unref(msg); ++ return; ++ ++fail: ++ if (msg) ++ dbus_message_unref(msg); ++} +diff --git a/systray/item.h b/systray/item.h +new file mode 100644 +index 0000000..dc22e25 +--- /dev/null ++++ b/systray/item.h +@@ -0,0 +1,46 @@ ++#ifndef ITEM_H ++#define ITEM_H ++ ++#include "icon.h" ++#include "watcher.h" ++ ++#include <wayland-util.h> ++ ++/* ++ * The FDO spec says "org.freedesktop.StatusNotifierItem"[1], ++ * but both the client libraries[2,3] actually use "org.kde.StatusNotifierItem" ++ * ++ * [1] https://www.freedesktop.org/wiki/Specifications/StatusNotifierItem/ ++ * [2] https://github.com/AyatanaIndicators/libayatana-appindicator-glib ++ * [3] https://invent.kde.org/frameworks/kstatusnotifieritem ++ * ++ */ ++#define SNI_NAME "org.kde.StatusNotifierItem" ++#define SNI_OPATH "/StatusNotifierItem" ++#define SNI_IFACE "org.kde.StatusNotifierItem" ++ ++typedef struct Item { ++ struct wl_list icons; ++ char *busname; ++ char *busobj; ++ char *menu_busobj; ++ char *appid; ++ Icon *icon; ++ FallbackIcon *fallback_icon; ++ ++ Watcher *watcher; ++ ++ int fgcolor; ++ ++ int ready; ++ ++ struct wl_list link; ++} Item; ++ ++Item *createitem (const char *busname, const char *busobj, Watcher *watcher); ++void destroyitem (Item *item); ++ ++void item_activate (Item *item); ++void item_show_menu (Item *item); ++ ++#endif /* ITEM_H */ +diff --git a/systray/menu.c b/systray/menu.c +new file mode 100644 +index 0000000..ff3bfb5 +--- /dev/null ++++ b/systray/menu.c +@@ -0,0 +1,757 @@ ++#include "menu.h" ++ ++#include <dbus/dbus.h> ++#include <wayland-server-core.h> ++#include <wayland-util.h> ++ ++#include <errno.h> ++#include <signal.h> ++#include <stdint.h> ++#include <stdio.h> ++#include <stdlib.h> ++#include <string.h> ++#include <sys/types.h> ++#include <sys/wait.h> ++#include <time.h> ++#include <unistd.h> ++ ++// IWYU pragma: no_include "dbus/dbus-protocol.h" ++// IWYU pragma: no_include "dbus/dbus-shared.h" ++ ++#define DBUSMENU_IFACE "com.canonical.dbusmenu" ++#define BUFSIZE 512 ++#define LABEL_MAX 64 ++ ++typedef struct { ++ struct wl_array layout; ++ DBusConnection *conn; ++ struct wl_event_loop *loop; ++ char *busname; ++ char *busobj; ++ const char **menucmd; ++} Menu; ++ ++typedef struct { ++ char label[LABEL_MAX]; ++ dbus_int32_t id; ++ struct wl_array submenu; ++ int has_submenu; ++} MenuItem; ++ ++typedef struct { ++ struct wl_event_loop *loop; ++ struct wl_event_source *fd_source; ++ struct wl_array *layout_node; ++ Menu *menu; ++ pid_t menu_pid; ++ int fd; ++} MenuShowContext; ++ ++static int extract_menu (DBusMessageIter *av, struct wl_array *menu); ++static int real_show_menu (Menu *menu, struct wl_array *m); ++static void submenus_destroy_recursive (struct wl_array *m); ++ ++static void ++menuitem_init(MenuItem *mi) ++{ ++ wl_array_init(&mi->submenu); ++ mi->id = -1; ++ *mi->label = '\0'; ++ mi->has_submenu = 0; ++} ++ ++static void ++submenus_destroy_recursive(struct wl_array *layout_node) ++{ ++ MenuItem *mi; ++ ++ wl_array_for_each(mi, layout_node) { ++ if (mi->has_submenu) { ++ submenus_destroy_recursive(&mi->submenu); ++ wl_array_release(&mi->submenu); ++ } ++ } ++} ++ ++static void ++menu_destroy(Menu *menu) ++{ ++ submenus_destroy_recursive(&menu->layout); ++ wl_array_release(&menu->layout); ++ free(menu->busname); ++ free(menu->busobj); ++ free(menu); ++} ++ ++static void ++menu_show_ctx_finalize(MenuShowContext *ctx, int error) ++{ ++ if (ctx->fd_source) ++ wl_event_source_remove(ctx->fd_source); ++ ++ if (ctx->fd >= 0) ++ close(ctx->fd); ++ ++ if (ctx->menu_pid >= 0) { ++ if (waitpid(ctx->menu_pid, NULL, WNOHANG) == 0) ++ kill(ctx->menu_pid, SIGTERM); ++ } ++ ++ if (error) ++ menu_destroy(ctx->menu); ++ ++ free(ctx); ++} ++ ++static void ++remove_newline(char *buf) ++{ ++ size_t len; ++ ++ len = strlen(buf); ++ if (len > 0 && buf[len - 1] == '\n') ++ buf[len - 1] = '\0'; ++} ++ ++static void ++send_clicked(const char *busname, const char *busobj, int itemid, ++ DBusConnection *conn) ++{ ++ DBusMessage *msg = NULL; ++ DBusMessageIter iter = DBUS_MESSAGE_ITER_INIT_CLOSED; ++ DBusMessageIter sub = DBUS_MESSAGE_ITER_INIT_CLOSED; ++ const char *data = ""; ++ const char *eventid = "clicked"; ++ time_t timestamp; ++ ++ timestamp = time(NULL); ++ ++ msg = dbus_message_new_method_call(busname, busobj, DBUSMENU_IFACE, ++ "Event"); ++ if (!msg) ++ goto fail; ++ ++ dbus_message_iter_init_append(msg, &iter); ++ if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_INT32, &itemid) || ++ !dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, ++ &eventid) || ++ !dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT, ++ DBUS_TYPE_STRING_AS_STRING, ++ &sub) || ++ !dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &data) || ++ !dbus_message_iter_close_container(&iter, &sub) || ++ !dbus_message_iter_append_basic(&iter, DBUS_TYPE_UINT32, ++ ×tamp)) { ++ goto fail; ++ } ++ ++ if (!dbus_connection_send_with_reply(conn, msg, NULL, -1)) ++ goto fail; ++ ++ dbus_message_unref(msg); ++ return; ++ ++fail: ++ dbus_message_iter_abandon_container_if_open(&iter, &sub); ++ if (msg) ++ dbus_message_unref(msg); ++} ++ ++static void ++menuitem_selected(const char *label, struct wl_array *m, Menu *menu) ++{ ++ MenuItem *mi; ++ ++ wl_array_for_each(mi, m) { ++ if (strcmp(mi->label, label) == 0) { ++ if (mi->has_submenu) { ++ real_show_menu(menu, &mi->submenu); ++ ++ } else { ++ send_clicked(menu->busname, menu->busobj, ++ mi->id, menu->conn); ++ menu_destroy(menu); ++ } ++ ++ return; ++ } ++ } ++} ++ ++static int ++read_pipe(int fd, uint32_t mask, void *data) ++{ ++ MenuShowContext *ctx = data; ++ ++ char buf[BUFSIZE]; ++ ssize_t bytes_read; ++ ++ bytes_read = read(fd, buf, BUFSIZE); ++ /* 0 == Got EOF, menu program closed without writing to stdout */ ++ if (bytes_read <= 0) ++ goto fail; ++ ++ buf[bytes_read] = '\0'; ++ remove_newline(buf); ++ ++ menuitem_selected(buf, ctx->layout_node, ctx->menu); ++ menu_show_ctx_finalize(ctx, 0); ++ return 0; ++ ++fail: ++ menu_show_ctx_finalize(ctx, 1); ++ return 0; ++} ++ ++static MenuShowContext * ++prepare_show_ctx(struct wl_event_loop *loop, int monitor_fd, int dmenu_pid, ++ struct wl_array *layout_node, Menu *menu) ++{ ++ MenuShowContext *ctx = NULL; ++ struct wl_event_source *fd_src = NULL; ++ ++ ctx = calloc(1, sizeof(MenuShowContext)); ++ if (!ctx) ++ goto fail; ++ ++ fd_src = wl_event_loop_add_fd(menu->loop, monitor_fd, WL_EVENT_READABLE, ++ read_pipe, ctx); ++ if (!fd_src) ++ goto fail; ++ ++ ctx->fd_source = fd_src; ++ ctx->fd = monitor_fd; ++ ctx->menu_pid = dmenu_pid; ++ ctx->layout_node = layout_node; ++ ctx->menu = menu; ++ ++ return ctx; ++ ++fail: ++ if (fd_src) ++ wl_event_source_remove(fd_src); ++ free(ctx); ++ return NULL; ++} ++ ++static int ++write_dmenu_buf(char *buf, struct wl_array *layout_node) ++{ ++ MenuItem *mi; ++ int r; ++ size_t curlen = 0; ++ ++ *buf = '\0'; ++ ++ wl_array_for_each(mi, layout_node) { ++ curlen += strlen(mi->label) + ++ 2; /* +2 is newline + nul terminator */ ++ if (curlen + 1 > BUFSIZE) { ++ r = -1; ++ goto fail; ++ } ++ ++ strcat(buf, mi->label); ++ strcat(buf, "\n"); ++ } ++ remove_newline(buf); ++ ++ return 0; ++ ++fail: ++ fprintf(stderr, "Failed to construct dmenu input\n"); ++ return r; ++} ++ ++static int ++real_show_menu(Menu *menu, struct wl_array *layout_node) ++{ ++ MenuShowContext *ctx = NULL; ++ char buf[BUFSIZE]; ++ int to_pipe[2], from_pipe[2]; ++ pid_t pid; ++ ++ if (pipe(to_pipe) < 0 || pipe(from_pipe) < 0) ++ goto fail; ++ ++ pid = fork(); ++ if (pid < 0) { ++ goto fail; ++ } else if (pid == 0) { ++ dup2(to_pipe[0], STDIN_FILENO); ++ dup2(from_pipe[1], STDOUT_FILENO); ++ ++ close(to_pipe[0]); ++ close(to_pipe[1]); ++ close(from_pipe[1]); ++ close(from_pipe[0]); ++ ++ if (execvp(menu->menucmd[0], (char *const *)menu->menucmd)) { ++ perror("Error spawning menu program"); ++ exit(EXIT_FAILURE); ++ } ++ } ++ ++ ctx = prepare_show_ctx(menu->loop, from_pipe[0], pid, layout_node, ++ menu); ++ if (!ctx) ++ goto fail; ++ ++ if (write_dmenu_buf(buf, layout_node) < 0 || ++ write(to_pipe[1], buf, strlen(buf)) < 0) { ++ goto fail; ++ } ++ ++ close(to_pipe[0]); ++ close(to_pipe[1]); ++ close(from_pipe[1]); ++ return 0; ++ ++fail: ++ close(to_pipe[0]); ++ close(to_pipe[1]); ++ close(from_pipe[1]); ++ menu_show_ctx_finalize(ctx, 1); ++ return -1; ++} ++ ++static void ++createmenuitem(MenuItem *mi, dbus_int32_t id, const char *label, ++ int toggle_state, int has_submenu) ++{ ++ char *tok; ++ char temp[LABEL_MAX]; ++ ++ if (toggle_state == 0) ++ strcpy(mi->label, "☐ "); ++ else if (toggle_state == 1) ++ strcpy(mi->label, "✓ "); ++ else ++ strcpy(mi->label, " "); ++ ++ /* Remove "mnemonics" (underscores which mark keyboard shortcuts) */ ++ strcpy(temp, label); ++ tok = strtok(temp, "_"); ++ do { ++ strcat(mi->label, tok); ++ } while ((tok = strtok(NULL, "_"))); ++ ++ if (has_submenu) { ++ mi->has_submenu = 1; ++ strcat(mi->label, " →"); ++ } ++ ++ mi->id = id; ++} ++ ++/** ++ * Populates the passed in menuitem based on the dictionary contents. ++ * ++ * @param[in] dict ++ * @param[in] itemid ++ * @param[in] mi ++ * @param[out] has_submenu ++ * @param[out] status <0 on error, 0 on success, >0 if menuitem was skipped ++ */ ++static int ++read_dict(DBusMessageIter *dict, dbus_int32_t itemid, MenuItem *mi, ++ int *has_submenu) ++{ ++ DBusMessageIter member, val; ++ const char *children_display = NULL, *label = NULL, *toggle_type = NULL; ++ const char *key; ++ dbus_bool_t visible = TRUE, enabled = TRUE; ++ dbus_int32_t toggle_state = 1; ++ int r; ++ ++ do { ++ dbus_message_iter_recurse(dict, &member); ++ if (dbus_message_iter_get_arg_type(&member) != ++ DBUS_TYPE_STRING) { ++ r = -1; ++ goto fail; ++ } ++ dbus_message_iter_get_basic(&member, &key); ++ ++ dbus_message_iter_next(&member); ++ if (dbus_message_iter_get_arg_type(&member) != ++ DBUS_TYPE_VARIANT) { ++ r = -1; ++ goto fail; ++ } ++ dbus_message_iter_recurse(&member, &val); ++ ++ if (strcmp(key, "visible") == 0) { ++ if (dbus_message_iter_get_arg_type(&val) != ++ DBUS_TYPE_BOOLEAN) { ++ r = -1; ++ goto fail; ++ } ++ dbus_message_iter_get_basic(&val, &visible); ++ ++ } else if (strcmp(key, "enabled") == 0) { ++ if (dbus_message_iter_get_arg_type(&val) != ++ DBUS_TYPE_BOOLEAN) { ++ r = -1; ++ goto fail; ++ } ++ dbus_message_iter_get_basic(&val, &enabled); ++ ++ } else if (strcmp(key, "toggle-type") == 0) { ++ if (dbus_message_iter_get_arg_type(&val) != ++ DBUS_TYPE_STRING) { ++ r = -1; ++ goto fail; ++ } ++ dbus_message_iter_get_basic(&val, &toggle_type); ++ ++ } else if (strcmp(key, "toggle-state") == 0) { ++ if (dbus_message_iter_get_arg_type(&val) != ++ DBUS_TYPE_INT32) { ++ r = -1; ++ goto fail; ++ } ++ dbus_message_iter_get_basic(&val, &toggle_state); ++ ++ } else if (strcmp(key, "children-display") == 0) { ++ if (dbus_message_iter_get_arg_type(&val) != ++ DBUS_TYPE_STRING) { ++ r = -1; ++ goto fail; ++ } ++ dbus_message_iter_get_basic(&val, &children_display); ++ ++ if (strcmp(children_display, "submenu") == 0) ++ *has_submenu = 1; ++ ++ } else if (strcmp(key, "label") == 0) { ++ if (dbus_message_iter_get_arg_type(&val) != ++ DBUS_TYPE_STRING) { ++ r = -1; ++ goto fail; ++ } ++ dbus_message_iter_get_basic(&val, &label); ++ } ++ } while (dbus_message_iter_next(dict)); ++ ++ /* Skip hidden etc items */ ++ if (!label || !visible || !enabled) ++ return 1; ++ ++ /* ++ * 4 characters for checkmark and submenu indicator, ++ * 1 for nul terminator ++ */ ++ if (strlen(label) + 5 > LABEL_MAX) { ++ fprintf(stderr, "Too long menu entry label: %s! Skipping...\n", ++ label); ++ return 1; ++ } ++ ++ if (toggle_type && strcmp(toggle_type, "checkmark") == 0) ++ createmenuitem(mi, itemid, label, toggle_state, *has_submenu); ++ else ++ createmenuitem(mi, itemid, label, -1, *has_submenu); ++ ++ return 0; ++ ++fail: ++ fprintf(stderr, "Error parsing menu data\n"); ++ return r; ++} ++ ++/** ++ * Extracts a menuitem from a DBusMessage ++ * ++ * @param[in] strct ++ * @param[in] mi ++ * @param[out] status <0 on error, 0 on success, >0 if menuitem was skipped ++ */ ++static int ++extract_menuitem(DBusMessageIter *strct, MenuItem *mi) ++{ ++ DBusMessageIter val, dict; ++ dbus_int32_t itemid; ++ int has_submenu = 0; ++ int r; ++ ++ dbus_message_iter_recurse(strct, &val); ++ if (dbus_message_iter_get_arg_type(&val) != DBUS_TYPE_INT32) { ++ r = -1; ++ goto fail; ++ } ++ dbus_message_iter_get_basic(&val, &itemid); ++ ++ if (!dbus_message_iter_next(&val) || ++ dbus_message_iter_get_arg_type(&val) != DBUS_TYPE_ARRAY) { ++ r = -1; ++ goto fail; ++ } ++ dbus_message_iter_recurse(&val, &dict); ++ if (dbus_message_iter_get_arg_type(&dict) != DBUS_TYPE_DICT_ENTRY) { ++ r = -1; ++ goto fail; ++ } ++ ++ r = read_dict(&dict, itemid, mi, &has_submenu); ++ if (r < 0) { ++ goto fail; ++ ++ } else if (r == 0 && has_submenu) { ++ dbus_message_iter_next(&val); ++ if (dbus_message_iter_get_arg_type(&val) != DBUS_TYPE_ARRAY) ++ goto fail; ++ r = extract_menu(&val, &mi->submenu); ++ if (r < 0) ++ goto fail; ++ } ++ ++ return r; ++ ++fail: ++ return r; ++} ++ ++static int ++extract_menu(DBusMessageIter *av, struct wl_array *layout_node) ++{ ++ DBusMessageIter variant, menuitem; ++ MenuItem *mi; ++ int r; ++ ++ dbus_message_iter_recurse(av, &variant); ++ if (dbus_message_iter_get_arg_type(&variant) != DBUS_TYPE_VARIANT) { ++ r = -1; ++ goto fail; ++ } ++ ++ mi = wl_array_add(layout_node, sizeof(MenuItem)); ++ if (!mi) { ++ r = -ENOMEM; ++ goto fail; ++ } ++ menuitem_init(mi); ++ ++ do { ++ dbus_message_iter_recurse(&variant, &menuitem); ++ if (dbus_message_iter_get_arg_type(&menuitem) != ++ DBUS_TYPE_STRUCT) { ++ r = -1; ++ goto fail; ++ } ++ ++ r = extract_menuitem(&menuitem, mi); ++ if (r < 0) ++ goto fail; ++ else if (r == 0) { ++ mi = wl_array_add(layout_node, sizeof(MenuItem)); ++ if (!mi) { ++ r = -ENOMEM; ++ goto fail; ++ } ++ menuitem_init(mi); ++ } ++ /* r > 0: no action was performed on mi */ ++ } while (dbus_message_iter_next(&variant)); ++ ++ return 0; ++ ++fail: ++ return r; ++} ++ ++static void ++layout_ready(DBusPendingCall *pending, void *data) ++{ ++ Menu *menu = data; ++ ++ DBusMessage *reply = NULL; ++ DBusMessageIter iter, strct; ++ dbus_uint32_t revision; ++ int r; ++ ++ reply = dbus_pending_call_steal_reply(pending); ++ if (!reply || dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR) { ++ r = -1; ++ goto fail; ++ } ++ ++ dbus_message_iter_init(reply, &iter); ++ if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_UINT32) { ++ r = -1; ++ goto fail; ++ } ++ dbus_message_iter_get_basic(&iter, &revision); ++ ++ if (!dbus_message_iter_next(&iter) || ++ dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRUCT) { ++ r = -1; ++ goto fail; ++ } ++ dbus_message_iter_recurse(&iter, &strct); ++ ++ /* ++ * id 0 is the root, which contains nothing of interest. ++ * Traverse past it. ++ */ ++ if (dbus_message_iter_get_arg_type(&strct) != DBUS_TYPE_INT32 || ++ !dbus_message_iter_next(&strct) || ++ dbus_message_iter_get_arg_type(&strct) != DBUS_TYPE_ARRAY || ++ !dbus_message_iter_next(&strct) || ++ dbus_message_iter_get_arg_type(&strct) != DBUS_TYPE_ARRAY) { ++ r = -1; ++ goto fail; ++ } ++ ++ /* Root traversed over, extract the menu */ ++ wl_array_init(&menu->layout); ++ r = extract_menu(&strct, &menu->layout); ++ if (r < 0) ++ goto fail; ++ ++ r = real_show_menu(menu, &menu->layout); ++ if (r < 0) ++ goto fail; ++ ++ dbus_message_unref(reply); ++ dbus_pending_call_unref(pending); ++ return; ++ ++fail: ++ menu_destroy(menu); ++ if (reply) ++ dbus_message_unref(reply); ++ if (pending) ++ dbus_pending_call_unref(pending); ++} ++ ++static int ++request_layout(Menu *menu) ++{ ++ DBusMessage *msg = NULL; ++ DBusMessageIter iter = DBUS_MESSAGE_ITER_INIT_CLOSED; ++ DBusMessageIter strings = DBUS_MESSAGE_ITER_INIT_CLOSED; ++ DBusPendingCall *pending = NULL; ++ dbus_int32_t parentid, depth; ++ int r; ++ ++ parentid = 0; ++ depth = -1; ++ ++ /* menu busobj request answer didn't arrive yet. */ ++ if (!menu->busobj) { ++ r = -1; ++ goto fail; ++ } ++ ++ msg = dbus_message_new_method_call(menu->busname, menu->busobj, ++ DBUSMENU_IFACE, "GetLayout"); ++ if (!msg) { ++ r = -ENOMEM; ++ goto fail; ++ } ++ ++ dbus_message_iter_init_append(msg, &iter); ++ if (!dbus_message_iter_append_basic(&iter, DBUS_TYPE_INT32, ++ &parentid) || ++ !dbus_message_iter_append_basic(&iter, DBUS_TYPE_INT32, &depth) || ++ !dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, ++ DBUS_TYPE_STRING_AS_STRING, ++ &strings) || ++ !dbus_message_iter_close_container(&iter, &strings)) { ++ r = -ENOMEM; ++ goto fail; ++ } ++ ++ if (!dbus_connection_send_with_reply(menu->conn, msg, &pending, -1) || ++ !dbus_pending_call_set_notify(pending, layout_ready, menu, NULL)) { ++ r = -ENOMEM; ++ goto fail; ++ } ++ ++ dbus_message_unref(msg); ++ return 0; ++ ++fail: ++ if (pending) { ++ dbus_pending_call_cancel(pending); ++ dbus_pending_call_unref(pending); ++ } ++ dbus_message_iter_abandon_container_if_open(&iter, &strings); ++ if (msg) ++ dbus_message_unref(msg); ++ menu_destroy(menu); ++ return r; ++} ++ ++static void ++about_to_show_handle(DBusPendingCall *pending, void *data) ++{ ++ Menu *menu = data; ++ ++ DBusMessage *reply = NULL; ++ ++ reply = dbus_pending_call_steal_reply(pending); ++ if (!reply) ++ goto fail; ++ ++ if (request_layout(menu) < 0) ++ goto fail; ++ ++ dbus_message_unref(reply); ++ dbus_pending_call_unref(pending); ++ return; ++ ++fail: ++ if (reply) ++ dbus_message_unref(reply); ++ if (pending) ++ dbus_pending_call_unref(pending); ++ menu_destroy(menu); ++} ++ ++void ++menu_show(DBusConnection *conn, struct wl_event_loop *loop, const char *busname, ++ const char *busobj, const char **menucmd) ++{ ++ DBusMessage *msg = NULL; ++ DBusPendingCall *pending = NULL; ++ Menu *menu = NULL; ++ char *busname_dup = NULL, *busobj_dup = NULL; ++ dbus_int32_t parentid = 0; ++ ++ menu = calloc(1, sizeof(Menu)); ++ busname_dup = strdup(busname); ++ busobj_dup = strdup(busobj); ++ if (!menu || !busname_dup || !busobj_dup) ++ goto fail; ++ ++ menu->conn = conn; ++ menu->loop = loop; ++ menu->busname = busname_dup; ++ menu->busobj = busobj_dup; ++ menu->menucmd = menucmd; ++ ++ msg = dbus_message_new_method_call(menu->busname, menu->busobj, ++ DBUSMENU_IFACE, "AboutToShow"); ++ if (!msg) ++ goto fail; ++ ++ if (!dbus_message_append_args(msg, DBUS_TYPE_INT32, &parentid, ++ DBUS_TYPE_INVALID) || ++ !dbus_connection_send_with_reply(menu->conn, msg, &pending, -1) || ++ !dbus_pending_call_set_notify(pending, about_to_show_handle, menu, ++ NULL)) { ++ goto fail; ++ } ++ ++ dbus_message_unref(msg); ++ return; ++ ++fail: ++ if (pending) ++ dbus_pending_call_unref(pending); ++ if (msg) ++ dbus_message_unref(msg); ++ free(menu); ++} +diff --git a/systray/menu.h b/systray/menu.h +new file mode 100644 +index 0000000..7f48ada +--- /dev/null ++++ b/systray/menu.h +@@ -0,0 +1,11 @@ ++#ifndef MENU_H ++#define MENU_H ++ ++#include <dbus/dbus.h> ++#include <wayland-server-core.h> ++ ++/* The menu is built on demand and not kept around */ ++void menu_show (DBusConnection *conn, struct wl_event_loop *loop, ++ const char *busname, const char *busobj, const char **menucmd); ++ ++#endif /* MENU_H */ +diff --git a/systray/tray.c b/systray/tray.c +new file mode 100644 +index 0000000..7f9b1b0 +--- /dev/null ++++ b/systray/tray.c +@@ -0,0 +1,237 @@ ++#include "tray.h" ++ ++#include "icon.h" ++#include "item.h" ++#include "menu.h" ++#include "watcher.h" ++ ++#include <fcft/fcft.h> ++#include <pixman.h> ++#include <wayland-util.h> ++ ++#include <stddef.h> ++#include <stdint.h> ++#include <stdio.h> ++#include <stdlib.h> ++ ++#define PIXMAN_COLOR(hex) \ ++ { .red = ((hex >> 24) & 0xff) * 0x101, \ ++ .green = ((hex >> 16) & 0xff) * 0x101, \ ++ .blue = ((hex >> 8) & 0xff) * 0x101, \ ++ .alpha = (hex & 0xff) * 0x101 } ++ ++static Watcher * ++tray_get_watcher(const Tray *tray) ++{ ++ if (!tray) ++ return NULL; ++ ++ return tray->watcher; ++} ++ ++static pixman_image_t * ++createcanvas(int width, int height, int bgcolor) ++{ ++ pixman_image_t *src, *dest; ++ pixman_color_t bgcolor_pix = PIXMAN_COLOR(bgcolor); ++ ++ dest = pixman_image_create_bits(PIXMAN_a8r8g8b8, width, height, NULL, ++ 0); ++ src = pixman_image_create_solid_fill(&bgcolor_pix); ++ ++ pixman_image_composite32(PIXMAN_OP_SRC, src, NULL, dest, 0, 0, 0, 0, 0, ++ 0, width, height); ++ ++ pixman_image_unref(src); ++ return dest; ++} ++ ++void ++tray_update(Tray *tray) ++{ ++ Item *item; ++ Watcher *watcher; ++ int icon_size, i = 0, canvas_width, canvas_height, n_items, spacing; ++ pixman_image_t *canvas = NULL, *img; ++ ++ watcher = tray_get_watcher(tray); ++ n_items = watcher_get_n_items(watcher); ++ ++ if (!n_items) { ++ if (tray->image) { ++ pixman_image_unref(tray->image); ++ tray->image = NULL; ++ } ++ tray->cb(tray->monitor); ++ return; ++ } ++ ++ icon_size = tray->height; ++ spacing = tray->spacing; ++ canvas_width = n_items * (icon_size + spacing) + spacing; ++ canvas_height = tray->height; ++ ++ canvas = createcanvas(canvas_width, canvas_height, tray->scheme[1]); ++ if (!canvas) ++ goto fail; ++ ++ wl_list_for_each(item, &watcher->items, link) { ++ int slot_x_start = spacing + i * (icon_size + spacing); ++ int slot_x_end = slot_x_start + icon_size + spacing; ++ int slot_x_width = slot_x_end - slot_x_start; ++ ++ int slot_y_start = 0; ++ int slot_y_end = canvas_height; ++ int slot_y_width = slot_y_end - slot_y_start; ++ ++ if (item->icon) { ++ /* Real icon */ ++ img = item->icon->img; ++ if (resize_image(img, icon_size, icon_size) < 0) ++ goto fail; ++ pixman_image_composite32(PIXMAN_OP_OVER, img, NULL, ++ canvas, 0, 0, 0, 0, ++ slot_x_start, 0, canvas_width, ++ canvas_height); ++ ++ } else if (item->appid) { ++ /* Font glyph alpha mask */ ++ const struct fcft_glyph *g; ++ int pen_y, pen_x; ++ pixman_color_t fg_color = PIXMAN_COLOR(tray->scheme[0]); ++ pixman_image_t *fg; ++ ++ if (item->fallback_icon) { ++ g = item->fallback_icon; ++ } else { ++ g = createfallbackicon(item->appid, ++ item->fgcolor, ++ tray->font); ++ if (!g) ++ goto fail; ++ item->fallback_icon = g; ++ } ++ ++ pen_x = slot_x_start + (slot_x_width - g->width) / 2; ++ pen_y = slot_y_start + (slot_y_width - g->height) / 2; ++ ++ fg = pixman_image_create_solid_fill(&fg_color); ++ pixman_image_composite32(PIXMAN_OP_OVER, fg, g->pix, ++ canvas, 0, 0, 0, 0, pen_x, ++ pen_y, canvas_width, ++ canvas_height); ++ pixman_image_unref(fg); ++ } ++ i++; ++ } ++ ++ if (tray->image) ++ pixman_image_unref(tray->image); ++ tray->image = canvas; ++ tray->cb(tray->monitor); ++ ++ return; ++ ++fail: ++ if (canvas) ++ pixman_image_unref(canvas); ++ return; ++} ++ ++void ++destroytray(Tray *tray) ++{ ++ if (tray->image) ++ pixman_image_unref(tray->image); ++ if (tray->font) ++ fcft_destroy(tray->font); ++ free(tray); ++} ++ ++Tray * ++createtray(void *monitor, int height, int spacing, uint32_t *colorscheme, ++ const char **fonts, const char *fontattrs, TrayNotifyCb cb, ++ Watcher *watcher) ++{ ++ Tray *tray = NULL; ++ char fontattrs_my[128]; ++ struct fcft_font *font = NULL; ++ ++ sprintf(fontattrs_my, "%s:%s", fontattrs, "weight:bold"); ++ ++ tray = calloc(1, sizeof(Tray)); ++ font = fcft_from_name(1, fonts, fontattrs_my); ++ if (!tray || !font) ++ goto fail; ++ ++ tray->monitor = monitor; ++ tray->height = height; ++ tray->spacing = spacing; ++ tray->scheme = colorscheme; ++ tray->cb = cb; ++ tray->watcher = watcher; ++ tray->font = font; ++ ++ return tray; ++ ++fail: ++ if (font) ++ fcft_destroy(font); ++ free(tray); ++ return NULL; ++} ++ ++int ++tray_get_width(const Tray *tray) ++{ ++ if (tray && tray->image) ++ return pixman_image_get_width(tray->image); ++ else ++ return 0; ++} ++ ++int ++tray_get_icon_width(const Tray *tray) ++{ ++ if (!tray) ++ return 0; ++ ++ return tray->height; ++} ++ ++void ++tray_rightclicked(Tray *tray, unsigned int index, const char **menucmd) ++{ ++ Item *item; ++ Watcher *watcher; ++ unsigned int count = 0; ++ ++ watcher = tray_get_watcher(tray); ++ ++ wl_list_for_each(item, &watcher->items, link) { ++ if (count == index) { ++ menu_show(watcher->conn, watcher->loop, item->busname, ++ item->menu_busobj, menucmd); ++ return; ++ } ++ count++; ++ } ++} ++ ++void ++tray_leftclicked(Tray *tray, unsigned int index) ++{ ++ Item *item; ++ Watcher *watcher; ++ unsigned int count = 0; ++ ++ watcher = tray_get_watcher(tray); ++ ++ wl_list_for_each(item, &watcher->items, link) { ++ if (count == index) { ++ item_activate(item); ++ return; ++ } ++ count++; ++ } ++} +diff --git a/systray/tray.h b/systray/tray.h +new file mode 100644 +index 0000000..af4e5e3 +--- /dev/null ++++ b/systray/tray.h +@@ -0,0 +1,37 @@ ++#ifndef TRAY_H ++#define TRAY_H ++ ++#include "watcher.h" ++ ++#include <pixman.h> ++#include <wayland-util.h> ++ ++#include <stdint.h> ++ ++typedef void (*TrayNotifyCb)(void *data); ++ ++typedef struct { ++ pixman_image_t *image; ++ struct fcft_font *font; ++ uint32_t *scheme; ++ TrayNotifyCb cb; ++ Watcher *watcher; ++ void *monitor; ++ int height; ++ int spacing; ++ ++ struct wl_list link; ++} Tray; ++ ++Tray *createtray (void *monitor, int height, int spacing, uint32_t *colorscheme, ++ const char **fonts, const char *fontattrs, TrayNotifyCb cb, ++ Watcher *watcher); ++void destroytray (Tray *tray); ++ ++int tray_get_width (const Tray *tray); ++int tray_get_icon_width (const Tray *tray); ++void tray_update (Tray *tray); ++void tray_leftclicked (Tray *tray, unsigned int index); ++void tray_rightclicked (Tray *tray, unsigned int index, const char **menucmd); ++ ++#endif /* TRAY_H */ +diff --git a/systray/watcher.c b/systray/watcher.c +new file mode 100644 +index 0000000..8dd84b9 +--- /dev/null ++++ b/systray/watcher.c +@@ -0,0 +1,551 @@ ++#include "watcher.h" ++ ++#include "item.h" ++#include "tray.h" ++ ++#include <dbus/dbus.h> ++#include <wayland-util.h> ++ ++#include <errno.h> ++#include <stdio.h> ++#include <string.h> ++ ++// IWYU pragma: no_include "dbus/dbus-protocol.h" ++// IWYU pragma: no_include "dbus/dbus-shared.h" ++ ++static const char *const match_rule = ++ "type='signal'," ++ "interface='" DBUS_INTERFACE_DBUS ++ "'," ++ "member='NameOwnerChanged'"; ++ ++static const char *const snw_xml = ++ "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\"\n" ++ " \"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n" ++ "<node>\n" ++ " <interface name=\"" DBUS_INTERFACE_PROPERTIES ++ "\">\n" ++ " <method name=\"Get\">\n" ++ " <arg type=\"s\" name=\"interface_name\" direction=\"in\"/>\n" ++ " <arg type=\"s\" name=\"property_name\" direction=\"in\"/>\n" ++ " <arg type=\"v\" name=\"value\" direction=\"out\"/>\n" ++ " </method>\n" ++ " <method name=\"GetAll\">\n" ++ " <arg type=\"s\" name=\"interface_name\" direction=\"in\"/>\n" ++ " <arg type=\"a{sv}\" name=\"properties\" direction=\"out\"/>\n" ++ " </method>\n" ++ " <method name=\"Set\">\n" ++ " <arg type=\"s\" name=\"interface_name\" direction=\"in\"/>\n" ++ " <arg type=\"s\" name=\"property_name\" direction=\"in\"/>\n" ++ " <arg type=\"v\" name=\"value\" direction=\"in\"/>\n" ++ " </method>\n" ++ " <signal name=\"PropertiesChanged\">\n" ++ " <arg type=\"s\" name=\"interface_name\"/>\n" ++ " <arg type=\"a{sv}\" name=\"changed_properties\"/>\n" ++ " <arg type=\"as\" name=\"invalidated_properties\"/>\n" ++ " </signal>\n" ++ " </interface>\n" ++ " <interface name=\"" DBUS_INTERFACE_INTROSPECTABLE ++ "\">\n" ++ " <method name=\"Introspect\">\n" ++ " <arg type=\"s\" name=\"xml_data\" direction=\"out\"/>\n" ++ " </method>\n" ++ " </interface>\n" ++ " <interface name=\"" DBUS_INTERFACE_PEER ++ "\">\n" ++ " <method name=\"Ping\"/>\n" ++ " <method name=\"GetMachineId\">\n" ++ " <arg type=\"s\" name=\"machine_uuid\" direction=\"out\"/>\n" ++ " </method>\n" ++ " </interface>\n" ++ " <interface name=\"" SNW_IFACE ++ "\">\n" ++ " <!-- methods -->\n" ++ " <method name=\"RegisterStatusNotifierItem\">\n" ++ " <arg name=\"service\" type=\"s\" direction=\"in\" />\n" ++ " </method>\n" ++ " <!-- properties -->\n" ++ " <property name=\"IsStatusNotifierHostRegistered\" type=\"b\" access=\"read\" />\n" ++ " <property name=\"ProtocolVersion\" type=\"i\" access=\"read\" />\n" ++ " <property name=\"RegisteredStatusNotifierItems\" type=\"as\" access=\"read\" />\n" ++ " <!-- signals -->\n" ++ " <signal name=\"StatusNotifierHostRegistered\">\n" ++ " </signal>\n" ++ " </interface>\n" ++ "</node>\n"; ++ ++static void ++unregister_item(Watcher *watcher, Item *item) ++{ ++ wl_list_remove(&item->link); ++ destroyitem(item); ++ ++ watcher_update_trays(watcher); ++} ++ ++static Item * ++item_name_to_ptr(const Watcher *watcher, const char *busname) ++{ ++ Item *item; ++ ++ wl_list_for_each(item, &watcher->items, link) { ++ if (!item || !item->busname) ++ return NULL; ++ if (strcmp(item->busname, busname) == 0) ++ return item; ++ } ++ ++ return NULL; ++} ++ ++static DBusHandlerResult ++handle_nameowner_changed(Watcher *watcher, DBusConnection *conn, ++ DBusMessage *msg) ++{ ++ char *name, *old_owner, *new_owner; ++ Item *item; ++ ++ if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &name, ++ DBUS_TYPE_STRING, &old_owner, ++ DBUS_TYPE_STRING, &new_owner, ++ DBUS_TYPE_INVALID)) { ++ return DBUS_HANDLER_RESULT_HANDLED; ++ } ++ ++ if (*new_owner != '\0' || *name == '\0') ++ return DBUS_HANDLER_RESULT_HANDLED; ++ ++ item = item_name_to_ptr(watcher, name); ++ if (!item) ++ return DBUS_HANDLER_RESULT_HANDLED; ++ ++ unregister_item(watcher, item); ++ ++ return DBUS_HANDLER_RESULT_HANDLED; ++} ++ ++static DBusHandlerResult ++filter_bus(DBusConnection *conn, DBusMessage *msg, void *data) ++{ ++ Watcher *watcher = data; ++ ++ if (dbus_message_is_signal(msg, DBUS_INTERFACE_DBUS, ++ "NameOwnerChanged")) ++ return handle_nameowner_changed(watcher, conn, msg); ++ ++ else ++ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; ++} ++ ++static DBusHandlerResult ++respond_register_item(Watcher *watcher, DBusConnection *conn, DBusMessage *msg) ++{ ++ DBusHandlerResult res = DBUS_HANDLER_RESULT_HANDLED; ++ ++ DBusMessage *reply = NULL; ++ Item *item; ++ const char *sender, *param, *busobj, *registree_name; ++ ++ if (!(sender = dbus_message_get_sender(msg)) || ++ !dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, ¶m, ++ DBUS_TYPE_INVALID)) { ++ reply = dbus_message_new_error(msg, DBUS_ERROR_INVALID_ARGS, ++ "Malformed message"); ++ goto send; ++ } ++ ++ switch (*param) { ++ case '/': ++ registree_name = sender; ++ busobj = param; ++ break; ++ case ':': ++ registree_name = param; ++ busobj = SNI_OPATH; ++ break; ++ default: ++ reply = dbus_message_new_error_printf(msg, ++ DBUS_ERROR_INVALID_ARGS, ++ "Bad argument: \"%s\"", ++ param); ++ goto send; ++ } ++ ++ if (*registree_name != ':' || ++ !dbus_validate_bus_name(registree_name, NULL)) { ++ reply = dbus_message_new_error_printf(msg, ++ DBUS_ERROR_INVALID_ARGS, ++ "Invalid busname %s", ++ registree_name); ++ goto send; ++ } ++ ++ if (item_name_to_ptr(watcher, registree_name)) { ++ reply = dbus_message_new_error_printf(msg, ++ DBUS_ERROR_INVALID_ARGS, ++ "%s already tracked", ++ registree_name); ++ goto send; ++ } ++ ++ item = createitem(registree_name, busobj, watcher); ++ wl_list_insert(&watcher->items, &item->link); ++ watcher_update_trays(watcher); ++ ++ reply = dbus_message_new_method_return(msg); ++ ++send: ++ if (!reply || !dbus_connection_send(conn, reply, NULL)) ++ res = DBUS_HANDLER_RESULT_NEED_MEMORY; ++ ++ if (reply) ++ dbus_message_unref(reply); ++ return res; ++} ++ ++static int ++get_registered_items(const Watcher *watcher, DBusMessageIter *iter) ++{ ++ DBusMessageIter names = DBUS_MESSAGE_ITER_INIT_CLOSED; ++ Item *item; ++ int r; ++ ++ if (!dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, ++ DBUS_TYPE_STRING_AS_STRING, ++ &names)) { ++ r = -ENOMEM; ++ goto fail; ++ } ++ ++ wl_list_for_each(item, &watcher->items, link) { ++ if (!dbus_message_iter_append_basic(&names, DBUS_TYPE_STRING, ++ &item->busname)) { ++ r = -ENOMEM; ++ goto fail; ++ } ++ } ++ ++ dbus_message_iter_close_container(iter, &names); ++ return 0; ++ ++fail: ++ dbus_message_iter_abandon_container_if_open(iter, &names); ++ return r; ++} ++ ++static int ++get_registered_items_variant(const Watcher *watcher, DBusMessageIter *iter) ++{ ++ DBusMessageIter variant = DBUS_MESSAGE_ITER_INIT_CLOSED; ++ int r; ++ ++ if (!dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, "as", ++ &variant) || ++ get_registered_items(watcher, &variant) < 0) { ++ r = -ENOMEM; ++ goto fail; ++ } ++ ++ dbus_message_iter_close_container(iter, &variant); ++ return 0; ++ ++fail: ++ dbus_message_iter_abandon_container_if_open(iter, &variant); ++ return r; ++} ++ ++static int ++get_isregistered(DBusMessageIter *iter) ++{ ++ DBusMessageIter variant = DBUS_MESSAGE_ITER_INIT_CLOSED; ++ dbus_bool_t is_registered = TRUE; ++ int r; ++ ++ if (!dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, ++ DBUS_TYPE_BOOLEAN_AS_STRING, ++ &variant) || ++ !dbus_message_iter_append_basic(&variant, DBUS_TYPE_BOOLEAN, ++ &is_registered)) { ++ r = -ENOMEM; ++ goto fail; ++ } ++ ++ dbus_message_iter_close_container(iter, &variant); ++ return 0; ++ ++fail: ++ dbus_message_iter_abandon_container_if_open(iter, &variant); ++ return r; ++} ++ ++static int ++get_version(DBusMessageIter *iter) ++{ ++ DBusMessageIter variant = DBUS_MESSAGE_ITER_INIT_CLOSED; ++ dbus_int32_t protovers = 0; ++ int r; ++ ++ if (!dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, ++ DBUS_TYPE_INT32_AS_STRING, ++ &variant) || ++ !dbus_message_iter_append_basic(&variant, DBUS_TYPE_INT32, ++ &protovers)) { ++ r = -ENOMEM; ++ goto fail; ++ } ++ ++ dbus_message_iter_close_container(iter, &variant); ++ return 0; ++ ++fail: ++ dbus_message_iter_abandon_container_if_open(iter, &variant); ++ return r; ++} ++ ++static DBusHandlerResult ++respond_get_prop(Watcher *watcher, DBusConnection *conn, DBusMessage *msg) ++{ ++ DBusError err = DBUS_ERROR_INIT; ++ DBusMessage *reply = NULL; ++ DBusMessageIter iter = DBUS_MESSAGE_ITER_INIT_CLOSED; ++ const char *iface, *prop; ++ ++ if (!dbus_message_get_args(msg, &err, DBUS_TYPE_STRING, &iface, ++ DBUS_TYPE_STRING, &prop, ++ DBUS_TYPE_INVALID)) { ++ reply = dbus_message_new_error(msg, err.name, err.message); ++ dbus_error_free(&err); ++ goto send; ++ } ++ ++ if (strcmp(iface, SNW_IFACE) != 0) { ++ reply = dbus_message_new_error_printf( ++ msg, DBUS_ERROR_UNKNOWN_INTERFACE, ++ "Unknown interface \"%s\"", iface); ++ goto send; ++ } ++ ++ reply = dbus_message_new_method_return(msg); ++ if (!reply) ++ goto fail; ++ ++ if (strcmp(prop, "ProtocolVersion") == 0) { ++ dbus_message_iter_init_append(reply, &iter); ++ if (get_version(&iter) < 0) ++ goto fail; ++ ++ } else if (strcmp(prop, "IsStatusNotifierHostRegistered") == 0) { ++ dbus_message_iter_init_append(reply, &iter); ++ if (get_isregistered(&iter) < 0) ++ goto fail; ++ ++ } else if (strcmp(prop, "RegisteredStatusNotifierItems") == 0) { ++ dbus_message_iter_init_append(reply, &iter); ++ if (get_registered_items_variant(watcher, &iter) < 0) ++ goto fail; ++ ++ } else { ++ dbus_message_unref(reply); ++ reply = dbus_message_new_error_printf( ++ reply, DBUS_ERROR_UNKNOWN_PROPERTY, ++ "Property \"%s\" does not exist", prop); ++ } ++ ++send: ++ if (!reply || !dbus_connection_send(conn, reply, NULL)) ++ goto fail; ++ ++ if (reply) ++ dbus_message_unref(reply); ++ return DBUS_HANDLER_RESULT_HANDLED; ++ ++fail: ++ if (reply) ++ dbus_message_unref(reply); ++ return DBUS_HANDLER_RESULT_NEED_MEMORY; ++} ++ ++static DBusHandlerResult ++respond_all_props(Watcher *watcher, DBusConnection *conn, DBusMessage *msg) ++{ ++ DBusMessage *reply = NULL; ++ DBusMessageIter array = DBUS_MESSAGE_ITER_INIT_CLOSED; ++ DBusMessageIter dict = DBUS_MESSAGE_ITER_INIT_CLOSED; ++ DBusMessageIter iter = DBUS_MESSAGE_ITER_INIT_CLOSED; ++ const char *prop; ++ ++ reply = dbus_message_new_method_return(msg); ++ if (!reply) ++ goto fail; ++ dbus_message_iter_init_append(reply, &iter); ++ ++ if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", ++ &array)) ++ goto fail; ++ ++ prop = "ProtocolVersion"; ++ if (!dbus_message_iter_open_container(&array, DBUS_TYPE_DICT_ENTRY, ++ NULL, &dict) || ++ !dbus_message_iter_append_basic(&dict, DBUS_TYPE_STRING, &prop) || ++ get_version(&dict) < 0 || ++ !dbus_message_iter_close_container(&array, &dict)) { ++ goto fail; ++ } ++ ++ prop = "IsStatusNotifierHostRegistered"; ++ if (!dbus_message_iter_open_container(&array, DBUS_TYPE_DICT_ENTRY, ++ NULL, &dict) || ++ !dbus_message_iter_append_basic(&dict, DBUS_TYPE_STRING, &prop) || ++ get_isregistered(&dict) < 0 || ++ !dbus_message_iter_close_container(&array, &dict)) { ++ goto fail; ++ } ++ ++ prop = "RegisteredStatusNotifierItems"; ++ if (!dbus_message_iter_open_container(&array, DBUS_TYPE_DICT_ENTRY, ++ NULL, &dict) || ++ !dbus_message_iter_append_basic(&dict, DBUS_TYPE_STRING, &prop) || ++ get_registered_items_variant(watcher, &dict) < 0 || ++ !dbus_message_iter_close_container(&array, &dict)) { ++ goto fail; ++ } ++ ++ if (!dbus_message_iter_close_container(&iter, &array) || ++ !dbus_connection_send(conn, reply, NULL)) { ++ goto fail; ++ } ++ ++ dbus_message_unref(reply); ++ return DBUS_HANDLER_RESULT_HANDLED; ++ ++fail: ++ dbus_message_iter_abandon_container_if_open(&array, &dict); ++ dbus_message_iter_abandon_container_if_open(&iter, &array); ++ if (reply) ++ dbus_message_unref(reply); ++ return DBUS_HANDLER_RESULT_NEED_MEMORY; ++} ++ ++static DBusHandlerResult ++respond_introspect(DBusConnection *conn, DBusMessage *msg) ++{ ++ DBusMessage *reply = NULL; ++ ++ reply = dbus_message_new_method_return(msg); ++ if (!reply) ++ goto fail; ++ ++ if (!dbus_message_append_args(reply, DBUS_TYPE_STRING, &snw_xml, ++ DBUS_TYPE_INVALID) || ++ !dbus_connection_send(conn, reply, NULL)) { ++ goto fail; ++ } ++ ++ dbus_message_unref(reply); ++ return DBUS_HANDLER_RESULT_HANDLED; ++ ++fail: ++ if (reply) ++ dbus_message_unref(reply); ++ return DBUS_HANDLER_RESULT_NEED_MEMORY; ++} ++ ++static DBusHandlerResult ++snw_message_handler(DBusConnection *conn, DBusMessage *msg, void *data) ++{ ++ Watcher *watcher = data; ++ ++ if (dbus_message_is_method_call(msg, DBUS_INTERFACE_INTROSPECTABLE, ++ "Introspect")) ++ return respond_introspect(conn, msg); ++ ++ else if (dbus_message_is_method_call(msg, DBUS_INTERFACE_PROPERTIES, ++ "GetAll")) ++ return respond_all_props(watcher, conn, msg); ++ ++ else if (dbus_message_is_method_call(msg, DBUS_INTERFACE_PROPERTIES, ++ "Get")) ++ return respond_get_prop(watcher, conn, msg); ++ ++ else if (dbus_message_is_method_call(msg, SNW_IFACE, ++ "RegisterStatusNotifierItem")) ++ return respond_register_item(watcher, conn, msg); ++ ++ else ++ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; ++} ++ ++static const DBusObjectPathVTable snw_vtable = { .message_function = ++ snw_message_handler }; ++ ++void ++watcher_start(Watcher *watcher, DBusConnection *conn, ++ struct wl_event_loop *loop) ++{ ++ DBusError err = DBUS_ERROR_INIT; ++ int r, flags; ++ ++ wl_list_init(&watcher->items); ++ wl_list_init(&watcher->trays); ++ watcher->conn = conn; ++ watcher->loop = loop; ++ ++ flags = DBUS_NAME_FLAG_REPLACE_EXISTING | DBUS_NAME_FLAG_DO_NOT_QUEUE; ++ r = dbus_bus_request_name(conn, SNW_NAME, ++ flags, NULL); ++ if (r != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) ++ goto fail; ++ ++ if (!dbus_connection_add_filter(conn, filter_bus, watcher, NULL)) { ++ dbus_bus_release_name(conn, SNW_NAME, NULL); ++ goto fail; ++ } ++ ++ dbus_bus_add_match(conn, match_rule, &err); ++ if (dbus_error_is_set(&err)) { ++ dbus_connection_remove_filter(conn, filter_bus, watcher); ++ dbus_bus_release_name(conn, SNW_NAME, NULL); ++ goto fail; ++ } ++ ++ if (!dbus_connection_register_object_path(conn, SNW_OPATH, &snw_vtable, ++ watcher)) { ++ dbus_bus_remove_match(conn, match_rule, NULL); ++ dbus_connection_remove_filter(conn, filter_bus, watcher); ++ dbus_bus_release_name(conn, SNW_NAME, NULL); ++ goto fail; ++ } ++ ++ watcher->running = 1; ++ return; ++ ++fail: ++ fprintf(stderr, "Couldn't start watcher, systray not available\n"); ++ dbus_error_free(&err); ++ return; ++} ++ ++void ++watcher_stop(Watcher *watcher) ++{ ++ dbus_connection_unregister_object_path(watcher->conn, SNW_OPATH); ++ dbus_bus_remove_match(watcher->conn, match_rule, NULL); ++ dbus_connection_remove_filter(watcher->conn, filter_bus, watcher); ++ dbus_bus_release_name(watcher->conn, SNW_NAME, NULL); ++ watcher->running = 0; ++} ++ ++int ++watcher_get_n_items(const Watcher *watcher) ++{ ++ return wl_list_length(&watcher->items); ++} ++ ++void ++watcher_update_trays(Watcher *watcher) ++{ ++ Tray *tray; ++ ++ wl_list_for_each(tray, &watcher->trays, link) ++ tray_update(tray); ++} +diff --git a/systray/watcher.h b/systray/watcher.h +new file mode 100644 +index 0000000..127eb64 +--- /dev/null ++++ b/systray/watcher.h +@@ -0,0 +1,35 @@ ++#ifndef WATCHER_H ++#define WATCHER_H ++ ++#include <dbus/dbus.h> ++#include <wayland-server-core.h> ++#include <wayland-util.h> ++ ++/* ++ * The FDO spec says "org.freedesktop.StatusNotifierWatcher"[1], ++ * but both the client libraries[2,3] actually use "org.kde.StatusNotifierWatcher" ++ * ++ * [1] https://www.freedesktop.org/wiki/Specifications/StatusNotifierItem/ ++ * [2] https://github.com/AyatanaIndicators/libayatana-appindicator-glib ++ * [3] https://invent.kde.org/frameworks/kstatusnotifieritem ++ */ ++#define SNW_NAME "org.kde.StatusNotifierWatcher" ++#define SNW_OPATH "/StatusNotifierWatcher" ++#define SNW_IFACE "org.kde.StatusNotifierWatcher" ++ ++typedef struct { ++ struct wl_list items; ++ struct wl_list trays; ++ struct wl_event_loop *loop; ++ DBusConnection *conn; ++ int running; ++} Watcher; ++ ++void watcher_start (Watcher *watcher, DBusConnection *conn, ++ struct wl_event_loop *loop); ++void watcher_stop (Watcher *watcher); ++ ++int watcher_get_n_items (const Watcher *watcher); ++void watcher_update_trays (Watcher *watcher); ++ ++#endif /* WATCHER_H */ +-- +2.49.0 + |