commit c2ca3402b92d106af09010320b729704fb5c379b from: Brett Fisher date: Sat May 30 22:38:00 2026 UTC bxnotify.c: Implement registry-aware window dismissal. - Enable intuitive dismissal of notifications using Mod4+q, respecting the vertical stack order. - Implement get_oldest_pid() to identify the top-most active instance and ensure ordered dismissal. - Introduce SIGUSR1 delegation to allow concurrent instances to communicate; non-oldest instances signal the oldest to dismiss. - Perform a global passive grab (XGrabKey on Root) to capture dismissal commands without requiring window focus. - Install a custom X error handler to gracefully ignore BadAccess errors caused by shared Root grabs across multiple instances. - Update event loop to handle KeyPress events, delegating the process termination to the identified oldest PID. commit - 7bb4fd609f572b48d95a3328750a34a7add0916e commit + c2ca3402b92d106af09010320b729704fb5c379b blob - ee6b7aad582c69a1d19eb4900b05e370dafcdfbf blob + 7e4e035b5bc8a122de59be66afa1586aa74232c2 --- bxnotify.c +++ bxnotify.c @@ -16,8 +16,9 @@ #include #include #include -#include #include +#include +#include #include "config.h" @@ -44,11 +45,13 @@ static void die(const char *fmt, ...); static void cleanup(void); static void sigusr1(int sig); static int measure_text(const char *text); +static int (*xerrorxlib)(Display *, XErrorEvent *); static void init_display(void); static void calc_layout(void); static void init_reg_path(void); static int get_and_register_stack_y(int my_h); static void unregister_position(void); +static int get_oldest_pid(void); static void setup(int y, int win_h); static void readstdin(void); static void draw(void); @@ -146,12 +149,23 @@ readstdin(void) die("bxnotify: no input received\n"); } +static int +xerror(Display *dpy, XErrorEvent *ee) +{ + /* Silently ignore BadAccess from XGrabKey - another instance has the grab */ + if (ee->error_code == BadAccess && ee->request_code == 33) + return 0; + return xerrorxlib(dpy, ee); +} + static void init_display(void) { if (!(dpy = XOpenDisplay(NULL))) die("bxnotify: cannot open display\n"); + xerrorxlib = XSetErrorHandler(xerror); + screen = DefaultScreen(dpy); if (!(font = XftFontOpenName(dpy, screen, FONT))) @@ -294,6 +308,38 @@ unregister_position(void) } } +static int +get_oldest_pid(void) +{ + int fd = open(reg_path, O_RDONLY); + if (fd == -1) return getpid(); + + flock(fd, LOCK_SH); + FILE *f = fdopen(fd, "r"); + + int min_y = 99999; + int oldest_pid = getpid(); + + if (f) { + char line[64]; + int pid, y, h; + + while (fgets(line, sizeof(line), f)) { + if (sscanf(line, "%d %d %d", &pid, &y, &h) == 3) { + if (kill(pid, 0) == 0 && y < min_y) { + min_y = y; + oldest_pid = pid; + } + } + } + fclose(f); + } else { + close(fd); + } + + return oldest_pid; +} + static void setup(int y, int win_h) { @@ -304,7 +350,7 @@ setup(int y, int win_h) swa.override_redirect = True; swa.background_pixel = bg.pixel; swa.border_pixel = 0; - swa.event_mask = ExposureMask; + swa.event_mask = ExposureMask | KeyPressMask; win = XCreateWindow(dpy, RootWindow(dpy, screen), x, y, win_w, win_h, 0, @@ -326,6 +372,9 @@ setup(int y, int win_h) XSetClassHint(dpy, win, &ch); XMapWindow(dpy, win); + + /* Register the passive grab for key press Mod4+q */ + XGrabKey(dpy, XKeysymToKeycode(dpy, XK_q), Mod4Mask, RootWindow(dpy, screen), True, GrabModeAsync, GrabModeAsync); } static void @@ -359,20 +408,29 @@ run(void) { XEvent ev; + KeySym q = XStringToKeysym("q"); + while (running) { if (XPending(dpy)) { XNextEvent(dpy, &ev); - if (ev.type == Expose) + if (ev.type == Expose) { draw(); + } else if (ev.type == KeyPress) { + if (XLookupKeysym(&ev.xkey, 0) == q && (ev.xkey.state & Mod4Mask)) { + int oldest = get_oldest_pid(); + if (oldest==getpid()) + running = 0; + else if (oldest > 0) + kill(oldest, SIGUSR1); + } + } } - /* Check duration timeout */ if (DURATION > 0 && (time(NULL) - start_time) >= DURATION) running = 0; struct timespec ts = {0, 10000000}; /* 10 milliseconds */ nanosleep(&ts, NULL); - } }