commit 7bb4fd609f572b48d95a3328750a34a7add0916e from: Brett Fisher date: Tue May 26 07:48:41 2026 UTC bxnotify.c: Implement vertical stacking - Enable concurrent notifications to stack vertically without overlapping or race conditions by introducing a locked registry file. - Add registry system: Use ~/.local/tmp/bxnotify.reg to coordinate Y positions between concurrent instances. - Prevent race conditions by implementing flock(2) (exclusive lock) to atomically read, calculate, and write window positions. - Use kill(pid, 0) to garbage-collect stale PIDs of crashed or closed notifications from the registry for a self-cleaing state. - Apply XSetClassHint so the window is properly identified as “bxnotify”. - Refactor setup() to accept y as an argument calculated before window mapping, removing hardcoded WIN_Y positioning. commit - 7b13656e3ef0f51754993932e6c95f5a3d71f894 commit + 7bb4fd609f572b48d95a3328750a34a7add0916e blob - c6c6d155c3a77956ad8ec22c5b60a818ede9a9ed blob + ee6b7aad582c69a1d19eb4900b05e370dafcdfbf --- bxnotify.c +++ bxnotify.c @@ -6,6 +6,7 @@ #define _POSIX_C_SOURCE 200809L +#include #include #include #include @@ -13,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -35,6 +37,7 @@ static int running = 1; static TextBlock tb; static int total_height; static time_t start_time; +static char reg_path[256]; /* Forward Declarations */ static void die(const char *fmt, ...); @@ -43,7 +46,10 @@ static void sigusr1(int sig); static int measure_text(const char *text); static void init_display(void); static void calc_layout(void); -static void setup(int win_h); +static void init_reg_path(void); +static int get_and_register_stack_y(int my_h); +static void unregister_position(void); +static void setup(int y, int win_h); static void readstdin(void); static void draw(void); static void run(void); @@ -58,7 +64,9 @@ main(int argc, char *argv[]) readstdin(); init_display(); calc_layout(); - setup(total_height); + init_reg_path(); + int y = get_and_register_stack_y(total_height); + setup(y, total_height); start_time = time(NULL); run(); @@ -80,6 +88,8 @@ die(const char *fmt, ...) static void cleanup(void) { + unregister_position(); + for (int i = 0; i < tb.count; i++) free(tb.lines[i]); free(tb.lines); @@ -185,15 +195,112 @@ calc_layout(void) } static void -setup(int win_h) +init_reg_path(void) { + char *home = getenv("HOME"); + if (!home) die("bxnotify: HOME not set\n"); + snprintf(reg_path, sizeof(reg_path), "%s/.local/tmp/bxnotify.reg", home); +} + +static int +get_and_register_stack_y(int my_h) +{ + int fd = open(reg_path, O_RDWR | O_CREAT, 0644); + if (fd == -1) return WIN_Y; /* Fallback if file fails to open */ + + /* Wait for exclusive lock - blocks until no other bxnotify is reading/writing */ + flock(fd, LOCK_EX); + + int my_y = WIN_Y; + int lowest_y = 0; + + /* Store valid PIDs so we can rewrite the file cleanly */ + char valid_lines[100][64]; + int valid_count = 0; + + /* Open the file descriptor as a FILE* for easy reading with fgets */ + FILE *f = fdopen(fd, "r+"); + if (f) { + char line[64]; + int pid, y, h; + + /* 1. Read existing entries and find the lowest Y */ + while (fgets(line, sizeof(line), f) && valid_count < 100) { + if (sscanf(line, "%d %d %d", &pid, &y, &h) == 3) { + if (kill(pid, 0) == 0) { /* Process is still alive */ + if ((y + h) > lowest_y) + lowest_y = y + h; + strcpy(valid_lines[valid_count++], line); /* Keep it */ + } + } + } + + /* 2. Calculate our Y position */ + my_y = (lowest_y > 0) ? lowest_y + 16 : WIN_Y; + + /* 3. Clear file and rewrite with valid PIDs + our new one */ + ftruncate(fd, 0); /* Erase file contents */ + fseek(f, 0, SEEK_SET); /* Move cursor back to start */ + + for (int i = 0; i < valid_count; i++) { + fputs(valid_lines[i], f); + } + + fprintf(f, "%d %d %d\n", getpid(), my_y, my_h); + fflush(f); + fclose(f); /* This automatically closes the underlying fd and releases the lock */ + } else { + close(fd); /* Fallback if fdopen failed */ + } + + /* Lock is automatically released when fd is closed by fclose() */ + return my_y; +} + +static void +unregister_position(void) +{ + int fd = open(reg_path, O_RDWR | O_CREAT, 0644); + if (fd == -1) return; + + flock(fd, LOCK_EX); + + FILE *f = fdopen(fd, "r+"); + if (f) { + char line[64]; + char keep[100][64]; + int count = 0; + int my_pid = getpid(); + + while (fgets(line, sizeof(line), f) && count < 100) { + int pid; + sscanf(line, "%d", &pid); + /* Keep if it's not ours AND process is alive */ + if (pid != my_pid && kill(pid, 0) == 0) { + strcpy(keep[count++], line); + } + } + + ftruncate(fd, 0); + fseek(f, 0, SEEK_SET); + + for (int i = 0; i < count; i++) + fputs(keep[i], f); + + fflush(f); + fclose(f); + } else { + close(fd); + } +} + +static void +setup(int y, int win_h) +{ XSetWindowAttributes swa; - int x, y; int win_w = WIN_WIDTH + 2 * BORDER_WIDTH; - - x = DisplayWidth(dpy, screen) - win_w - WIN_X; - y = WIN_Y; - + int x = DisplayWidth(dpy, screen) - win_w - WIN_X; + swa.override_redirect = True; swa.background_pixel = bg.pixel; swa.border_pixel = 0; @@ -215,6 +322,9 @@ setup(int win_h) xft_draw = XftDrawCreate(dpy, win, DefaultVisual(dpy, screen), DefaultColormap(dpy, screen)); + XClassHint ch = { "bxnotify", "Bxnotify" }; + XSetClassHint(dpy, win, &ch); + XMapWindow(dpy, win); }