From 3b4f2dd9894397c44ec0e10d4247e886c4f4c314 Mon Sep 17 00:00:00 2001 From: Nikita Langer Date: Mon, 6 Apr 2026 22:26:07 +0200 Subject: Initial Komm mit :) --- patches/alwayscenter.patch | 25 + patches/autostart-0.8.patch | 141 ++ patches/bar-systray-0.7.patch | 3023 ++++++++++++++++++++++++++++++++ patches/bar.patch | 1271 ++++++++++++++ patches/better-resize-0.7.patch | 110 ++ patches/monitorconfig-0.8.patch | 90 + patches/mouse-trackpad-split-0.7.patch | 96 + patches/movecenter.patch | 82 + patches/movestack-0.8.patch | 87 + patches/numlock-capslock-0.8.patch | 79 + patches/tablet-input-0.8.patch | 378 ++++ patches/touch-input-0.8.patch | 259 +++ patches/unclutter.patch | 192 ++ patches/vanitygaps-0.7.patch | 358 ++++ 14 files changed, 6191 insertions(+) create mode 100644 patches/alwayscenter.patch create mode 100644 patches/autostart-0.8.patch create mode 100644 patches/bar-systray-0.7.patch create mode 100644 patches/bar.patch create mode 100644 patches/better-resize-0.7.patch create mode 100644 patches/monitorconfig-0.8.patch create mode 100644 patches/mouse-trackpad-split-0.7.patch create mode 100644 patches/movecenter.patch create mode 100644 patches/movestack-0.8.patch create mode 100644 patches/numlock-capslock-0.8.patch create mode 100644 patches/tablet-input-0.8.patch create mode 100644 patches/touch-input-0.8.patch create mode 100644 patches/unclutter.patch create mode 100644 patches/vanitygaps-0.7.patch (limited to 'patches') diff --git a/patches/alwayscenter.patch b/patches/alwayscenter.patch new file mode 100644 index 0000000..962745d --- /dev/null +++ b/patches/alwayscenter.patch @@ -0,0 +1,25 @@ +From 48110f0443c8e1ddcd56b6fed5da46535024919c Mon Sep 17 00:00:00 2001 +From: Guido Cella +Date: Tue, 13 Jan 2026 21:17:22 +0100 +Subject: [PATCH] center floating windows + +--- + dwl.c | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/dwl.c b/dwl.c +index 44f3ad9..72714f8 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -2414,6 +2414,8 @@ setmon(Client *c, Monitor *m, uint32_t newtags) + /* Make sure window actually overlaps with the monitor */ + resize(c, c->geom, 0); + c->tags = newtags ? newtags : m->tagset[m->seltags]; /* assign tags of target monitor */ ++ c->prev.x = (m->w.width - c->prev.width) / 2 + m->m.x; ++ c->prev.y = (m->w.height - c->prev.height) / 2 + m->m.y; + setfullscreen(c, c->isfullscreen); /* This will call arrange(c->mon) */ + setfloating(c, c->isfloating); + } +-- +2.52.0 + diff --git a/patches/autostart-0.8.patch b/patches/autostart-0.8.patch new file mode 100644 index 0000000..a1ce9ac --- /dev/null +++ b/patches/autostart-0.8.patch @@ -0,0 +1,141 @@ +From d2e2e61aeb25ad71c2c559994968ba64e0974503 Mon Sep 17 00:00:00 2001 +From: A Frederick Christensen +Date: Fri, 27 Feb 2026 12:23:04 -0600 +Subject: [PATCH] Applied autostart patch + +--- + config.def.h | 6 ++++++ + dwl.c | 58 +++++++++++++++++++++++++++++++++++++++++++++++++--- + 2 files changed, 61 insertions(+), 3 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 8a6eda0..6eb0db4 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -20,6 +20,12 @@ static const float fullscreen_bg[] = {0.0f, 0.0f, 0.0f, 1.0f}; /* You ca + /* logging */ + static int log_level = WLR_ERROR; + ++/* Autostart */ ++static const char *const autostart[] = { ++ "wbg", "/path/to/your/image", NULL, ++ NULL /* terminate */ ++}; ++ + static const Rule rules[] = { + /* app_id title tags mask isfloating monitor */ + { "Gimp_EXAMPLE", NULL, 0, 1, -1 }, /* Start on currently visible tags floating, not tiled */ +diff --git a/dwl.c b/dwl.c +index 44f3ad9..e7b6199 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -246,6 +246,7 @@ static void arrange(Monitor *m); + static void arrangelayer(Monitor *m, struct wl_list *list, + struct wlr_box *usable_area, int exclusive); + static void arrangelayers(Monitor *m); ++static void autostartexec(void); + static void axisnotify(struct wl_listener *listener, void *data); + static void buttonpress(struct wl_listener *listener, void *data); + static void chvt(const Arg *arg); +@@ -449,6 +450,9 @@ static struct wl_listener xwayland_ready = {.notify = xwaylandready}; + static struct wlr_xwayland *xwayland; + #endif + ++static pid_t *autostart_pids; ++static size_t autostart_len; ++ + /* configuration, allows nested code to access above variables */ + #include "config.h" + +@@ -603,6 +607,27 @@ arrangelayers(Monitor *m) + } + } + ++void ++autostartexec(void) { ++ const char *const *p; ++ size_t i = 0; ++ ++ /* count entries */ ++ for (p = autostart; *p; autostart_len++, p++) ++ while (*++p); ++ ++ autostart_pids = calloc(autostart_len, sizeof(pid_t)); ++ for (p = autostart; *p; i++, p++) { ++ if ((autostart_pids[i] = fork()) == 0) { ++ setsid(); ++ execvp(*p, (char *const *)p); ++ die("dwl: execvp %s:", *p); ++ } ++ /* skip arguments */ ++ while (*++p); ++ } ++} ++ + void + axisnotify(struct wl_listener *listener, void *data) + { +@@ -699,12 +724,23 @@ checkidleinhibitor(struct wlr_surface *exclude) + void + cleanup(void) + { ++ size_t i; ++ + cleanuplisteners(); + #ifdef XWAYLAND + wlr_xwayland_destroy(xwayland); + xwayland = NULL; + #endif + wl_display_destroy_clients(dpy); ++ ++ /* kill child processes */ ++ for (i = 0; i < autostart_len; i++) { ++ if (0 < autostart_pids[i]) { ++ kill(autostart_pids[i], SIGTERM); ++ waitpid(autostart_pids[i], NULL, 0); ++ } ++ } ++ + if (child_pid > 0) { + kill(-child_pid, SIGTERM); + waitpid(child_pid, NULL, 0); +@@ -1560,10 +1596,25 @@ gpureset(struct wl_listener *listener, void *data) + void + handlesig(int signo) + { +- if (signo == SIGCHLD) +- while (waitpid(-1, NULL, WNOHANG) > 0); +- else if (signo == SIGINT || signo == SIGTERM) ++ if (signo == SIGCHLD) { ++ pid_t pid, *p, *lim; ++ while ((pid = waitpid(-1, NULL, WNOHANG)) > 0) { ++ if (pid == child_pid) ++ child_pid = -1; ++ if (!(p = autostart_pids)) ++ continue; ++ lim = &p[autostart_len]; ++ ++ for (; p < lim; p++) { ++ if (*p == pid) { ++ *p = -1; ++ break; ++ } ++ } ++ } ++ } else if (signo == SIGINT || signo == SIGTERM) { + quit(NULL); ++ } + } + + void +@@ -2250,6 +2301,7 @@ run(char *startup_cmd) + die("startup: backend_start"); + + /* Now that the socket exists and the backend is started, run the startup command */ ++ autostartexec(); + if (startup_cmd) { + int piperw[2]; + if (pipe(piperw) < 0) +-- +2.52.0 + diff --git a/patches/bar-systray-0.7.patch b/patches/bar-systray-0.7.patch new file mode 100644 index 0000000..4cf63e3 --- /dev/null +++ b/patches/bar-systray-0.7.patch @@ -0,0 +1,3023 @@ +From cf228147250f4616d150fbe5276088c5f9969bba Mon Sep 17 00:00:00 2001 +From: vetu104 +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 ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++ ++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_dispatch 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 ++#include ++ ++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 + #include + #include + #include +@@ -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 ++ ++#include ++#include ++ ++// 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 ++ ++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 ++#include ++ ++#include ++#include ++#include ++#include ++ ++#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 ++#include ++ ++#include ++#include ++ ++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 ++ ++#include ++#include ++#include ++#include ++ ++// 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 ++ ++/* ++ * 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 ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++// 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 ++#include ++ ++/* 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 ++#include ++#include ++ ++#include ++#include ++#include ++#include ++ ++#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 ++#include ++ ++#include ++ ++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 ++#include ++ ++#include ++#include ++#include ++ ++// 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 = ++ "\n" ++ "\n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ "\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 ++#include ++#include ++ ++/* ++ * 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 + diff --git a/patches/bar.patch b/patches/bar.patch new file mode 100644 index 0000000..e098118 --- /dev/null +++ b/patches/bar.patch @@ -0,0 +1,1271 @@ +From d6a2c4e7f0d3108c7a8a1ad6e76a62e854b2f8e1 Mon Sep 17 00:00:00 2001 +From: sewn +Date: Mon, 5 Jan 2026 16:51:14 +0300 +Subject: [PATCH] Implement dwm bar clone + +--- + Makefile | 2 +- + config.def.h | 33 ++-- + drwl.h | 310 +++++++++++++++++++++++++++++++++++ + dwl.c | 444 +++++++++++++++++++++++++++++++++++++++++---------- + 4 files changed, 697 insertions(+), 92 deletions(-) + create mode 100644 drwl.h + +diff --git a/Makefile b/Makefile +index 578194f..279b1c0 100644 +--- a/Makefile ++++ b/Makefile +@@ -12,7 +12,7 @@ DWLDEVCFLAGS = -g -Wpedantic -Wall -Wextra -Wdeclaration-after-statement \ + -Wfloat-conversion + + # CFLAGS / LDFLAGS +-PKGS = wayland-server xkbcommon libinput $(XLIBS) ++PKGS = wayland-server xkbcommon libinput pixman-1 fcft $(XLIBS) + DWLCFLAGS = `$(PKG_CONFIG) --cflags $(PKGS)` $(WLR_INCS) $(DWLCPPFLAGS) $(DWLDEVCFLAGS) $(CFLAGS) + LDLIBS = `$(PKG_CONFIG) --libs $(PKGS)` $(WLR_LIBS) -lm $(LIBS) + +diff --git a/config.def.h b/config.def.h +index 8a6eda0..7da50d2 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -7,15 +7,21 @@ + 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 float rootcolor[] = COLOR(0x222222ff); +-static const float bordercolor[] = COLOR(0x444444ff); +-static const float focuscolor[] = COLOR(0x005577ff); +-static const float urgentcolor[] = COLOR(0xff0000ff); ++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"}; ++static const float rootcolor[] = COLOR(0x000000ff); + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ + static const float fullscreen_bg[] = {0.0f, 0.0f, 0.0f, 1.0f}; /* You can also use glsl colors */ ++static uint32_t colors[][3] = { ++ /* fg bg border */ ++ [SchemeNorm] = { 0xbbbbbbff, 0x222222ff, 0x444444ff }, ++ [SchemeSel] = { 0xeeeeeeff, 0x005577ff, 0x005577ff }, ++ [SchemeUrg] = { 0, 0, 0x770000ff }, ++}; + +-/* tagging - TAGCOUNT must be no greater than 31 */ +-#define TAGCOUNT (9) ++/* tagging */ ++static char *tags[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9" }; + + /* logging */ + static int log_level = WLR_ERROR; +@@ -123,6 +129,7 @@ static const Key keys[] = { + /* modifier key function argument */ + { MODKEY, XKB_KEY_p, spawn, {.v = menucmd} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Return, spawn, {.v = termcmd} }, ++ { MODKEY, XKB_KEY_b, togglebar, {0} }, + { MODKEY, XKB_KEY_j, focusstack, {.i = +1} }, + { MODKEY, XKB_KEY_k, focusstack, {.i = -1} }, + { MODKEY, XKB_KEY_i, incnmaster, {.i = +1} }, +@@ -166,7 +173,15 @@ static const Key keys[] = { + }; + + static const Button buttons[] = { +- { MODKEY, BTN_LEFT, moveresize, {.ui = CurMove} }, +- { MODKEY, BTN_MIDDLE, togglefloating, {0} }, +- { MODKEY, BTN_RIGHT, moveresize, {.ui = CurResize} }, ++ { ClkLtSymbol, 0, BTN_LEFT, setlayout, {.v = &layouts[0]} }, ++ { ClkLtSymbol, 0, BTN_RIGHT, setlayout, {.v = &layouts[2]} }, ++ { ClkTitle, 0, BTN_MIDDLE, zoom, {0} }, ++ { ClkStatus, 0, BTN_MIDDLE, spawn, {.v = termcmd} }, ++ { ClkClient, MODKEY, BTN_LEFT, moveresize, {.ui = CurMove} }, ++ { ClkClient, MODKEY, BTN_MIDDLE, togglefloating, {0} }, ++ { ClkClient, MODKEY, BTN_RIGHT, moveresize, {.ui = CurResize} }, ++ { ClkTagBar, 0, BTN_LEFT, view, {0} }, ++ { ClkTagBar, 0, BTN_RIGHT, toggleview, {0} }, ++ { ClkTagBar, MODKEY, BTN_LEFT, tag, {0} }, ++ { ClkTagBar, MODKEY, BTN_RIGHT, toggletag, {0} }, + }; +diff --git a/drwl.h b/drwl.h +new file mode 100644 +index 0000000..21afd21 +--- /dev/null ++++ b/drwl.h +@@ -0,0 +1,310 @@ ++/* ++ * drwl - https://codeberg.org/sewn/drwl ++ * ++ * Copyright (c) 2023-2025 sewn ++ * Copyright (c) 2024 notchoc ++ * ++ * Permission is hereby granted, free of charge, to any person obtaining ++ * a copy of this software and associated documentation files (the ++ * "Software"), to deal in the Software without restriction, including ++ * without limitation the rights to use, copy, modify, merge, publish, ++ * distribute, sublicense, and/or sell copies of the Software, and to ++ * permit persons to whom the Software is furnished to do so, subject to ++ * the following conditions: ++ * ++ * The above copyright notice and this permission notice shall be ++ * included in all copies or substantial portions of the Software. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, ++ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF ++ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND ++ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE ++ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION ++ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION ++ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ++ * ++ * The UTF-8 Decoder included is from Bjoern Hoehrmann: ++ * Copyright (c) 2008-2010 Bjoern Hoehrmann ++ * See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. ++ */ ++#pragma once ++ ++#include ++#include ++#include ++ ++enum { ColFg, ColBg, ColBorder }; /* colorscheme index */ ++ ++typedef struct fcft_font Fnt; ++typedef pixman_image_t Img; ++ ++typedef struct { ++ Img *image; ++ Fnt *font; ++ uint32_t *scheme; ++} Drwl; ++ ++#define UTF8_ACCEPT 0 ++#define UTF8_REJECT 12 ++#define UTF8_INVALID 0xFFFD ++ ++static const uint8_t utf8d[] = { ++ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, ++ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, ++ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, ++ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, ++ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, ++ 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, ++ 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, ++ 10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8, ++ ++ 0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12, ++ 12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12, ++ 12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12, ++ 12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12, ++ 12,36,12,12,12,12,12,12,12,12,12,12, ++}; ++ ++static inline uint32_t ++utf8decode(uint32_t *state, uint32_t *codep, uint8_t byte) ++{ ++ uint32_t type = utf8d[byte]; ++ ++ *codep = (*state != UTF8_ACCEPT) ? ++ (byte & 0x3fu) | (*codep << 6) : ++ (0xff >> type) & (byte); ++ ++ *state = utf8d[256 + *state + type]; ++ return *state; ++} ++ ++static int ++drwl_init(void) ++{ ++ return fcft_init(FCFT_LOG_COLORIZE_AUTO, 0, FCFT_LOG_CLASS_ERROR); ++} ++ ++static Drwl * ++drwl_create(void) ++{ ++ Drwl *drwl; ++ ++ if (!(drwl = calloc(1, sizeof(Drwl)))) ++ return NULL; ++ ++ return drwl; ++} ++ ++static void ++drwl_setfont(Drwl *drwl, Fnt *font) ++{ ++ if (drwl) ++ drwl->font = font; ++} ++ ++static void ++drwl_setimage(Drwl *drwl, Img *image) ++{ ++ if (drwl) ++ drwl->image = image; ++} ++ ++static Fnt * ++drwl_font_create(Drwl *drwl, size_t count, ++ const char *names[static count], const char *attributes) ++{ ++ Fnt *font = fcft_from_name(count, names, attributes); ++ if (drwl) ++ drwl_setfont(drwl, font); ++ return font; ++} ++ ++static void ++drwl_font_destroy(Fnt *font) ++{ ++ fcft_destroy(font); ++} ++ ++static inline pixman_color_t ++convert_color(uint32_t clr) ++{ ++ return (pixman_color_t){ ++ ((clr >> 24) & 0xFF) * 0x101 * (clr & 0xFF) / 0xFF, ++ ((clr >> 16) & 0xFF) * 0x101 * (clr & 0xFF) / 0xFF, ++ ((clr >> 8) & 0xFF) * 0x101 * (clr & 0xFF) / 0xFF, ++ (clr & 0xFF) * 0x101 ++ }; ++} ++ ++static void ++drwl_setscheme(Drwl *drwl, uint32_t *scm) ++{ ++ if (drwl) ++ drwl->scheme = scm; ++} ++ ++static Img * ++drwl_image_create(Drwl *drwl, unsigned int w, unsigned int h, uint32_t *bits) ++{ ++ Img *image; ++ pixman_region32_t clip; ++ ++ image = pixman_image_create_bits_no_clear( ++ PIXMAN_a8r8g8b8, w, h, bits, w * 4); ++ if (!image) ++ return NULL; ++ pixman_region32_init_rect(&clip, 0, 0, w, h); ++ pixman_image_set_clip_region32(image, &clip); ++ pixman_region32_fini(&clip); ++ ++ if (drwl) ++ drwl_setimage(drwl, image); ++ return image; ++} ++ ++static void ++drwl_rect(Drwl *drwl, ++ int x, int y, unsigned int w, unsigned int h, ++ int filled, int invert) ++{ ++ pixman_color_t clr; ++ if (!drwl || !drwl->scheme || !drwl->image) ++ return; ++ ++ clr = convert_color(drwl->scheme[invert ? ColBg : ColFg]); ++ if (filled) ++ pixman_image_fill_rectangles(PIXMAN_OP_SRC, drwl->image, &clr, 1, ++ &(pixman_rectangle16_t){x, y, w, h}); ++ else ++ pixman_image_fill_rectangles(PIXMAN_OP_SRC, drwl->image, &clr, 4, ++ (pixman_rectangle16_t[4]){ ++ { x, y, w, 1 }, ++ { x, y + h - 1, w, 1 }, ++ { x, y, 1, h }, ++ { x + w - 1, y, 1, h }}); ++} ++ ++static int ++drwl_text(Drwl *drwl, ++ int x, int y, unsigned int w, unsigned int h, ++ unsigned int lpad, const char *text, int invert) ++{ ++ int ty; ++ int render = x || y || w || h; ++ long x_kern; ++ uint32_t cp = 0, last_cp = 0, state; ++ pixman_color_t clr; ++ pixman_image_t *fg_pix = NULL; ++ int noellipsis = 0; ++ const struct fcft_glyph *glyph, *eg = NULL; ++ int fcft_subpixel_mode = FCFT_SUBPIXEL_DEFAULT; ++ ++ if (!drwl || (render && (!drwl->scheme || !w || !drwl->image)) || !text || !drwl->font) ++ return 0; ++ ++ if (!render) { ++ w = invert ? invert : ~invert; ++ } else { ++ clr = convert_color(drwl->scheme[invert ? ColBg : ColFg]); ++ fg_pix = pixman_image_create_solid_fill(&clr); ++ ++ drwl_rect(drwl, x, y, w, h, 1, !invert); ++ ++ x += lpad; ++ w -= lpad; ++ } ++ ++ if (render && (drwl->scheme[ColBg] & 0xFF) != 0xFF) ++ fcft_subpixel_mode = FCFT_SUBPIXEL_NONE; ++ ++ if (render) ++ eg = fcft_rasterize_char_utf32(drwl->font, 0x2026 /* … */, fcft_subpixel_mode); ++ ++ for (const char *p = text, *pp; pp = p, *p; p++) { ++ for (state = UTF8_ACCEPT; *p && ++ utf8decode(&state, &cp, *p) > UTF8_REJECT; p++) ++ ; ++ if (!*p || state == UTF8_REJECT) { ++ cp = UTF8_INVALID; ++ if (p > pp) ++ p--; ++ } ++ ++ glyph = fcft_rasterize_char_utf32(drwl->font, cp, fcft_subpixel_mode); ++ if (!glyph) ++ continue; ++ ++ x_kern = 0; ++ if (last_cp) ++ fcft_kerning(drwl->font, last_cp, cp, &x_kern, NULL); ++ last_cp = cp; ++ ++ ty = y + (h - drwl->font->height) / 2 + drwl->font->ascent; ++ ++ if (render && !noellipsis && x_kern + glyph->advance.x + eg->advance.x > w && ++ *(p + 1) != '\0') { ++ /* cannot fit ellipsis after current codepoint */ ++ if (drwl_text(drwl, 0, 0, 0, 0, 0, pp, 0) + x_kern <= w) { ++ noellipsis = 1; ++ } else { ++ w -= eg->advance.x; ++ pixman_image_composite32( ++ PIXMAN_OP_OVER, fg_pix, eg->pix, drwl->image, 0, 0, 0, 0, ++ x + eg->x, ty - eg->y, eg->width, eg->height); ++ } ++ } ++ ++ if ((x_kern + glyph->advance.x) > w) ++ break; ++ ++ x += x_kern; ++ ++ if (render && pixman_image_get_format(glyph->pix) == PIXMAN_a8r8g8b8) ++ /* pre-rendered glyphs (eg. emoji) */ ++ pixman_image_composite32( ++ PIXMAN_OP_OVER, glyph->pix, NULL, drwl->image, 0, 0, 0, 0, ++ x + glyph->x, ty - glyph->y, glyph->width, glyph->height); ++ else if (render) ++ pixman_image_composite32( ++ PIXMAN_OP_OVER, fg_pix, glyph->pix, drwl->image, 0, 0, 0, 0, ++ x + glyph->x, ty - glyph->y, glyph->width, glyph->height); ++ ++ x += glyph->advance.x; ++ w -= glyph->advance.x; ++ } ++ ++ if (render) ++ pixman_image_unref(fg_pix); ++ ++ return x + (render ? w : 0); ++} ++ ++static unsigned int ++drwl_font_getwidth(Drwl *drwl, const char *text) ++{ ++ if (!drwl || !drwl->font || !text) ++ return 0; ++ return drwl_text(drwl, 0, 0, 0, 0, 0, text, 0); ++} ++ ++static void ++drwl_image_destroy(Img *image) ++{ ++ pixman_image_unref(image); ++} ++ ++static void ++drwl_destroy(Drwl *drwl) ++{ ++ if (drwl->font) ++ drwl_font_destroy(drwl->font); ++ if (drwl->image) ++ drwl_image_destroy(drwl->image); ++ free(drwl); ++} ++ ++static void ++drwl_fini(void) ++{ ++ fcft_fini(); ++} +diff --git a/dwl.c b/dwl.c +index 44f3ad9..7b2abf5 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -1,10 +1,12 @@ + /* + * See LICENSE file for copyright and license details. + */ ++#include + #include + #include + #include + #include ++#include + #include + #include + #include +@@ -59,6 +61,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -69,6 +72,7 @@ + #endif + + #include "util.h" ++#include "drwl.h" + + /* macros */ + #define MAX(A, B) ((A) > (B) ? (A) : (B)) +@@ -77,14 +81,17 @@ + #define VISIBLEON(C, M) ((M) && (C)->mon == (M) && ((C)->tags & (M)->tagset[(M)->seltags])) + #define LENGTH(X) (sizeof X / sizeof X[0]) + #define END(A) ((A) + LENGTH(A)) +-#define TAGMASK ((1u << TAGCOUNT) - 1) ++#define TAGMASK ((1u << LENGTH(tags)) - 1) + #define LISTEN(E, L, H) wl_signal_add((E), ((L)->notify = (H), (L))) + #define LISTEN_STATIC(E, H) do { struct wl_listener *_l = ecalloc(1, sizeof(*_l)); _l->notify = (H); wl_signal_add((E), _l); } while (0) ++#define TEXTW(mon, text) (drwl_font_getwidth(mon->drw, text) + mon->lrpad) + + /* enums */ ++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 */ + + typedef union { + int i; +@@ -94,6 +101,7 @@ typedef union { + } Arg; + + typedef struct { ++ unsigned int click; + unsigned int mod; + unsigned int button; + void (*func)(const Arg *); +@@ -183,10 +191,19 @@ typedef struct { + void (*arrange)(Monitor *); + } Layout; + ++typedef struct { ++ struct wlr_buffer base; ++ struct wl_listener release; ++ bool busy; ++ Img *image; ++ uint32_t data[]; ++} Buffer; ++ + struct Monitor { + struct wl_list link; + struct wlr_output *wlr_output; + struct wlr_scene_output *scene_output; ++ struct wlr_scene_buffer *scene_buffer; /* bar buffer */ + struct wlr_scene_rect *fullscreen_bg; /* See createmon() for info */ + struct wl_listener frame; + struct wl_listener destroy; +@@ -194,6 +211,11 @@ struct Monitor { + struct wl_listener destroy_lock_surface; + struct wlr_session_lock_surface_v1 *lock_surface; + struct wlr_box m; /* monitor area, layout-relative */ ++ struct { ++ int width, height; ++ int real_width, real_height; /* non-scaled */ ++ float scale; ++ } b; /* bar area */ + struct wlr_box w; /* window area, layout-relative */ + struct wl_list layers[4]; /* LayerSurface.link */ + const Layout *lt[2]; +@@ -205,6 +227,9 @@ struct Monitor { + int nmaster; + char ltsymbol[16]; + int asleep; ++ Drwl *drw; ++ Buffer *pool[2]; ++ int lrpad; + }; + + typedef struct { +@@ -247,6 +272,13 @@ static void arrangelayer(Monitor *m, struct wl_list *list, + struct wlr_box *usable_area, int exclusive); + static void arrangelayers(Monitor *m); + static void axisnotify(struct wl_listener *listener, void *data); ++static bool baracceptsinput(struct wlr_scene_buffer *buffer, double *sx, double *sy); ++static void bufdestroy(struct wlr_buffer *buffer); ++static bool bufdatabegin(struct wlr_buffer *buffer, uint32_t flags, ++ void **data, uint32_t *format, size_t *stride); ++static void bufdataend(struct wlr_buffer *buffer); ++static Buffer *bufmon(Monitor *m); ++static void bufrelease(struct wl_listener *listener, void *data); + static void buttonpress(struct wl_listener *listener, void *data); + static void chvt(const Arg *arg); + static void checkidleinhibitor(struct wlr_surface *exclude); +@@ -282,6 +314,8 @@ static void destroypointerconstraint(struct wl_listener *listener, void *data); + static void destroysessionlock(struct wl_listener *listener, void *data); + static void destroykeyboardgroup(struct wl_listener *listener, void *data); + static Monitor *dirtomon(enum wlr_direction dir); ++static void drawbar(Monitor *m); ++static void drawbars(void); + static void focusclient(Client *c, int lift); + static void focusmon(const Arg *arg); + static void focusstack(const Arg *arg); +@@ -310,7 +344,6 @@ static void outputmgrapplyortest(struct wlr_output_configuration_v1 *config, int + static void outputmgrtest(struct wl_listener *listener, void *data); + static void pointerfocus(Client *c, struct wlr_surface *surface, + double sx, double sy, uint32_t time); +-static void printstatus(void); + static void powermgrsetmode(struct wl_listener *listener, void *data); + static void quit(const Arg *arg); + static void rendermon(struct wl_listener *listener, void *data); +@@ -331,9 +364,11 @@ static void setsel(struct wl_listener *listener, void *data); + static void setup(void); + static void spawn(const Arg *arg); + static void startdrag(struct wl_listener *listener, void *data); ++static int statusin(int fd, unsigned int mask, void *data); + static void tag(const Arg *arg); + static void tagmon(const Arg *arg); + static void tile(Monitor *m); ++static void togglebar(const Arg *arg); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); + static void toggletag(const Arg *arg); +@@ -342,6 +377,7 @@ 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); + static void updatemons(struct wl_listener *listener, void *data); ++static void updatebar(Monitor *m); + static void updatetitle(struct wl_listener *listener, void *data); + static void urgent(struct wl_listener *listener, void *data); + static void view(const Arg *arg); +@@ -406,6 +442,15 @@ static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; + ++static char stext[256]; ++static struct wl_event_source *status_event_source; ++ ++static const struct wlr_buffer_impl buffer_impl = { ++ .destroy = bufdestroy, ++ .begin_data_ptr_access = bufdatabegin, ++ .end_data_ptr_access = bufdataend, ++}; ++ + /* global event handlers */ + static struct wl_listener cursor_axis = {.notify = axisnotify}; + static struct wl_listener cursor_button = {.notify = buttonpress}; +@@ -521,7 +566,7 @@ arrange(Monitor *m) + wlr_scene_node_set_enabled(&m->fullscreen_bg->node, + (c = focustop(m)) && c->isfullscreen); + +- strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, LENGTH(m->ltsymbol)); ++ strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, sizeof(m->ltsymbol)); + + /* We move all clients (except fullscreen and unmanaged) to LyrTile while + * in floating layout to avoid "real" floating clients be always on top */ +@@ -576,6 +621,11 @@ arrangelayers(Monitor *m) + if (!m->wlr_output->enabled) + return; + ++ if (m->scene_buffer->node.enabled) { ++ usable_area.height -= m->b.real_height; ++ usable_area.y += topbar ? m->b.real_height : 0; ++ } ++ + /* Arrange exclusive surfaces from top->bottom */ + for (i = 3; i >= 0; i--) + arrangelayer(m, &m->layers[i], &usable_area, 1); +@@ -618,17 +668,102 @@ axisnotify(struct wl_listener *listener, void *data) + event->delta_discrete, event->source, event->relative_direction); + } + ++bool ++baracceptsinput(struct wlr_scene_buffer *buffer, double *sx, double *sy) ++{ ++ return true; ++} ++ ++void ++bufdestroy(struct wlr_buffer *wlr_buffer) ++{ ++ Buffer *buf = wl_container_of(wlr_buffer, buf, base); ++ if (buf->busy) ++ wl_list_remove(&buf->release.link); ++ drwl_image_destroy(buf->image); ++ free(buf); ++} ++ ++bool ++bufdatabegin(struct wlr_buffer *wlr_buffer, uint32_t flags, ++ void **data, uint32_t *format, size_t *stride) ++{ ++ Buffer *buf = wl_container_of(wlr_buffer, buf, base); ++ ++ if (flags & WLR_BUFFER_DATA_PTR_ACCESS_WRITE) return false; ++ ++ *data = buf->data; ++ *stride = wlr_buffer->width * 4; ++ *format = DRM_FORMAT_ARGB8888; ++ ++ return true; ++} ++ ++void ++bufdataend(struct wlr_buffer *wlr_buffer) ++{ ++} ++ ++Buffer * ++bufmon(Monitor *m) ++{ ++ size_t i; ++ Buffer *buf = NULL; ++ ++ for (i = 0; i < LENGTH(m->pool); i++) { ++ if (m->pool[i]) { ++ if (m->pool[i]->busy) ++ continue; ++ buf = m->pool[i]; ++ break; ++ } ++ ++ buf = ecalloc(1, sizeof(Buffer) + (m->b.width * 4 * m->b.height)); ++ buf->image = drwl_image_create(NULL, m->b.width, m->b.height, buf->data); ++ wlr_buffer_init(&buf->base, &buffer_impl, m->b.width, m->b.height); ++ m->pool[i] = buf; ++ break; ++ } ++ if (!buf) ++ return NULL; ++ ++ buf->busy = true; ++ LISTEN(&buf->base.events.release, &buf->release, bufrelease); ++ wlr_buffer_lock(&buf->base); ++ drwl_setimage(m->drw, buf->image); ++ return buf; ++} ++ ++void ++bufrelease(struct wl_listener *listener, void *data) ++{ ++ Buffer *buf = wl_container_of(listener, buf, release); ++ buf->busy = false; ++ wl_list_remove(&buf->release.link); ++} ++ + void + buttonpress(struct wl_listener *listener, void *data) + { ++ unsigned int i = 0, x = 0; ++ double cx; ++ unsigned int click; + struct wlr_pointer_button_event *event = data; + struct wlr_keyboard *keyboard; ++ struct wlr_scene_node *node; ++ struct wlr_scene_buffer *buffer; + uint32_t mods; ++ Arg arg = {0}; + Client *c; + const Button *b; + + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); + ++ click = ClkRoot; ++ xytonode(cursor->x, cursor->y, NULL, &c, NULL, NULL, NULL); ++ if (c) ++ click = ClkClient; ++ + switch (event->state) { + case WL_POINTER_BUTTON_STATE_PRESSED: + cursor_mode = CurPressed; +@@ -636,17 +771,34 @@ buttonpress(struct wl_listener *listener, void *data) + if (locked) + break; + ++ if (!c && !exclusive_focus && ++ (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; ++ do ++ x += TEXTW(selmon, tags[i]); ++ while (cx >= x && ++i < LENGTH(tags)); ++ if (i < LENGTH(tags)) { ++ click = ClkTagBar; ++ 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)) { ++ click = ClkStatus; ++ } else ++ click = ClkTitle; ++ } ++ + /* Change focus if the button was _pressed_ over a client */ + xytonode(cursor->x, cursor->y, NULL, &c, NULL, NULL, NULL); +- if (c && (!client_is_unmanaged(c) || client_wants_focus(c))) ++ if (click == ClkClient && (!client_is_unmanaged(c) || client_wants_focus(c))) + focusclient(c, 1); + + keyboard = wlr_seat_get_keyboard(seat); + 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 && b->func) { +- b->func(&b->arg); ++ 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); + return; + } + } +@@ -721,6 +873,8 @@ cleanup(void) + /* Destroy after the wayland display (when the monitors are already destroyed) + to avoid destroying them with an invalid scene output. */ + wlr_scene_node_destroy(&scene->tree.node); ++ ++ drwl_fini(); + } + + void +@@ -736,6 +890,12 @@ cleanupmon(struct wl_listener *listener, void *data) + wlr_layer_surface_v1_destroy(l->layer_surface); + } + ++ for (i = 0; i < LENGTH(m->pool); i++) ++ wlr_buffer_drop(&m->pool[i]->base); ++ ++ drwl_setimage(m->drw, NULL); ++ drwl_destroy(m->drw); ++ + wl_list_remove(&m->destroy.link); + wl_list_remove(&m->frame.link); + wl_list_remove(&m->link); +@@ -748,6 +908,7 @@ cleanupmon(struct wl_listener *listener, void *data) + + closemon(m); + wlr_scene_node_destroy(&m->fullscreen_bg->node); ++ wlr_scene_node_destroy(&m->scene_buffer->node); + free(m); + } + +@@ -814,7 +975,7 @@ closemon(Monitor *m) + setmon(c, selmon, c->tags); + } + focusclient(focustop(selmon), 1); +- printstatus(); ++ drawbars(); + } + + void +@@ -1066,7 +1227,7 @@ createmon(struct wl_listener *listener, void *data) + m->nmaster = r->nmaster; + m->lt[0] = r->lt; + m->lt[1] = &layouts[LENGTH(layouts) > 1 && r->lt != &layouts[1]]; +- strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, LENGTH(m->ltsymbol)); ++ strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, sizeof(m->ltsymbol)); + wlr_output_state_set_scale(&state, r->scale); + wlr_output_state_set_transform(&state, r->rr); + break; +@@ -1088,8 +1249,15 @@ createmon(struct wl_listener *listener, void *data) + wlr_output_commit_state(wlr_output, &state); + wlr_output_state_finish(&state); + ++ if (!(m->drw = drwl_create())) ++ die("failed to create drwl context"); ++ ++ m->scene_buffer = wlr_scene_buffer_create(layers[LyrBottom], NULL); ++ m->scene_buffer->point_accepts_input = baracceptsinput; ++ updatebar(m); ++ + wl_list_insert(&mons, &m->link); +- printstatus(); ++ drawbars(); + + /* The xdg-protocol specifies: + * +@@ -1399,6 +1567,80 @@ dirtomon(enum wlr_direction dir) + return selmon; + } + ++void ++drawbar(Monitor *m) ++{ ++ int x, w, tw = 0; ++ int boxs = m->drw->font->height / 9; ++ int boxw = m->drw->font->height / 6 + 2; ++ uint32_t i, occ = 0, urg = 0; ++ Client *c; ++ Buffer *buf; ++ ++ if (!m->scene_buffer->node.enabled) ++ return; ++ if (!(buf = bufmon(m))) ++ return; ++ ++ /* 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); ++ } ++ ++ wl_list_for_each(c, &clients, link) { ++ if (c->mon != m) ++ continue; ++ occ |= c->tags; ++ if (c->isurgent) ++ urg |= c->tags; ++ } ++ x = 0; ++ c = focustop(m); ++ for (i = 0; i < LENGTH(tags); i++) { ++ w = TEXTW(m, tags[i]); ++ drwl_setscheme(m->drw, colors[m->tagset[m->seltags] & 1 << i ? SchemeSel : SchemeNorm]); ++ drwl_text(m->drw, x, 0, w, m->b.height, m->lrpad / 2, tags[i], urg & 1 << i); ++ if (occ & 1 << i) ++ drwl_rect(m->drw, x + boxs, boxs, boxw, boxw, ++ m == selmon && c && c->tags & 1 << i, ++ urg & 1 << i); ++ x += w; ++ } ++ w = TEXTW(m, m->ltsymbol); ++ 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 (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); ++ if (c && c->isfloating) ++ drwl_rect(m->drw, x + boxs, boxs, boxw, boxw, 0, 0); ++ } else { ++ drwl_setscheme(m->drw, colors[SchemeNorm]); ++ drwl_rect(m->drw, x, 0, w, m->b.height, 1, 1); ++ } ++ } ++ ++ 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, ++ m->m.y + (topbar ? 0 : m->m.height - m->b.real_height)); ++ wlr_scene_buffer_set_buffer(m->scene_buffer, &buf->base); ++ wlr_buffer_unlock(&buf->base); ++} ++ ++void ++drawbars(void) ++{ ++ Monitor *m = NULL; ++ ++ wl_list_for_each(m, &mons, link) ++ drawbar(m); ++} ++ + void + focusclient(Client *c, int lift) + { +@@ -1433,13 +1675,13 @@ focusclient(Client *c, int lift) + /* Don't change border color if there is an exclusive focus or we are + * handling a drag operation */ + if (!exclusive_focus && !seat->drag) +- client_set_border_color(c, focuscolor); ++ client_set_border_color(c, (float[])COLOR(colors[SchemeSel][ColBorder])); + } + + /* Deactivate old client if focus is changing */ + if (old && (!c || client_surface(c) != old)) { + /* If an overlay is focused, don't focus or activate the client, +- * but only update its position in fstack to render its border with focuscolor ++ * but only update its position in fstack to render its border with its color + * and focus it after the overlay is closed. */ + if (old_client_type == LayerShell && wlr_scene_node_coords( + &old_l->scene->node, &unused_lx, &unused_ly) +@@ -1450,12 +1692,11 @@ focusclient(Client *c, int lift) + /* Don't deactivate old client if the new one wants focus, as this causes issues with winecfg + * and probably other clients */ + } else if (old_c && !client_is_unmanaged(old_c) && (!c || !client_wants_focus(c))) { +- client_set_border_color(old_c, bordercolor); +- ++ client_set_border_color(old_c, (float[])COLOR(colors[SchemeNorm][ColBorder])); + client_activate_surface(old, 0); + } + } +- printstatus(); ++ drawbars(); + + if (!c) { + /* With no client, all we have left is to clear focus */ +@@ -1769,7 +2010,7 @@ mapnotify(struct wl_listener *listener, void *data) + + for (i = 0; i < 4; i++) { + c->border[i] = wlr_scene_rect_create(c->scene, 0, 0, +- c->isurgent ? urgentcolor : bordercolor); ++ (float[])COLOR(colors[c->isurgent ? SchemeUrg : SchemeNorm][ColBorder])); + c->border[i]->node.data = c; + } + +@@ -1792,7 +2033,7 @@ mapnotify(struct wl_listener *listener, void *data) + } else { + applyrules(c); + } +- printstatus(); ++ drawbars(); + + unset_fullscreen: + m = c->mon ? c->mon : xytomon(c->geom.x, c->geom.y); +@@ -2085,44 +2326,6 @@ pointerfocus(Client *c, struct wlr_surface *surface, double sx, double sy, + wlr_seat_pointer_notify_motion(seat, time, sx, sy); + } + +-void +-printstatus(void) +-{ +- Monitor *m = NULL; +- Client *c; +- uint32_t occ, urg, sel; +- +- wl_list_for_each(m, &mons, link) { +- occ = urg = 0; +- wl_list_for_each(c, &clients, link) { +- if (c->mon != m) +- continue; +- occ |= c->tags; +- if (c->isurgent) +- urg |= c->tags; +- } +- if ((c = focustop(m))) { +- printf("%s title %s\n", m->wlr_output->name, client_get_title(c)); +- printf("%s appid %s\n", m->wlr_output->name, client_get_appid(c)); +- printf("%s fullscreen %d\n", m->wlr_output->name, c->isfullscreen); +- printf("%s floating %d\n", m->wlr_output->name, c->isfloating); +- sel = c->tags; +- } else { +- printf("%s title \n", m->wlr_output->name); +- printf("%s appid \n", m->wlr_output->name); +- printf("%s fullscreen \n", m->wlr_output->name); +- printf("%s floating \n", m->wlr_output->name); +- sel = 0; +- } +- +- printf("%s selmon %u\n", m->wlr_output->name, m == selmon); +- printf("%s tags %"PRIu32" %"PRIu32" %"PRIu32" %"PRIu32"\n", +- m->wlr_output->name, occ, m->tagset[m->seltags], sel, urg); +- printf("%s layout %s\n", m->wlr_output->name, m->ltsymbol); +- } +- fflush(stdout); +-} +- + void + powermgrsetmode(struct wl_listener *listener, void *data) + { +@@ -2251,22 +2454,14 @@ run(char *startup_cmd) + + /* Now that the socket exists and the backend is started, run the startup command */ + if (startup_cmd) { +- int piperw[2]; +- if (pipe(piperw) < 0) +- die("startup: pipe:"); + if ((child_pid = fork()) < 0) + die("startup: fork:"); + if (child_pid == 0) { ++ close(STDIN_FILENO); + setsid(); +- dup2(piperw[0], STDIN_FILENO); +- close(piperw[0]); +- close(piperw[1]); + execl("/bin/sh", "/bin/sh", "-c", startup_cmd, NULL); + die("startup: execl:"); + } +- dup2(piperw[1], STDOUT_FILENO); +- close(piperw[1]); +- close(piperw[0]); + } + + /* Mark stdout as non-blocking to avoid the startup script +@@ -2276,7 +2471,7 @@ run(char *startup_cmd) + if (fd_set_nonblock(STDOUT_FILENO) < 0) + close(STDOUT_FILENO); + +- printstatus(); ++ drawbars(); + + /* At this point the outputs are initialized, choose initial selmon based on + * cursor position, and set default cursor image */ +@@ -2342,7 +2537,7 @@ setfloating(Client *c, int floating) + (p && p->isfullscreen) ? LyrFS + : c->isfloating ? LyrFloat : LyrTile]); + arrange(c->mon); +- printstatus(); ++ drawbars(); + } + + void +@@ -2365,7 +2560,7 @@ setfullscreen(Client *c, int fullscreen) + resize(c, c->prev, 0); + } + arrange(c->mon); +- printstatus(); ++ drawbars(); + } + + void +@@ -2377,9 +2572,9 @@ setlayout(const Arg *arg) + selmon->sellt ^= 1; + if (arg && arg->v) + selmon->lt[selmon->sellt] = (Layout *)arg->v; +- strncpy(selmon->ltsymbol, selmon->lt[selmon->sellt]->symbol, LENGTH(selmon->ltsymbol)); ++ strncpy(selmon->ltsymbol, selmon->lt[selmon->sellt]->symbol, sizeof(selmon->ltsymbol)); + arrange(selmon); +- printstatus(); ++ drawbar(selmon); + } + + /* arg > 1.0 will set mfact absolutely */ +@@ -2452,6 +2647,7 @@ setup(void) + for (i = 0; i < (int)LENGTH(sig); i++) + sigaction(sig[i], &sa, NULL); + ++ + wlr_log_init(log_level, NULL); + + /* The Wayland display is managed by libwayland. It handles accepting +@@ -2646,6 +2842,11 @@ setup(void) + wl_signal_add(&output_mgr->events.apply, &output_mgr_apply); + wl_signal_add(&output_mgr->events.test, &output_mgr_test); + ++ drwl_init(); ++ ++ status_event_source = wl_event_loop_add_fd(wl_display_get_event_loop(dpy), ++ STDIN_FILENO, WL_EVENT_READABLE, statusin, NULL); ++ + /* 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 */ +@@ -2670,6 +2871,8 @@ void + spawn(const Arg *arg) + { + if (fork() == 0) { ++ close(STDIN_FILENO); ++ open("/dev/null", O_RDWR); + dup2(STDERR_FILENO, STDOUT_FILENO); + setsid(); + execvp(((char **)arg->v)[0], (char **)arg->v); +@@ -2688,6 +2891,30 @@ startdrag(struct wl_listener *listener, void *data) + LISTEN_STATIC(&drag->icon->events.destroy, destroydragicon); + } + ++int ++statusin(int fd, unsigned int mask, void *data) ++{ ++ char status[256]; ++ ssize_t n; ++ ++ if (mask & WL_EVENT_ERROR) ++ die("status in event error"); ++ if (mask & WL_EVENT_HANGUP) ++ wl_event_source_remove(status_event_source); ++ ++ n = read(fd, status, sizeof(status) - 1); ++ if (n < 0 && errno != EWOULDBLOCK) ++ die("read:"); ++ ++ status[n] = '\0'; ++ status[strcspn(status, "\n")] = '\0'; ++ ++ strncpy(stext, status, sizeof(stext)); ++ drawbars(); ++ ++ return 0; ++} ++ + void + tag(const Arg *arg) + { +@@ -2698,7 +2925,7 @@ tag(const Arg *arg) + sel->tags = arg->ui & TAGMASK; + focusclient(focustop(selmon), 1); + arrange(selmon); +- printstatus(); ++ drawbars(); + } + + void +@@ -2743,6 +2970,15 @@ tile(Monitor *m) + } + } + ++void ++togglebar(const Arg *arg) ++{ ++ wlr_scene_node_set_enabled(&selmon->scene_buffer->node, ++ !selmon->scene_buffer->node.enabled); ++ arrangelayers(selmon); ++ drawbars(); ++} ++ + void + togglefloating(const Arg *arg) + { +@@ -2771,7 +3007,7 @@ toggletag(const Arg *arg) + sel->tags = newtags; + focusclient(focustop(selmon), 1); + arrange(selmon); +- printstatus(); ++ drawbars(); + } + + void +@@ -2784,7 +3020,7 @@ toggleview(const Arg *arg) + selmon->tagset[selmon->seltags] = newtagset; + focusclient(focustop(selmon), 1); + arrange(selmon); +- printstatus(); ++ drawbars(); + } + + void +@@ -2832,7 +3068,7 @@ unmapnotify(struct wl_listener *listener, void *data) + } + + wlr_scene_node_destroy(&c->scene->node); +- printstatus(); ++ drawbars(); + motionnotify(0, NULL, 0, 0, 0, 0); + } + +@@ -2932,6 +3168,13 @@ updatemons(struct wl_listener *listener, void *data) + } + } + ++ if (stext[0] == '\0') ++ strncpy(stext, "dwl-"VERSION, sizeof(stext)); ++ wl_list_for_each(m, &mons, link) { ++ updatebar(m); ++ drawbar(m); ++ } ++ + /* FIXME: figure out why the cursor image is at 0,0 after turning all + * the monitors on. + * Move the cursor image where it used to be. It does not generate a +@@ -2942,12 +3185,45 @@ updatemons(struct wl_listener *listener, void *data) + wlr_output_manager_v1_set_configuration(output_mgr, config); + } + ++void ++updatebar(Monitor *m) ++{ ++ size_t i; ++ int rw, rh; ++ char fontattrs[12]; ++ ++ wlr_output_transformed_resolution(m->wlr_output, &rw, &rh); ++ m->b.width = rw; ++ m->b.real_width = (int)((float)m->b.width / m->wlr_output->scale); ++ ++ wlr_scene_node_set_enabled(&m->scene_buffer->node, m->wlr_output->enabled ? showbar : 0); ++ ++ for (i = 0; i < LENGTH(m->pool); i++) ++ if (m->pool[i]) { ++ wlr_buffer_drop(&m->pool[i]->base); ++ m->pool[i] = NULL; ++ } ++ ++ if (m->b.scale == m->wlr_output->scale && m->drw) ++ return; ++ ++ drwl_font_destroy(m->drw->font); ++ snprintf(fontattrs, sizeof(fontattrs), "dpi=%.2f", 96. * m->wlr_output->scale); ++ if (!(drwl_font_create(m->drw, LENGTH(fonts), fonts, fontattrs))) ++ die("Could not load font"); ++ ++ m->b.scale = m->wlr_output->scale; ++ 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); ++} ++ + void + updatetitle(struct wl_listener *listener, void *data) + { + Client *c = wl_container_of(listener, c, set_title); + if (c == focustop(c->mon)) +- printstatus(); ++ drawbars(); + } + + void +@@ -2960,10 +3236,10 @@ urgent(struct wl_listener *listener, void *data) + return; + + c->isurgent = 1; +- printstatus(); ++ drawbars(); + + if (client_surface(c)->mapped) +- client_set_border_color(c, urgentcolor); ++ client_set_border_color(c, (float[])COLOR(colors[SchemeUrg][ColBorder])); + } + + void +@@ -2976,7 +3252,7 @@ view(const Arg *arg) + selmon->tagset[selmon->seltags] = arg->ui & TAGMASK; + focusclient(focustop(selmon), 1); + arrange(selmon); +- printstatus(); ++ drawbars(); + } + + void +@@ -3017,6 +3293,7 @@ xytonode(double x, double y, struct wlr_surface **psurface, + { + struct wlr_scene_node *node, *pnode; + struct wlr_surface *surface = NULL; ++ struct wlr_scene_surface *scene_surface = NULL; + Client *c = NULL; + LayerSurface *l = NULL; + int layer; +@@ -3025,9 +3302,12 @@ xytonode(double x, double y, struct wlr_surface **psurface, + if (!(node = wlr_scene_node_at(&layers[layer]->node, x, y, nx, ny))) + continue; + +- if (node->type == WLR_SCENE_NODE_BUFFER) +- surface = wlr_scene_surface_try_from_buffer( +- wlr_scene_buffer_from_node(node))->surface; ++ if (node->type == WLR_SCENE_NODE_BUFFER) { ++ scene_surface = wlr_scene_surface_try_from_buffer( ++ wlr_scene_buffer_from_node(node)); ++ if (!scene_surface) continue; ++ surface = scene_surface->surface; ++ } + /* Walk the tree to find a node that knows the client */ + for (pnode = node; pnode && !c; pnode = &pnode->parent->node) + c = pnode->data; +@@ -3160,10 +3440,10 @@ sethints(struct wl_listener *listener, void *data) + return; + + c->isurgent = xcb_icccm_wm_hints_get_urgency(c->surface.xwayland->hints); +- printstatus(); ++ drawbars(); + + if (c->isurgent && surface && surface->mapped) +- client_set_border_color(c, urgentcolor); ++ client_set_border_color(c, (float[])COLOR(colors[SchemeUrg][ColBorder])); + } + + void +-- +2.52.0 + diff --git a/patches/better-resize-0.7.patch b/patches/better-resize-0.7.patch new file mode 100644 index 0000000..633f181 --- /dev/null +++ b/patches/better-resize-0.7.patch @@ -0,0 +1,110 @@ +From 5fab55803d009d400f6c3fcbe6a0fc807431bbe7 Mon Sep 17 00:00:00 2001 +From: mmistika +Date: Thu, 17 Jul 2025 11:59:18 +0200 +Subject: [PATCH] Add configurable window resize + +Signed-off-by: mmistika +--- + config.def.h | 12 ++++++++++++ + dwl.c | 48 ++++++++++++++++++++++++++++++++++++++++-------- + 2 files changed, 52 insertions(+), 8 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171..e404549 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -20,6 +20,18 @@ static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You ca + /* logging */ + static int log_level = WLR_ERROR; + ++/* window resizing */ ++/* resize_corner: ++ * 0: top-left ++ * 1: top-right ++ * 2: bottom-left ++ * 3: bottom-right ++ * 4: closest to the cursor ++ */ ++static const int resize_corner = 4; ++static const int warp_cursor = 1; /* 1: warp to corner, 0: don’t warp */ ++static const int lock_cursor = 0; /* 1: lock cursor, 0: don't lock */ ++ + /* NOTE: ALWAYS keep a rule declared even if you don't use rules (e.g leave at least one example) */ + static const Rule rules[] = { + /* app_id title tags mask isfloating monitor */ +diff --git a/dwl.c b/dwl.c +index c717c1d..0d56b49 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -407,6 +407,7 @@ static KeyboardGroup *kb_group; + static unsigned int cursor_mode; + static Client *grabc; + static int grabcx, grabcy; /* client-relative */ ++static int rzcorner; + + static struct wlr_output_layout *output_layout; + static struct wlr_box sgeom; +@@ -1873,8 +1874,27 @@ motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double d + .width = grabc->geom.width, .height = grabc->geom.height}, 1); + return; + } else if (cursor_mode == CurResize) { +- resize(grabc, (struct wlr_box){.x = grabc->geom.x, .y = grabc->geom.y, +- .width = (int)round(cursor->x) - grabc->geom.x, .height = (int)round(cursor->y) - grabc->geom.y}, 1); ++ int cdx = (int)round(cursor->x) - grabcx; ++ int cdy = (int)round(cursor->y) - grabcy; ++ ++ cdx = !(rzcorner & 1) && grabc->geom.width - 2 * (int)grabc->bw - cdx < 1 ? 0 : cdx; ++ cdy = !(rzcorner & 2) && grabc->geom.height - 2 * (int)grabc->bw - cdy < 1 ? 0 : cdy; ++ ++ const struct wlr_box box = { ++ .x = grabc->geom.x + (rzcorner & 1 ? 0 : cdx), ++ .y = grabc->geom.y + (rzcorner & 2 ? 0 : cdy), ++ .width = grabc->geom.width + (rzcorner & 1 ? cdx : -cdx), ++ .height = grabc->geom.height + (rzcorner & 2 ? cdy : -cdy) ++ }; ++ resize(grabc, box, 1); ++ ++ if (!lock_cursor) { ++ grabcx += cdx; ++ grabcy += cdy; ++ } else { ++ wlr_cursor_warp_closest(cursor, NULL, grabcx, grabcy); ++ } ++ + return; + } + +@@ -1920,12 +1940,24 @@ moveresize(const Arg *arg) + wlr_cursor_set_xcursor(cursor, cursor_mgr, "fleur"); + break; + case CurResize: +- /* Doesn't work for X11 output - the next absolute motion event +- * returns the cursor to where it started */ +- wlr_cursor_warp_closest(cursor, NULL, +- grabc->geom.x + grabc->geom.width, +- grabc->geom.y + grabc->geom.height); +- wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize"); ++ const char *cursors[] = { "nw-resize", "ne-resize", "sw-resize", "se-resize" }; ++ ++ rzcorner = resize_corner; ++ grabcx = (int)round(cursor->x); ++ grabcy = (int)round(cursor->y); ++ ++ if (rzcorner == 4) ++ /* identify the closest corner index */ ++ rzcorner = (grabcx - grabc->geom.x < grabc->geom.x + grabc->geom.width - grabcx ? 0 : 1) ++ + (grabcy - grabc->geom.y < grabc->geom.y + grabc->geom.height - grabcy ? 0 : 2); ++ ++ if (warp_cursor) { ++ grabcx = rzcorner & 1 ? grabc->geom.x + grabc->geom.width : grabc->geom.x; ++ grabcy = rzcorner & 2 ? grabc->geom.y + grabc->geom.height : grabc->geom.y; ++ wlr_cursor_warp_closest(cursor, NULL, grabcx, grabcy); ++ } ++ ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, cursors[rzcorner]); + break; + } + } +-- +2.50.1 + diff --git a/patches/monitorconfig-0.8.patch b/patches/monitorconfig-0.8.patch new file mode 100644 index 0000000..9c6daf2 --- /dev/null +++ b/patches/monitorconfig-0.8.patch @@ -0,0 +1,90 @@ +From 72137ab7f63e251f2e1c9557e236fd4e9c4efa38 Mon Sep 17 00:00:00 2001 +From: A Frederick Christensen +Date: Tue, 24 Feb 2026 23:12:17 -0600 +Subject: [PATCH] monitorconfig: update for dwl 0.8 + +--- + config.def.h | 12 ++++++++---- + dwl.c | 25 +++++++++++++++++++------ + 2 files changed, 27 insertions(+), 10 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 8a6eda0..597e3bb 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -40,10 +40,14 @@ static const Layout layouts[] = { + * WARNING: negative values other than (-1, -1) cause problems with Xwayland clients due to + * https://gitlab.freedesktop.org/xorg/xserver/-/issues/899 */ + static const MonitorRule monrules[] = { +- /* name mfact nmaster scale layout rotate/reflect x y +- * example of a HiDPI laptop monitor: +- { "eDP-1", 0.5f, 1, 2, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL, -1, -1 }, */ +- { NULL, 0.55f, 1, 1, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL, -1, -1 }, ++ /* name mfact nmaster scale layout rotate/reflect x y resx resy rate mode adaptive */ ++ /*{"eDP-1", 0.5f, 1, 2, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL, 0, 0, 0, 0, 120.000f, 1, 1}, /* example of a HiDPI laptop monitor at 120Hz: */ ++ /* ++ * mode lets the user decide how dwl should implement the modes: ++ * -1 sets a custom mode following the user's choice ++ * All other numbers set the mode at the index n; 0 is the standard mode; see wlr-randr ++ */ ++ { NULL, 0.55f, 1, 1, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL, -1, -1, 0, 0, 0.0f, 0, 1}, + /* default monitor rule: can be changed but cannot be eliminated; at least one monitor rule must exist */ + }; + +diff --git a/dwl.c b/dwl.c +index 44f3ad9..987b5a6 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -215,6 +215,11 @@ typedef struct { + const Layout *lt; + enum wl_output_transform rr; + int x, y; ++ int resx; ++ int resy; ++ float rate; ++ int mode; ++ int adaptive; + } MonitorRule; + + typedef struct { +@@ -1041,6 +1046,7 @@ createmon(struct wl_listener *listener, void *data) + /* This event is raised by the backend when a new output (aka a display or + * monitor) becomes available. */ + struct wlr_output *wlr_output = data; ++ struct wlr_output_mode *mode = wl_container_of(wlr_output->modes.next, mode, link); + const MonitorRule *r; + size_t i; + struct wlr_output_state state; +@@ -1069,16 +1075,23 @@ createmon(struct wl_listener *listener, void *data) + strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, LENGTH(m->ltsymbol)); + wlr_output_state_set_scale(&state, r->scale); + wlr_output_state_set_transform(&state, r->rr); ++ ++ wlr_output_state_set_adaptive_sync_enabled(&state, r->adaptive); ++ ++ if(r->mode == -1) ++ wlr_output_state_set_custom_mode(&state, r->resx, r->resy, ++ (int) (r->rate > 0 ? r->rate * 1000 : 0)); ++ else if (!wl_list_empty(&wlr_output->modes)) { ++ for (int j = 0; j < r->mode; j++) { ++ mode = wl_container_of(mode->link.next, mode, link); ++ } ++ wlr_output_state_set_mode(&state, mode); ++ } ++ + break; + } + } + +- /* The mode is a tuple of (width, height, refresh rate), and each +- * monitor supports only a specific set of modes. We just pick the +- * monitor's preferred mode; a more sophisticated compositor would let +- * the user configure it. */ +- wlr_output_state_set_mode(&state, wlr_output_preferred_mode(wlr_output)); +- + /* Set up event listeners */ + LISTEN(&wlr_output->events.frame, &m->frame, rendermon); + LISTEN(&wlr_output->events.destroy, &m->destroy, cleanupmon); +-- +2.52.0 + diff --git a/patches/mouse-trackpad-split-0.7.patch b/patches/mouse-trackpad-split-0.7.patch new file mode 100644 index 0000000..89e33ac --- /dev/null +++ b/patches/mouse-trackpad-split-0.7.patch @@ -0,0 +1,96 @@ +From 211b52988756e9fecccf071fdea82832e1e17a0c Mon Sep 17 00:00:00 2001 +From: mmistika +Date: Tue, 24 Jun 2025 22:25:00 +0200 +Subject: [PATCH] Separate trackpad/mouse natural scroll and accel + +Signed-off-by: mmistika +--- + config.def.h | 12 +++++++++--- + dwl.c | 26 ++++++++++++++++++-------- + 2 files changed, 27 insertions(+), 11 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171..9d05a89 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -67,10 +67,14 @@ static const int repeat_delay = 600; + static const int tap_to_click = 1; + static const int tap_and_drag = 1; + static const int drag_lock = 1; +-static const int natural_scrolling = 0; + static const int disable_while_typing = 1; + static const int left_handed = 0; + static const int middle_button_emulation = 0; ++ ++/* Natural scrolling */ ++static const int trackpad_natural_scrolling = 0; ++static const int mouse_natural_scrolling = 0; ++ + /* You can choose between: + LIBINPUT_CONFIG_SCROLL_NO_SCROLL + LIBINPUT_CONFIG_SCROLL_2FG +@@ -97,8 +101,10 @@ static const uint32_t send_events_mode = LIBINPUT_CONFIG_SEND_EVENTS_ENABLED; + LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT + LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE + */ +-static const enum libinput_config_accel_profile accel_profile = LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE; +-static const double accel_speed = 0.0; ++static const enum libinput_config_accel_profile trackpad_accel_profile = LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE; ++static const double trackpad_accel_speed = 0.0; ++static const enum libinput_config_accel_profile mouse_accel_profile = LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT; ++static const double mouse_accel_speed = 0.0; + + /* You can choose between: + LIBINPUT_CONFIG_TAP_MAP_LRM -- 1/2/3 finger tap maps to left/right/middle +diff --git a/dwl.c b/dwl.c +index c717c1d..f05d6c5 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -1083,14 +1083,29 @@ createpointer(struct wlr_pointer *pointer) + && (device = wlr_libinput_get_device_handle(&pointer->base))) { + + if (libinput_device_config_tap_get_finger_count(device)) { ++ /* Trackpad */ + libinput_device_config_tap_set_enabled(device, tap_to_click); + libinput_device_config_tap_set_drag_enabled(device, tap_and_drag); + libinput_device_config_tap_set_drag_lock_enabled(device, drag_lock); + libinput_device_config_tap_set_button_map(device, button_map); +- } + +- if (libinput_device_config_scroll_has_natural_scroll(device)) +- libinput_device_config_scroll_set_natural_scroll_enabled(device, natural_scrolling); ++ if (libinput_device_config_scroll_has_natural_scroll(device)) ++ libinput_device_config_scroll_set_natural_scroll_enabled(device, trackpad_natural_scrolling); ++ ++ if (libinput_device_config_accel_is_available(device)) { ++ libinput_device_config_accel_set_profile(device, trackpad_accel_profile); ++ libinput_device_config_accel_set_speed(device, trackpad_accel_speed); ++ } ++ } else { ++ /* Mouse */ ++ if (libinput_device_config_scroll_has_natural_scroll(device)) ++ libinput_device_config_scroll_set_natural_scroll_enabled(device, mouse_natural_scrolling); ++ ++ if (libinput_device_config_accel_is_available(device)) { ++ libinput_device_config_accel_set_profile(device, mouse_accel_profile); ++ libinput_device_config_accel_set_speed(device, mouse_accel_speed); ++ } ++ } + + if (libinput_device_config_dwt_is_available(device)) + libinput_device_config_dwt_set_enabled(device, disable_while_typing); +@@ -1109,11 +1124,6 @@ createpointer(struct wlr_pointer *pointer) + + if (libinput_device_config_send_events_get_modes(device)) + libinput_device_config_send_events_set_mode(device, send_events_mode); +- +- if (libinput_device_config_accel_is_available(device)) { +- libinput_device_config_accel_set_profile(device, accel_profile); +- libinput_device_config_accel_set_speed(device, accel_speed); +- } + } + + wlr_cursor_attach_input_device(cursor, &pointer->base); +-- +2.50.0 + diff --git a/patches/movecenter.patch b/patches/movecenter.patch new file mode 100644 index 0000000..f96bd36 --- /dev/null +++ b/patches/movecenter.patch @@ -0,0 +1,82 @@ +From bc5206882c71b32198dae5f1c85601a863a7c0a9 Mon Sep 17 00:00:00 2001 +From: wochap +Date: Wed, 31 Jul 2024 07:43:10 -0500 +Subject: [PATCH] implement movecenter fn + +--- + config.def.h | 2 ++ + dwl.c | 31 +++++++++++++++++++++++++++++++ + 2 files changed, 33 insertions(+) + +diff --git a/config.def.h b/config.def.h +index 22d2171..f5225d9 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -13,6 +13,7 @@ static const float focuscolor[] = COLOR(0x005577ff); + static const float urgentcolor[] = COLOR(0xff0000ff); + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ + static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */ ++static const int respect_monitor_reserved_area = 0; /* 1 to monitor center while respecting the monitor's reserved area, 0 to monitor center */ + + /* tagging - TAGCOUNT must be no greater than 31 */ + #define TAGCOUNT (9) +@@ -142,6 +143,7 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_space, setlayout, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} }, + { MODKEY, XKB_KEY_e, togglefullscreen, {0} }, ++ { MODKEY, XKB_KEY_x, movecenter, {0} }, + { MODKEY, XKB_KEY_0, view, {.ui = ~0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_parenright, tag, {.ui = ~0} }, + { MODKEY, XKB_KEY_comma, focusmon, {.i = WLR_DIRECTION_LEFT} }, +diff --git a/dwl.c b/dwl.c +index 145fd01..791e598 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -336,6 +336,8 @@ static void tagmon(const Arg *arg); + static void tile(Monitor *m); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); ++static void _movecenter(Client *c, int interact); ++static void movecenter(const Arg *arg); + static void toggletag(const Arg *arg); + static void toggleview(const Arg *arg); + static void unlocksession(struct wl_listener *listener, void *data); +@@ -2683,6 +2685,35 @@ togglefullscreen(const Arg *arg) + setfullscreen(sel, !sel->isfullscreen); + } + ++void ++_movecenter(Client *c, int interact) ++{ ++ struct wlr_box b; ++ ++ if (!c || !c->mon) { ++ return; ++ } ++ ++ if (!c->isfloating) { ++ return; ++ } ++ ++ b = respect_monitor_reserved_area ? c->mon->w : c->mon->m; ++ resize(c, (struct wlr_box){ ++ .x = (b.width - c->geom.width) / 2 + b.x, ++ .y = (b.height - c->geom.height) / 2 + b.y, ++ .width = c->geom.width, ++ .height = c->geom.height, ++ }, interact); ++} ++ ++void ++movecenter(const Arg *arg) ++{ ++ Client *c = focustop(selmon); ++ _movecenter(c, 1); ++} ++ + void + toggletag(const Arg *arg) + { +-- +2.45.2 + diff --git a/patches/movestack-0.8.patch b/patches/movestack-0.8.patch new file mode 100644 index 0000000..cf0c933 --- /dev/null +++ b/patches/movestack-0.8.patch @@ -0,0 +1,87 @@ +From 7e8de6e79d24114272b22b55c906d7b55c36173a Mon Sep 17 00:00:00 2001 +From: Nikita Ivanov +Date: Mon, 16 Mar 2026 20:50:44 +0100 +Subject: [PATCH] Add movestack + +--- + config.def.h | 2 ++ + dwl.c | 43 +++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 45 insertions(+) + +diff --git a/config.def.h b/config.def.h +index 8a6eda0..f490e24 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -125,6 +125,8 @@ static const Key keys[] = { + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Return, spawn, {.v = termcmd} }, + { MODKEY, XKB_KEY_j, focusstack, {.i = +1} }, + { MODKEY, XKB_KEY_k, focusstack, {.i = -1} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_j, movestack, {.i = +1} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_k, movestack, {.i = -1} }, + { MODKEY, XKB_KEY_i, incnmaster, {.i = +1} }, + { MODKEY, XKB_KEY_d, incnmaster, {.i = -1} }, + { MODKEY, XKB_KEY_h, setmfact, {.f = -0.05f} }, +diff --git a/dwl.c b/dwl.c +index 44f3ad9..726fbdd 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -300,6 +300,7 @@ static void locksession(struct wl_listener *listener, void *data); + static void mapnotify(struct wl_listener *listener, void *data); + static void maximizenotify(struct wl_listener *listener, void *data); + static void monocle(Monitor *m); ++static void movestack(const Arg *arg); + static void motionabsolute(struct wl_listener *listener, void *data); + static void motionnotify(uint32_t time, struct wlr_input_device *device, double sx, + double sy, double sx_unaccel, double sy_unaccel); +@@ -1838,6 +1839,48 @@ monocle(Monitor *m) + wlr_scene_node_raise_to_top(&c->scene->node); + } + ++void ++movestack(const Arg *arg) ++{ ++ Client *c, *sel = focustop(selmon); ++ ++ if (!sel) { ++ return; ++ } ++ ++ if (wl_list_length(&clients) <= 1) { ++ return; ++ } ++ ++ if (arg->i > 0) { ++ wl_list_for_each(c, &sel->link, link) { ++ if (&c->link == &clients) { ++ c = wl_container_of(&clients, c, link); ++ break; /* wrap past the sentinel node */ ++ } ++ if (VISIBLEON(c, selmon) || &c->link == &clients) { ++ break; /* found it */ ++ } ++ } ++ } else { ++ wl_list_for_each_reverse(c, &sel->link, link) { ++ if (&c->link == &clients) { ++ c = wl_container_of(&clients, c, link); ++ break; /* wrap past the sentinel node */ ++ } ++ if (VISIBLEON(c, selmon) || &c->link == &clients) { ++ break; /* found it */ ++ } ++ } ++ /* backup one client */ ++ c = wl_container_of(c->link.prev, c, link); ++ } ++ ++ wl_list_remove(&sel->link); ++ wl_list_insert(&c->link, &sel->link); ++ arrange(selmon); ++} ++ + void + motionabsolute(struct wl_listener *listener, void *data) + { +-- +2.53.0 + diff --git a/patches/numlock-capslock-0.8.patch b/patches/numlock-capslock-0.8.patch new file mode 100644 index 0000000..8b3f53f --- /dev/null +++ b/patches/numlock-capslock-0.8.patch @@ -0,0 +1,79 @@ +From ba40e5a9866b3a22a9d56c9e30068a3d776e0991 Mon Sep 17 00:00:00 2001 +From: A Frederick Christensen +Date: Wed, 25 Feb 2026 19:59:18 -0600 +Subject: [PATCH] Apply numlock-capslock patch + +--- + config.def.h | 4 ++++ + dwl.c | 19 +++++++++++++++++++ + 2 files changed, 23 insertions(+) + +diff --git a/config.def.h b/config.def.h +index 8a6eda0..074fb88 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -56,6 +56,10 @@ static const struct xkb_rule_names xkb_rules = { + .options = NULL, + }; + ++/* numlock and capslock */ ++static const int numlock = 1; ++static const int capslock = 0; ++ + static const int repeat_rate = 25; + static const int repeat_delay = 600; + +diff --git a/dwl.c b/dwl.c +index 44f3ad9..c6ace09 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -14,6 +14,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -355,6 +356,7 @@ static void zoom(const Arg *arg); + /* variables */ + static pid_t child_pid = -1; + static int locked; ++static uint32_t locked_mods = 0; + static void *exclusive_focus; + static struct wl_display *dpy; + static struct wl_event_loop *event_loop; +@@ -943,6 +945,8 @@ createkeyboard(struct wlr_keyboard *keyboard) + /* Set the keymap to match the group keymap */ + wlr_keyboard_set_keymap(keyboard, kb_group->wlr_group->keyboard.keymap); + ++ wlr_keyboard_notify_modifiers(keyboard, 0, 0, locked_mods, 0); ++ + /* Add the new keyboard to the group */ + wlr_keyboard_group_add_keyboard(kb_group->wlr_group, keyboard); + } +@@ -964,6 +968,21 @@ createkeyboardgroup(void) + die("failed to compile keymap"); + + wlr_keyboard_set_keymap(&group->wlr_group->keyboard, keymap); ++ if (numlock) { ++ xkb_mod_index_t mod_index = xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_NUM); ++ if (mod_index != XKB_MOD_INVALID) ++ locked_mods |= (uint32_t)1 << mod_index; ++ } ++ ++ if (capslock) { ++ xkb_mod_index_t mod_index = xkb_keymap_mod_get_index(keymap, XKB_MOD_NAME_CAPS); ++ if (mod_index != XKB_MOD_INVALID) ++ locked_mods |= (uint32_t)1 << mod_index; ++ } ++ ++ if (locked_mods) ++ wlr_keyboard_notify_modifiers(&group->wlr_group->keyboard, 0, 0, locked_mods, 0); ++ + xkb_keymap_unref(keymap); + xkb_context_unref(context); + +-- +2.52.0 + diff --git a/patches/tablet-input-0.8.patch b/patches/tablet-input-0.8.patch new file mode 100644 index 0000000..3c64854 --- /dev/null +++ b/patches/tablet-input-0.8.patch @@ -0,0 +1,378 @@ +From 1069919d0252e554faddccc1f9da154e171ccdda Mon Sep 17 00:00:00 2001 +From: A Frederick Christensen +Date: Thu, 26 Feb 2026 21:56:13 -0600 +Subject: [PATCH] Apply tablet-input patch + +--- + Makefile | 6 +- + config.def.h | 1 + + dwl.c | 240 +++++++++++++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 246 insertions(+), 1 deletion(-) + +diff --git a/Makefile b/Makefile +index 578194f..e0d1835 100644 +--- a/Makefile ++++ b/Makefile +@@ -21,7 +21,8 @@ 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 \ + 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 \ ++ tablet-v2-protocol.h + util.o: util.c util.h + + # wayland-scanner is a tool which generates C headers and rigging for Wayland +@@ -45,6 +46,9 @@ wlr-output-power-management-unstable-v1-protocol.h: + xdg-shell-protocol.h: + $(WAYLAND_SCANNER) server-header \ + $(WAYLAND_PROTOCOLS)/stable/xdg-shell/xdg-shell.xml $@ ++tablet-v2-protocol.h: ++ $(WAYLAND_SCANNER) server-header \ ++ $(WAYLAND_PROTOCOLS)/unstable/tablet/tablet-unstable-v2.xml $@ + + config.h: + cp config.def.h $@ +diff --git a/config.def.h b/config.def.h +index 8a6eda0..1f20dfd 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -4,6 +4,7 @@ + ((hex >> 8) & 0xFF) / 255.0f, \ + (hex & 0xFF) / 255.0f } + /* appearance */ ++static const int tabletmaptosurface = 0; /* map tablet input to surface(1) or monitor(0) */ + 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 */ +diff --git a/dwl.c b/dwl.c +index 44f3ad9..4ef2cc8 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -51,6 +51,9 @@ + #include + #include + #include ++#include ++#include ++#include + #include + #include + #include +@@ -268,6 +271,7 @@ static void createnotify(struct wl_listener *listener, void *data); + static void createpointer(struct wlr_pointer *pointer); + static void createpointerconstraint(struct wl_listener *listener, void *data); + static void createpopup(struct wl_listener *listener, void *data); ++static void createtablet(struct wlr_input_device *device); + static void cursorconstrain(struct wlr_pointer_constraint_v1 *constraint); + static void cursorframe(struct wl_listener *listener, void *data); + static void cursorwarptohint(void); +@@ -281,6 +285,9 @@ static void destroynotify(struct wl_listener *listener, void *data); + static void destroypointerconstraint(struct wl_listener *listener, void *data); + static void destroysessionlock(struct wl_listener *listener, void *data); + static void destroykeyboardgroup(struct wl_listener *listener, void *data); ++static void destroytablet(struct wl_listener *listener, void *data); ++static void destroytabletsurfacenotify(struct wl_listener *listener, void *data); ++static void destroytablettool(struct wl_listener *listener, void *data); + static Monitor *dirtomon(enum wlr_direction dir); + static void focusclient(Client *c, int lift); + static void focusmon(const Arg *arg); +@@ -333,6 +340,11 @@ static void spawn(const Arg *arg); + static void startdrag(struct wl_listener *listener, void *data); + static void tag(const Arg *arg); + static void tagmon(const Arg *arg); ++static void tablettoolmotion(struct wlr_tablet_v2_tablet_tool *tool, bool change_x, bool change_y, double x, double y, double dx, double dy); ++static void tablettoolproximity(struct wl_listener *listener, void *data); ++static void tablettoolaxis(struct wl_listener *listener, void *data); ++static void tablettoolbutton(struct wl_listener *listener, void *data); ++static void tablettooltip(struct wl_listener *listener, void *data); + static void tile(Monitor *m); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); +@@ -390,6 +402,13 @@ static struct wlr_pointer_constraint_v1 *active_constraint; + static struct wlr_cursor *cursor; + static struct wlr_xcursor_manager *cursor_mgr; + ++static struct wlr_tablet_manager_v2 *tablet_mgr; ++static struct wlr_tablet_v2_tablet *tablet = NULL; ++static struct wlr_tablet_v2_tablet_tool *tablet_tool = NULL; ++static struct wlr_tablet_v2_tablet_pad *tablet_pad = NULL; ++static struct wlr_surface *tablet_curr_surface = NULL; ++static struct wl_listener destroy_tablet_surface_listener = {.notify = destroytabletsurfacenotify}; ++ + static struct wlr_scene_rect *root_bg; + static struct wlr_session_lock_manager_v1 *session_lock_mgr; + static struct wlr_scene_rect *locked_bg; +@@ -412,6 +431,12 @@ static struct wl_listener cursor_button = {.notify = buttonpress}; + static struct wl_listener cursor_frame = {.notify = cursorframe}; + static struct wl_listener cursor_motion = {.notify = motionrelative}; + static struct wl_listener cursor_motion_absolute = {.notify = motionabsolute}; ++static struct wl_listener tablet_device_destroy = {.notify = destroytablet}; ++static struct wl_listener tablet_tool_axis = {.notify = tablettoolaxis}; ++static struct wl_listener tablet_tool_button = {.notify = tablettoolbutton}; ++static struct wl_listener tablet_tool_destroy = {.notify = destroytablettool}; ++static struct wl_listener tablet_tool_proximity = {.notify = tablettoolproximity}; ++static struct wl_listener tablet_tool_tip = {.notify = tablettooltip}; + static struct wl_listener gpu_reset = {.notify = gpureset}; + static struct wl_listener layout_change = {.notify = updatemons}; + static struct wl_listener new_idle_inhibitor = {.notify = createidleinhibitor}; +@@ -1199,6 +1224,28 @@ createpopup(struct wl_listener *listener, void *data) + LISTEN_STATIC(&popup->base->surface->events.commit, commitpopup); + } + ++void ++createtablet(struct wlr_input_device *device) ++{ ++ if (!tablet) { ++ struct libinput_device *device_handle = NULL; ++ if (!wlr_input_device_is_libinput(device) || ++ !(device_handle = wlr_libinput_get_device_handle(device))) ++ return; ++ ++ tablet = wlr_tablet_create(tablet_mgr, seat, device); ++ wl_signal_add(&tablet->wlr_device->events.destroy, &tablet_device_destroy); ++ if (libinput_device_config_send_events_get_modes(device_handle)) { ++ libinput_device_config_send_events_set_mode(device_handle, send_events_mode); ++ wlr_cursor_attach_input_device(cursor, device); ++ } ++ } else if (device == tablet->wlr_device) { ++ wlr_log(WLR_ERROR, "createtablet: duplicate device"); ++ } else { ++ wlr_log(WLR_ERROR, "createtablet: already have one tablet"); ++ } ++} ++ + void + cursorconstrain(struct wlr_pointer_constraint_v1 *constraint) + { +@@ -1383,6 +1430,29 @@ destroykeyboardgroup(struct wl_listener *listener, void *data) + free(group); + } + ++void ++destroytablet(struct wl_listener *listener, void *data) ++{ ++ wl_list_remove(&tablet_device_destroy.link); ++ wlr_cursor_detach_input_device(cursor, tablet->wlr_device); ++ tablet = NULL; ++} ++ ++void ++destroytabletsurfacenotify(struct wl_listener *listener, void *data) ++{ ++ if (tablet_curr_surface) ++ wl_list_remove(&destroy_tablet_surface_listener.link); ++ tablet_curr_surface = NULL; ++} ++ ++void ++destroytablettool(struct wl_listener *listener, void *data) ++{ ++ destroytabletsurfacenotify(NULL, NULL); ++ tablet_tool = NULL; ++} ++ + Monitor * + dirtomon(enum wlr_direction dir) + { +@@ -1590,6 +1660,12 @@ inputdevice(struct wl_listener *listener, void *data) + case WLR_INPUT_DEVICE_POINTER: + createpointer(wlr_pointer_from_input_device(device)); + break; ++ case WLR_INPUT_DEVICE_TABLET: ++ createtablet(device); ++ break; ++ case WLR_INPUT_DEVICE_TABLET_PAD: ++ tablet_pad = wlr_tablet_pad_create(tablet_mgr, seat, device); ++ break; + default: + /* TODO handle other input device types */ + break; +@@ -2585,6 +2661,8 @@ setup(void) + + relative_pointer_mgr = wlr_relative_pointer_manager_v1_create(dpy); + ++ tablet_mgr = wlr_tablet_v2_create(dpy); ++ + /* + * Creates a cursor, which is a wlroots utility for tracking the cursor + * image shown on screen. +@@ -2614,6 +2692,11 @@ setup(void) + wl_signal_add(&cursor->events.button, &cursor_button); + wl_signal_add(&cursor->events.axis, &cursor_axis); + wl_signal_add(&cursor->events.frame, &cursor_frame); ++ wl_signal_add(&cursor->events.tablet_tool_proximity, &tablet_tool_proximity); ++ wl_signal_add(&cursor->events.tablet_tool_axis, &tablet_tool_axis); ++ wl_signal_add(&cursor->events.tablet_tool_button, &tablet_tool_button); ++ wl_signal_add(&cursor->events.tablet_tool_tip, &tablet_tool_tip); ++ + + cursor_shape_mgr = wlr_cursor_shape_manager_v1_create(dpy, 1); + wl_signal_add(&cursor_shape_mgr->events.request_set_shape, &request_set_cursor_shape); +@@ -2709,6 +2792,163 @@ tagmon(const Arg *arg) + setmon(sel, dirtomon(arg->i), 0); + } + ++void ++tabletapplymap(double x, double y, struct wlr_input_device *dev) ++{ ++ Client *p; ++ struct wlr_box geom = {0}; ++ if (tabletmaptosurface && tablet_curr_surface) { ++ toplevel_from_wlr_surface(tablet_curr_surface, &p, NULL); ++ if (p) { ++ for (; client_get_parent(p); p = client_get_parent(p)); ++ geom.x = p->geom.x + p->bw; ++ geom.y = p->geom.y + p->bw; ++ geom.width = p->geom.width - 2 * p->bw; ++ geom.height = p->geom.height - 2 * p->bw; ++ } ++ } ++ wlr_cursor_map_input_to_region(cursor, dev, &geom); ++ wlr_cursor_map_input_to_output(cursor, dev, selmon->wlr_output); ++} ++ ++void ++tablettoolmotion(struct wlr_tablet_v2_tablet_tool *tool, bool change_x, bool change_y, ++ double x, double y, double dx, double dy) ++{ ++ struct wlr_surface *surface = NULL; ++ double sx, sy; ++ ++ if (!change_x && !change_y) ++ return; ++ ++ tabletapplymap(x, y, tablet->wlr_device); ++ ++ // TODO: apply constraints ++ switch (tablet_tool->wlr_tool->type) { ++ case WLR_TABLET_TOOL_TYPE_LENS: ++ case WLR_TABLET_TOOL_TYPE_MOUSE: ++ wlr_cursor_move(cursor, tablet->wlr_device, dx, dy); ++ break; ++ default: ++ wlr_cursor_warp_absolute(cursor, tablet->wlr_device, change_x ? x : NAN, change_y ? y : NAN); ++ break; ++ } ++ ++ motionnotify(0, NULL, 0, 0, 0, 0); ++ ++ xytonode(cursor->x, cursor->y, &surface, NULL, NULL, &sx, &sy); ++ if (surface && !wlr_surface_accepts_tablet_v2(surface, tablet)) ++ surface = NULL; ++ ++ if (surface != tablet_curr_surface) { ++ if (tablet_curr_surface) { ++ // TODO: wait until all buttons released before leaving ++ if (tablet_tool) ++ wlr_tablet_v2_tablet_tool_notify_proximity_out(tablet_tool); ++ if (tablet_pad) ++ wlr_tablet_v2_tablet_pad_notify_leave(tablet_pad, tablet_curr_surface); ++ wl_list_remove(&destroy_tablet_surface_listener.link); ++ } ++ if (surface) { ++ if (tablet_pad) ++ wlr_tablet_v2_tablet_pad_notify_enter(tablet_pad, tablet, surface); ++ if (tablet_tool) ++ wlr_tablet_v2_tablet_tool_notify_proximity_in(tablet_tool, tablet, surface); ++ wl_signal_add(&surface->events.destroy, &destroy_tablet_surface_listener); ++ } ++ tablet_curr_surface = surface; ++ } ++ ++ if (surface) ++ wlr_tablet_v2_tablet_tool_notify_motion(tablet_tool, sx, sy); ++} ++ ++void ++tablettoolproximity(struct wl_listener *listener, void *data) ++{ ++ struct wlr_tablet_tool_proximity_event *event = data; ++ struct wlr_tablet_tool *tool = event->tool; ++ ++ if (!tablet_tool) { ++ tablet_tool = wlr_tablet_tool_create(tablet_mgr, seat, tool); ++ wl_signal_add(&tablet_tool->wlr_tool->events.destroy, &tablet_tool_destroy); ++ wl_signal_add(&tablet_tool->events.set_cursor, &request_cursor); ++ } ++ ++ switch (event->state) { ++ case WLR_TABLET_TOOL_PROXIMITY_OUT: ++ wlr_tablet_v2_tablet_tool_notify_proximity_out(tablet_tool); ++ destroytabletsurfacenotify(NULL, NULL); ++ break; ++ case WLR_TABLET_TOOL_PROXIMITY_IN: ++ tablettoolmotion(tablet_tool, true, true, event->x, event->y, 0, 0); ++ break; ++ } ++} ++ ++double tilt_x = 0; ++double tilt_y = 0; ++ ++void ++tablettoolaxis(struct wl_listener *listener, void *data) ++{ ++ struct wlr_tablet_tool_axis_event *event = data; ++ ++ tablettoolmotion(tablet_tool, ++ event->updated_axes & WLR_TABLET_TOOL_AXIS_X, ++ event->updated_axes & WLR_TABLET_TOOL_AXIS_Y, ++ event->x, event->y, event->dx, event->dy); ++ ++ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_PRESSURE) ++ wlr_tablet_v2_tablet_tool_notify_pressure(tablet_tool, event->pressure); ++ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_DISTANCE) ++ wlr_tablet_v2_tablet_tool_notify_distance(tablet_tool, event->distance); ++ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_TILT_X) ++ tilt_x = event->tilt_x; ++ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_TILT_Y) ++ tilt_y = event->tilt_y; ++ if (event->updated_axes & (WLR_TABLET_TOOL_AXIS_TILT_X | WLR_TABLET_TOOL_AXIS_TILT_Y)) ++ wlr_tablet_v2_tablet_tool_notify_tilt(tablet_tool, tilt_x, tilt_y); ++ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_ROTATION) ++ wlr_tablet_v2_tablet_tool_notify_rotation(tablet_tool, event->rotation); ++ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_SLIDER) ++ wlr_tablet_v2_tablet_tool_notify_slider(tablet_tool, event->slider); ++ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_WHEEL) ++ wlr_tablet_v2_tablet_tool_notify_wheel(tablet_tool, event->wheel_delta, 0); ++} ++ ++void ++tablettoolbutton(struct wl_listener *listener, void *data) ++{ ++ struct wlr_tablet_tool_button_event *event = data; ++ wlr_tablet_v2_tablet_tool_notify_button(tablet_tool, event->button, ++ (enum zwp_tablet_pad_v2_button_state)event->state); ++} ++ ++void ++tablettooltip(struct wl_listener *listener, void *data) ++{ ++ struct wlr_tablet_tool_tip_event *event = data; ++ ++ if (!tablet_curr_surface) { ++ struct wlr_pointer_button_event fakeptrbtnevent = { ++ .button = BTN_LEFT, ++ .state = event->state == WLR_TABLET_TOOL_TIP_UP ? ++ WL_POINTER_BUTTON_STATE_RELEASED : WL_POINTER_BUTTON_STATE_PRESSED, ++ .time_msec = event->time_msec, ++ }; ++ buttonpress(NULL, (void *)&fakeptrbtnevent); ++ } ++ ++ if (event->state == WLR_TABLET_TOOL_TIP_UP) { ++ wlr_tablet_v2_tablet_tool_notify_up(tablet_tool); ++ return; ++ } ++ ++ wlr_tablet_v2_tablet_tool_notify_down(tablet_tool); ++ wlr_tablet_tool_v2_start_implicit_grab(tablet_tool); ++} ++ + void + tile(Monitor *m) + { +-- +2.52.0 + diff --git a/patches/touch-input-0.8.patch b/patches/touch-input-0.8.patch new file mode 100644 index 0000000..45fc01e --- /dev/null +++ b/patches/touch-input-0.8.patch @@ -0,0 +1,259 @@ +From 44dfe863a00f4bdc646df3d761ac97711dd2107a Mon Sep 17 00:00:00 2001 +From: A Frederick Christensen +Date: Thu, 26 Feb 2026 22:12:53 -0600 +Subject: [PATCH] Apply touch-input patch + +--- + dwl.c | 158 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 158 insertions(+) + +diff --git a/dwl.c b/dwl.c +index 44f3ad9..ccfe33c 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -51,6 +51,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -161,6 +162,12 @@ typedef struct { + struct wl_listener destroy; + } KeyboardGroup; + ++typedef struct TouchGroup { ++ struct wl_list link; ++ struct wlr_touch *touch; ++ Monitor *m; ++} TouchGroup; ++ + typedef struct { + /* Must keep this field first */ + unsigned int type; /* LayerShell */ +@@ -268,7 +275,9 @@ static void createnotify(struct wl_listener *listener, void *data); + static void createpointer(struct wlr_pointer *pointer); + static void createpointerconstraint(struct wl_listener *listener, void *data); + static void createpopup(struct wl_listener *listener, void *data); ++static void createtouch(struct wlr_touch *touch); + static void cursorconstrain(struct wlr_pointer_constraint_v1 *constraint); ++static void createtouch(struct wlr_touch *touch); + static void cursorframe(struct wl_listener *listener, void *data); + static void cursorwarptohint(void); + static void destroydecoration(struct wl_listener *listener, void *data); +@@ -338,6 +347,10 @@ 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 touchdown(struct wl_listener *listener, void *data); ++static void touchup(struct wl_listener *listener, void *data); ++static void touchframe(struct wl_listener *listener, void *data); ++static void touchmotion(struct wl_listener *listener, 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); +@@ -405,6 +418,7 @@ static struct wlr_output_layout *output_layout; + static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; ++static struct wl_list touches; + + /* global event handlers */ + static struct wl_listener cursor_axis = {.notify = axisnotify}; +@@ -434,6 +448,10 @@ static struct wl_listener request_set_sel = {.notify = setsel}; + static struct wl_listener request_set_cursor_shape = {.notify = setcursorshape}; + static struct wl_listener request_start_drag = {.notify = requeststartdrag}; + static struct wl_listener start_drag = {.notify = startdrag}; ++static struct wl_listener touch_down = {.notify = touchdown}; ++static struct wl_listener touch_frame = {.notify = touchframe}; ++static struct wl_listener touch_motion = {.notify = touchmotion}; ++static struct wl_listener touch_up = {.notify = touchup}; + static struct wl_listener new_session_lock = {.notify = locksession}; + + #ifdef XWAYLAND +@@ -781,6 +799,10 @@ cleanuplisteners(void) + wl_list_remove(&request_set_cursor_shape.link); + wl_list_remove(&request_start_drag.link); + wl_list_remove(&start_drag.link); ++ wl_list_remove(&touch_down.link); ++ wl_list_remove(&touch_frame.link); ++ wl_list_remove(&touch_motion.link); ++ wl_list_remove(&touch_up.link); + wl_list_remove(&new_session_lock.link); + #ifdef XWAYLAND + wl_list_remove(&new_xwayland_surface.link); +@@ -1199,6 +1221,16 @@ createpopup(struct wl_listener *listener, void *data) + LISTEN_STATIC(&popup->base->surface->events.commit, commitpopup); + } + ++void ++createtouch(struct wlr_touch *wlr_touch) ++{ ++ TouchGroup *touch = ecalloc(1, sizeof(TouchGroup)); ++ ++ touch->touch = wlr_touch; ++ wl_list_insert(&touches, &touch->link); ++ wlr_cursor_attach_input_device(cursor, &wlr_touch->base); ++} ++ + void + cursorconstrain(struct wlr_pointer_constraint_v1 *constraint) + { +@@ -1590,6 +1622,9 @@ inputdevice(struct wl_listener *listener, void *data) + case WLR_INPUT_DEVICE_POINTER: + createpointer(wlr_pointer_from_input_device(device)); + break; ++ case WLR_INPUT_DEVICE_TOUCH: ++ createtouch(wlr_touch_from_input_device(device)); ++ break; + default: + /* TODO handle other input device types */ + break; +@@ -1602,6 +1637,8 @@ inputdevice(struct wl_listener *listener, void *data) + caps = WL_SEAT_CAPABILITY_POINTER; + if (!wl_list_empty(&kb_group->wlr_group->devices)) + caps |= WL_SEAT_CAPABILITY_KEYBOARD; ++ if (!wl_list_empty(&touches)) ++ caps |= WL_SEAT_CAPABILITY_TOUCH; + wlr_seat_set_capabilities(seat, caps); + } + +@@ -2615,6 +2652,13 @@ setup(void) + wl_signal_add(&cursor->events.axis, &cursor_axis); + wl_signal_add(&cursor->events.frame, &cursor_frame); + ++ wl_list_init(&touches); ++ ++ wl_signal_add(&cursor->events.touch_down, &touch_down); ++ wl_signal_add(&cursor->events.touch_frame, &touch_frame); ++ wl_signal_add(&cursor->events.touch_motion, &touch_motion); ++ wl_signal_add(&cursor->events.touch_up, &touch_up); ++ + cursor_shape_mgr = wlr_cursor_shape_manager_v1_create(dpy, 1); + wl_signal_add(&cursor_shape_mgr->events.request_set_shape, &request_set_cursor_shape); + +@@ -2787,6 +2831,120 @@ toggleview(const Arg *arg) + printstatus(); + } + ++void ++touchdown(struct wl_listener *listener, void *data) ++{ ++ struct wlr_touch_down_event *event = data; ++ double lx, ly; ++ double sx, sy; ++ struct wlr_surface *surface; ++ Client *c = NULL; ++ uint32_t serial = 0; ++ Monitor *m; ++ ++ wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); ++ ++ // Map the input to the appropriate output, to ensure that rotation is ++ // handled. ++ wl_list_for_each(m, &mons, link) { ++ if (m == NULL || m->wlr_output == NULL) { ++ continue; ++ } ++ if (event->touch->output_name != NULL && 0 != strcmp(event->touch->output_name, m->wlr_output->name)) { ++ continue; ++ } ++ ++ wlr_cursor_map_input_to_output(cursor, &event->touch->base, m->wlr_output); ++ } ++ ++ wlr_cursor_absolute_to_layout_coords(cursor, &event->touch->base, event->x, event->y, &lx, &ly); ++ ++ /* Find the client under the pointer and send the event along. */ ++ xytonode(lx, ly, &surface, &c, NULL, &sx, &sy); ++ if (sloppyfocus) ++ focusclient(c, 0); ++ ++ if (surface != NULL) { ++ serial = wlr_seat_touch_notify_down(seat, surface, event->time_msec, event->touch_id, sx, sy); ++ } ++ ++ if (serial && wlr_seat_touch_num_points(seat) == 1) { ++ /* Emulate a mouse click if the touch event wasn't handled */ ++ struct wlr_pointer_button_event *button_event = data; ++ struct wlr_pointer_motion_absolute_event *motion_event = data; ++ double dx, dy; ++ ++ wlr_cursor_absolute_to_layout_coords(cursor, &motion_event->pointer->base, motion_event->x, motion_event->y, &lx, &ly); ++ wlr_cursor_warp_closest(cursor, &motion_event->pointer->base, lx, ly); ++ dx = lx - cursor->x; ++ dy = ly - cursor->y; ++ motionnotify(motion_event->time_msec, &motion_event->pointer->base, dx, dy, dx, dy); ++ ++ button_event->button = BTN_LEFT; ++ button_event->state = WL_POINTER_BUTTON_STATE_PRESSED; ++ buttonpress(listener, button_event); ++ } ++} ++ ++void ++touchup(struct wl_listener *listener, void *data) ++{ ++ struct wlr_touch_up_event *event = data; ++ ++ if (!wlr_seat_touch_get_point(seat, event->touch_id)) { ++ return; ++ } ++ ++ if (wlr_seat_touch_num_points(seat) == 1) { ++ struct wlr_pointer_button_event *button_event = data; ++ ++ button_event->button = BTN_LEFT; ++ button_event->state = WL_POINTER_BUTTON_STATE_RELEASED; ++ buttonpress(listener, button_event); ++ } ++ ++ wlr_seat_touch_notify_up(seat, event->time_msec, event->touch_id); ++ wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); ++} ++ ++void ++touchframe(struct wl_listener *listener, void *data) ++{ ++ wlr_seat_touch_notify_frame(seat); ++ wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); ++} ++ ++void ++touchmotion(struct wl_listener *listener, void *data) ++{ ++ struct wlr_touch_motion_event *event = data; ++ double lx, ly; ++ double sx, sy; ++ struct wlr_surface *surface; ++ Client *c = NULL; ++ ++ if (!wlr_seat_touch_get_point(seat, event->touch_id)) { ++ return; ++ } ++ ++ wlr_cursor_absolute_to_layout_coords(cursor, &event->touch->base, event->x, event->y, &lx, &ly); ++ xytonode(lx, ly, &surface, &c, NULL, &sx, &sy); ++ ++ if (c != NULL && surface != NULL) { ++ if (sloppyfocus) ++ focusclient(c, 0); ++ wlr_seat_touch_point_focus(seat, surface, event->time_msec, event->touch_id, sx, sy); ++ } else { ++ if (sloppyfocus) ++ focusclient(NULL, 0); ++ wlr_seat_touch_point_clear_focus(seat, event->time_msec, event->touch_id); ++ } ++ wlr_seat_touch_notify_motion(seat, event->time_msec, event->touch_id, sx, sy); ++ ++ wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); ++} ++ ++ + void + unlocksession(struct wl_listener *listener, void *data) + { +-- +2.52.0 + diff --git a/patches/unclutter.patch b/patches/unclutter.patch new file mode 100644 index 0000000..dd5a298 --- /dev/null +++ b/patches/unclutter.patch @@ -0,0 +1,192 @@ +From 68914f40359ebccc7b684a1f74d82419b1796cdf Mon Sep 17 00:00:00 2001 +From: Guido Cella +Date: Thu, 25 Jul 2024 17:59:05 +0200 +Subject: [PATCH] =?UTF-8?q?hide=20the=20mouse=20cursor=20if=20it=20isn?= + =?UTF-8?q?=E2=80=99t=20being=20used?= +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +--- + config.def.h | 2 ++ + dwl.c | 74 +++++++++++++++++++++++++++++++++++++++++++++------- + 2 files changed, 67 insertions(+), 9 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171..790c73d 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -106,6 +106,8 @@ LIBINPUT_CONFIG_TAP_MAP_LMR -- 1/2/3 finger tap maps to left/middle/right + */ + static const enum libinput_config_tap_button_map button_map = LIBINPUT_CONFIG_TAP_MAP_LRM; + ++static const int cursor_timeout = 5; ++ + /* If you want to use the windows key for MODKEY, use WLR_MODIFIER_LOGO */ + #define MODKEY WLR_MODIFIER_ALT + +diff --git a/dwl.c b/dwl.c +index 775dadf..621779e 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -288,6 +288,8 @@ static void focusstack(const Arg *arg); + static Client *focustop(Monitor *m); + static void fullscreennotify(struct wl_listener *listener, void *data); + static void gpureset(struct wl_listener *listener, void *data); ++static void handlecursoractivity(void); ++static int hidecursor(void *data); + static void handlesig(int signo); + static void incnmaster(const Arg *arg); + static void inputdevice(struct wl_listener *listener, void *data); +@@ -389,6 +391,14 @@ static struct wlr_pointer_constraint_v1 *active_constraint; + + static struct wlr_cursor *cursor; + static struct wlr_xcursor_manager *cursor_mgr; ++static struct wl_event_source *hide_source; ++static bool cursor_hidden = false; ++static struct { ++ enum wp_cursor_shape_device_v1_shape shape; ++ struct wlr_surface *surface; ++ int hotspot_x; ++ int hotspot_y; ++} last_cursor; + + static struct wlr_scene_rect *root_bg; + static struct wlr_session_lock_manager_v1 *session_lock_mgr; +@@ -609,8 +619,9 @@ axisnotify(struct wl_listener *listener, void *data) + * for example when you move the scroll wheel. */ + struct wlr_pointer_axis_event *event = data; + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); +- /* TODO: allow usage of scroll wheel for mousebindings, it can be implemented +- * by checking the event's orientation and the delta of the event */ ++ handlecursoractivity(); ++ /* TODO: allow usage of scroll wheel for mousebindings, it can be implemented ++ * checking the event's orientation and the delta of the event */ + /* Notify the client with pointer focus of the axis event. */ + wlr_seat_pointer_notify_axis(seat, + event->time_msec, event->orientation, event->delta, +@@ -627,6 +638,7 @@ buttonpress(struct wl_listener *listener, void *data) + const Button *b; + + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); ++ handlecursoractivity(); + + switch (event->state) { + case WL_POINTER_BUTTON_STATE_PRESSED: +@@ -1563,6 +1575,32 @@ handlesig(int signo) + quit(NULL); + } + ++void ++handlecursoractivity(void) ++{ ++ wl_event_source_timer_update(hide_source, cursor_timeout * 1000); ++ ++ if (!cursor_hidden) ++ return; ++ ++ cursor_hidden = false; ++ ++ if (last_cursor.shape) ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, ++ wlr_cursor_shape_v1_name(last_cursor.shape)); ++ else ++ wlr_cursor_set_surface(cursor, last_cursor.surface, ++ last_cursor.hotspot_x, last_cursor.hotspot_y); ++} ++ ++int ++hidecursor(void *data) ++{ ++ wlr_cursor_unset_image(cursor); ++ cursor_hidden = true; ++ return 1; ++} ++ + void + incnmaster(const Arg *arg) + { +@@ -1903,6 +1941,7 @@ motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double d + + wlr_cursor_move(cursor, device, dx, dy); + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); ++ handlecursoractivity(); + + /* Update selmon (even while dragging a window) */ + if (sloppyfocus) +@@ -1927,7 +1966,7 @@ motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double d + /* If there's no client surface under the cursor, set the cursor image to a + * default. This is what makes the cursor image appear when you move it + * off of a client or over its border. */ +- if (!surface && !seat->drag) ++ if (!surface && !seat->drag && !cursor_hidden) + wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); + + pointerfocus(c, surface, sx, sy, time); +@@ -2284,6 +2323,7 @@ run(char *startup_cmd) + * monitor when displayed here */ + wlr_cursor_warp_closest(cursor, NULL, cursor->x, cursor->y); + wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); ++ handlecursoractivity(); + + /* Run the Wayland event loop. This does not return until you exit the + * compositor. Starting the backend rigged up all of the necessary event +@@ -2307,9 +2347,16 @@ setcursor(struct wl_listener *listener, void *data) + * use the provided surface as the cursor image. It will set the + * hardware cursor on the output that it's currently on and continue to + * do so as the cursor moves between outputs. */ +- if (event->seat_client == seat->pointer_state.focused_client) +- wlr_cursor_set_surface(cursor, event->surface, +- event->hotspot_x, event->hotspot_y); ++ if (event->seat_client == seat->pointer_state.focused_client) { ++ last_cursor.shape = 0; ++ last_cursor.surface = event->surface; ++ last_cursor.hotspot_x = event->hotspot_x; ++ last_cursor.hotspot_y = event->hotspot_y; ++ ++ if (!cursor_hidden) ++ wlr_cursor_set_surface(cursor, event->surface, ++ event->hotspot_x, event->hotspot_y); ++ } + } + + void +@@ -2321,9 +2368,14 @@ setcursorshape(struct wl_listener *listener, void *data) + /* This can be sent by any client, so we check to make sure this one + * actually has pointer focus first. If so, we can tell the cursor to + * use the provided cursor shape. */ +- if (event->seat_client == seat->pointer_state.focused_client) +- wlr_cursor_set_xcursor(cursor, cursor_mgr, +- wlr_cursor_shape_v1_name(event->shape)); ++ if (event->seat_client == seat->pointer_state.focused_client) { ++ last_cursor.shape = event->shape; ++ last_cursor.surface = NULL; ++ ++ if (!cursor_hidden) ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, ++ wlr_cursor_shape_v1_name(event->shape)); ++ } + } + + void +@@ -2614,6 +2666,9 @@ setup(void) + cursor_shape_mgr = wlr_cursor_shape_manager_v1_create(dpy, 1); + wl_signal_add(&cursor_shape_mgr->events.request_set_shape, &request_set_cursor_shape); + ++ hide_source = wl_event_loop_add_timer(wl_display_get_event_loop(dpy), ++ hidecursor, cursor); ++ + /* + * Configures a seat, which is a single "seat" at which a user sits and + * operates the computer. This conceptually includes up to one keyboard, +@@ -2998,6 +3053,7 @@ virtualpointer(struct wl_listener *listener, void *data) + wlr_cursor_attach_input_device(cursor, device); + if (event->suggested_output) + wlr_cursor_map_input_to_output(cursor, device, event->suggested_output); ++ handlecursoractivity(); + } + + Monitor * +-- +2.49.0 + diff --git a/patches/vanitygaps-0.7.patch b/patches/vanitygaps-0.7.patch new file mode 100644 index 0000000..622b444 --- /dev/null +++ b/patches/vanitygaps-0.7.patch @@ -0,0 +1,358 @@ +From 3c95b58bc2b87ebd9b8481b3b16e49d99883f0a7 Mon Sep 17 00:00:00 2001 +From: Bonicgamer <44382222+Bonicgamer@users.noreply.github.com> +Date: Mon, 17 Aug 2020 14:48:24 -0400 +Subject: [PATCH 1/2] Implement vanitygaps +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Signed-off-by: Leonardo Hernández Hernández +--- + config.def.h | 21 ++++++++ + dwl.c | 150 +++++++++++++++++++++++++++++++++++++++++++++++---- + 2 files changed, 161 insertions(+), 10 deletions(-) + +diff --git a/config.def.h b/config.def.h +index 22d2171d..39e528b1 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -6,7 +6,12 @@ + /* appearance */ + 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 int smartgaps = 0; /* 1 means no outer gap when there is only one window */ + static const unsigned int borderpx = 1; /* border pixel of windows */ ++static const unsigned int gappih = 10; /* horiz inner gap between windows */ ++static const unsigned int gappiv = 10; /* vert inner gap between windows */ ++static const unsigned int gappoh = 10; /* horiz outer gap between windows and screen edge */ ++static const unsigned int gappov = 10; /* vert outer gap between windows and screen edge */ + static const float rootcolor[] = COLOR(0x222222ff); + static const float bordercolor[] = COLOR(0x444444ff); + static const float focuscolor[] = COLOR(0x005577ff); +@@ -133,6 +138,22 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_d, incnmaster, {.i = -1} }, + { MODKEY, XKB_KEY_h, setmfact, {.f = -0.05f} }, + { MODKEY, XKB_KEY_l, setmfact, {.f = +0.05f} }, ++ { MODKEY|WLR_MODIFIER_LOGO, XKB_KEY_h, incgaps, {.i = +1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO, XKB_KEY_l, incgaps, {.i = -1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO|WLR_MODIFIER_SHIFT, XKB_KEY_H, incogaps, {.i = +1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO|WLR_MODIFIER_SHIFT, XKB_KEY_L, incogaps, {.i = -1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO|WLR_MODIFIER_CTRL, XKB_KEY_h, incigaps, {.i = +1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO|WLR_MODIFIER_CTRL, XKB_KEY_l, incigaps, {.i = -1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO, XKB_KEY_0, togglegaps, {0} }, ++ { MODKEY|WLR_MODIFIER_LOGO|WLR_MODIFIER_SHIFT, XKB_KEY_parenright,defaultgaps, {0} }, ++ { MODKEY, XKB_KEY_y, incihgaps, {.i = +1 } }, ++ { MODKEY, XKB_KEY_o, incihgaps, {.i = -1 } }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_y, incivgaps, {.i = +1 } }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_o, incivgaps, {.i = -1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO, XKB_KEY_y, incohgaps, {.i = +1 } }, ++ { MODKEY|WLR_MODIFIER_LOGO, XKB_KEY_o, incohgaps, {.i = -1 } }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Y, incovgaps, {.i = +1 } }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_O, incovgaps, {.i = -1 } }, + { MODKEY, XKB_KEY_Return, zoom, {0} }, + { MODKEY, XKB_KEY_Tab, view, {0} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_C, killclient, {0} }, +diff --git a/dwl.c b/dwl.c +index a2711f67..d749728a 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -200,6 +200,10 @@ struct Monitor { + struct wlr_box w; /* window area, layout-relative */ + struct wl_list layers[4]; /* LayerSurface.link */ + const Layout *lt[2]; ++ int gappih; /* horizontal gap between windows */ ++ int gappiv; /* vertical gap between windows */ ++ int gappoh; /* horizontal outer gaps */ ++ int gappov; /* vertical outer gaps */ + unsigned int seltags; + unsigned int sellt; + uint32_t tagset[2]; +@@ -273,6 +277,7 @@ static void createpopup(struct wl_listener *listener, void *data); + static void cursorconstrain(struct wlr_pointer_constraint_v1 *constraint); + static void cursorframe(struct wl_listener *listener, void *data); + static void cursorwarptohint(void); ++static void defaultgaps(const Arg *arg); + static void destroydecoration(struct wl_listener *listener, void *data); + static void destroydragicon(struct wl_listener *listener, void *data); + static void destroyidleinhibitor(struct wl_listener *listener, void *data); +@@ -293,6 +298,13 @@ static void fullscreennotify(struct wl_listener *listener, void *data); + static void gpureset(struct wl_listener *listener, void *data); + static void handlesig(int signo); + static void incnmaster(const Arg *arg); ++static void incgaps(const Arg *arg); ++static void incigaps(const Arg *arg); ++static void incihgaps(const Arg *arg); ++static void incivgaps(const Arg *arg); ++static void incogaps(const Arg *arg); ++static void incohgaps(const Arg *arg); ++static void incovgaps(const Arg *arg); + static void inputdevice(struct wl_listener *listener, void *data); + static int keybinding(uint32_t mods, xkb_keysym_t sym); + static void keypress(struct wl_listener *listener, void *data); +@@ -327,6 +339,7 @@ static void setcursorshape(struct wl_listener *listener, void *data); + static void setfloating(Client *c, int floating); + static void setfullscreen(Client *c, int fullscreen); + static void setgamma(struct wl_listener *listener, void *data); ++static void setgaps(int oh, int ov, int ih, int iv); + static void setlayout(const Arg *arg); + static void setmfact(const Arg *arg); + static void setmon(Client *c, Monitor *m, uint32_t newtags); +@@ -340,6 +353,7 @@ static void tagmon(const Arg *arg); + static void tile(Monitor *m); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); ++static void togglegaps(const Arg *arg); + static void toggletag(const Arg *arg); + static void toggleview(const Arg *arg); + static void unlocksession(struct wl_listener *listener, void *data); +@@ -413,6 +427,8 @@ static struct wlr_box sgeom; + static struct wl_list mons; + static Monitor *selmon; + ++static int enablegaps = 1; /* enables gaps, used by togglegaps */ ++ + #ifdef XWAYLAND + static void activatex11(struct wl_listener *listener, void *data); + static void associatex11(struct wl_listener *listener, void *data); +@@ -989,6 +1005,11 @@ createmon(struct wl_listener *listener, void *data) + for (i = 0; i < LENGTH(m->layers); i++) + wl_list_init(&m->layers[i]); + ++ m->gappih = gappih; ++ m->gappiv = gappiv; ++ m->gappoh = gappoh; ++ m->gappov = gappov; ++ + wlr_output_state_init(&state); + /* Initialize monitor state using configured rules */ + m->tagset[0] = m->tagset[1] = 1; +@@ -1171,6 +1192,12 @@ cursorwarptohint(void) + } + } + ++void ++defaultgaps(const Arg *arg) ++{ ++ setgaps(gappoh, gappov, gappih, gappiv); ++} ++ + void + destroydecoration(struct wl_listener *listener, void *data) + { +@@ -1524,6 +1551,83 @@ incnmaster(const Arg *arg) + arrange(selmon); + } + ++void ++incgaps(const Arg *arg) ++{ ++ setgaps( ++ selmon->gappoh + arg->i, ++ selmon->gappov + arg->i, ++ selmon->gappih + arg->i, ++ selmon->gappiv + arg->i ++ ); ++} ++ ++void ++incigaps(const Arg *arg) ++{ ++ setgaps( ++ selmon->gappoh, ++ selmon->gappov, ++ selmon->gappih + arg->i, ++ selmon->gappiv + arg->i ++ ); ++} ++ ++void ++incihgaps(const Arg *arg) ++{ ++ setgaps( ++ selmon->gappoh, ++ selmon->gappov, ++ selmon->gappih + arg->i, ++ selmon->gappiv ++ ); ++} ++ ++void ++incivgaps(const Arg *arg) ++{ ++ setgaps( ++ selmon->gappoh, ++ selmon->gappov, ++ selmon->gappih, ++ selmon->gappiv + arg->i ++ ); ++} ++ ++void ++incogaps(const Arg *arg) ++{ ++ setgaps( ++ selmon->gappoh + arg->i, ++ selmon->gappov + arg->i, ++ selmon->gappih, ++ selmon->gappiv ++ ); ++} ++ ++void ++incohgaps(const Arg *arg) ++{ ++ setgaps( ++ selmon->gappoh + arg->i, ++ selmon->gappov, ++ selmon->gappih, ++ selmon->gappiv ++ ); ++} ++ ++void ++incovgaps(const Arg *arg) ++{ ++ setgaps( ++ selmon->gappoh, ++ selmon->gappov + arg->i, ++ selmon->gappih, ++ selmon->gappiv ++ ); ++} ++ + void + inputdevice(struct wl_listener *listener, void *data) + { +@@ -2352,6 +2456,16 @@ setgamma(struct wl_listener *listener, void *data) + wlr_output_schedule_frame(m->wlr_output); + } + ++void ++setgaps(int oh, int ov, int ih, int iv) ++{ ++ selmon->gappoh = MAX(oh, 0); ++ selmon->gappov = MAX(ov, 0); ++ selmon->gappih = MAX(ih, 0); ++ selmon->gappiv = MAX(iv, 0); ++ arrange(selmon); ++} ++ + void + setlayout(const Arg *arg) + { +@@ -2691,7 +2805,7 @@ tagmon(const Arg *arg) + void + tile(Monitor *m) + { +- unsigned int mw, my, ty; ++ unsigned int mw, my, ty, h, r, oe = enablegaps, ie = enablegaps; + int i, n = 0; + Client *c; + +@@ -2701,22 +2815,31 @@ tile(Monitor *m) + if (n == 0) + return; + ++ if (smartgaps == n) { ++ oe = 0; // outer gaps disabled ++ } ++ + if (n > m->nmaster) +- mw = m->nmaster ? (int)roundf(m->w.width * m->mfact) : 0; ++ mw = m->nmaster ? (int)roundf((m->w.width + m->gappiv*ie) * m->mfact) : 0; + else +- mw = m->w.width; +- i = my = ty = 0; ++ mw = m->w.width - 2*m->gappov*oe + m->gappiv*ie; ++ i = 0; ++ my = ty = m->gappoh*oe; + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) + continue; + if (i < m->nmaster) { +- resize(c, (struct wlr_box){.x = m->w.x, .y = m->w.y + my, .width = mw, +- .height = (m->w.height - my) / (MIN(n, m->nmaster) - i)}, 0); +- my += c->geom.height; ++ r = MIN(n, m->nmaster) - i; ++ h = (m->w.height - my - m->gappoh*oe - m->gappih*ie * (r - 1)) / r; ++ resize(c, (struct wlr_box){.x = m->w.x + m->gappov*oe, .y = m->w.y + my, ++ .width = mw - m->gappiv*ie, .height = h}, 0); ++ my += c->geom.height + m->gappih*ie; + } else { +- resize(c, (struct wlr_box){.x = m->w.x + mw, .y = m->w.y + ty, +- .width = m->w.width - mw, .height = (m->w.height - ty) / (n - i)}, 0); +- ty += c->geom.height; ++ r = n - i; ++ h = (m->w.height - ty - m->gappoh*oe - m->gappih*ie * (r - 1)) / r; ++ resize(c, (struct wlr_box){.x = m->w.x + mw + m->gappov*oe, .y = m->w.y + ty, ++ .width = m->w.width - mw - 2*m->gappov*oe, .height = h}, 0); ++ ty += c->geom.height + m->gappih*ie; + } + i++; + } +@@ -2739,6 +2862,13 @@ togglefullscreen(const Arg *arg) + setfullscreen(sel, !sel->isfullscreen); + } + ++void ++togglegaps(const Arg *arg) ++{ ++ enablegaps = !enablegaps; ++ arrange(selmon); ++} ++ + void + toggletag(const Arg *arg) + { +-- +2.46.0 + + +From 8b9db986eddeade22d92fb15a8c836912869be29 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Leonardo=20Hern=C3=A1ndez=20Hern=C3=A1ndez?= + +Date: Wed, 20 Jul 2022 00:15:32 -0500 +Subject: [PATCH 2/2] allow gaps in monocle layout if requested +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Signed-off-by: Leonardo Hernández Hernández +--- + config.def.h | 1 + + dwl.c | 6 +++++- + 2 files changed, 6 insertions(+), 1 deletion(-) + +diff --git a/config.def.h b/config.def.h +index 39e528b1..f4d4095d 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -7,6 +7,7 @@ + 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 int smartgaps = 0; /* 1 means no outer gap when there is only one window */ ++static const int monoclegaps = 0; /* 1 means outer gaps in monocle layout */ + static const unsigned int borderpx = 1; /* border pixel of windows */ + static const unsigned int gappih = 10; /* horiz inner gap between windows */ + static const unsigned int gappiv = 10; /* vert inner gap between windows */ +diff --git a/dwl.c b/dwl.c +index d749728a..dbc66ef8 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -1879,8 +1879,12 @@ monocle(Monitor *m) + wl_list_for_each(c, &clients, link) { + if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) + continue; +- resize(c, m->w, 0); + n++; ++ if (!monoclegaps) ++ resize(c, m->w, 0); ++ else ++ resize(c, (struct wlr_box){.x = m->w.x + gappoh, .y = m->w.y + gappov, ++ .width = m->w.width - 2 * gappoh, .height = m->w.height - 2 * gappov}, 0); + } + if (n) + snprintf(m->ltsymbol, LENGTH(m->ltsymbol), "[%d]", n); +-- +2.46.0 + -- cgit