commit 3b54925cbf35d15232908b7ff996ef354d5b4fab from: Brett Fisher date: Wed May 13 20:03:42 2026 UTC bxwm.c: - Implement socket-based Inter-Process Communication (IPC) for command execution. - Replace blocking XNextEvent() loop with poll() multiplexing to watch both X11 events and a new Unix domain socket. - Add socket_init() and handle_socket_command() to manage IPC. - Migrate window management commands to be callable via socket strings. - Refactor event loop to handle X11 events and socket commands concurrently. commit - 78a11878fa94c94014b7a0364319fb5025821916 commit + 3b54925cbf35d15232908b7ff996ef354d5b4fab blob - 1b19669b48acd0d65bfc6423ef3f8ff04efbe10b blob + 0812932392a17514093d9fe4d36951657c2a5705 --- bxwm.c +++ bxwm.c @@ -8,8 +8,13 @@ #include #include #include -#include +#include #include +#include +#include +#include +#include +#include #include #include #include @@ -18,6 +23,8 @@ #include "config.h" +#define SOCKET_SUFFIX "/.local/tmp/bxwm.sock" + /* Layout types */ enum { FLOAT, CENTER, LEFT_HALF, RIGHT_HALF, SMALL, MAXIMIZE }; @@ -43,6 +50,9 @@ static unsigned long focus_pixel; static unsigned long unfocus_pixel; static int curws = INITIAL_WORKSPACE; static int ws_focus_idx[NUM_WORKSPACES] = {-1}; /* Index of last focused per WS */ +static int server_fd; +static char socket_path[256]; +static int running; /* EWMH atoms */ static Atom net_supported, net_number_of_desktops, net_current_desktop; @@ -55,9 +65,10 @@ static Atom wm_protocols, wm_delete_window; /* Forward function declarations */ static void logmsg(const char *fmt, ...); -static void run(void); -static void cleanup(int status); +int socket_init(void); static void setup(void); +static void handle_socket_command(int fd); +static void cleanup(int status); static int xerror(Display *dpy, XErrorEvent *ee); static void update_workarea(void); static void manage(Window w); @@ -76,18 +87,161 @@ static void movetows(int ws); static void unmanage(Window w); static void update_client_list(void); -int -main(void) { - signal(SIGTERM, cleanup); - dpy = XOpenDisplay(NULL); - if (!dpy) { - fprintf(stderr, "bxwm: cannot open display '%s'\n", XDisplayName(NULL)); - return 1; - } +int +main(int argc, char *argv[]) +{ + (void)argc; + (void)argv; + + XEvent ev; + + // 1. Initial setup + if (!(dpy = XOpenDisplay(NULL))) exit(1); root = DefaultRootWindow(dpy); + + // 2. Initialize Socket and Poll + server_fd = socket_init(); + int x_fd = ConnectionNumber(dpy); + + struct pollfd fds[2]; + fds[0].fd = x_fd; + fds[0].events = POLLIN; + fds[1].fd = server_fd; + fds[1].events = POLLIN; + + // 3. User configuration/setup (key grabs, etc) setup(); - run(); + + /* Cache keycodes */ + KeyCode kcodes[11]; + KeyCode ws_codes[10]; + int i, w; + + for (i = 0; i < 11; i++) + kcodes[i] = XKeysymToKeycode(dpy, XStringToKeysym( + i == 0 ? "Return" : + i == 1 ? "Q" : + i == 2 ? "C" : + i == 3 ? "H" : + i == 4 ? "L" : + i == 5 ? "S" : + i == 6 ? "M" : + i == 7 ? "J" : + i == 8 ? "K" : + i == 9 ? "X" : "P")); + + for (w = 0; w < 10; w++) { + KeySym ks = (w == 9) ? XK_0 : (XK_1 + w); + ws_codes[w] = XKeysymToKeycode(dpy, ks); + } + + running = 1; + + // 4. Main Loop + while (running) { + if (poll(fds, 2, -1) < 0) { + if (errno == EINTR) continue; + break; + } + + // Handle X11 events + if (fds[0].revents & POLLIN) { + while (XPending(dpy)) { + XNextEvent(dpy, &ev); + if (ev.type == KeyPress) { + /* Workspace view keys */ + for (w = 0; w < 10; w++) { + if (ev.xkey.keycode == ws_codes[w] && + (ev.xkey.state & Mod4Mask) && + !(ev.xkey.state & ShiftMask)) { + view(w); + goto processed; + } + } + /* Workspace move keys */ + for (w = 0; w < 10; w++) { + if (ev.xkey.keycode == ws_codes[w] && + (ev.xkey.state & (Mod4Mask | ShiftMask)) == + (Mod4Mask | ShiftMask)) { + movetows(w); + goto processed; + } + } + /* Action keys */ + for (i = 0; i < 11; i++) { + if (ev.xkey.keycode == kcodes[i]) { + switch (i) { + case 0: + if ((ev.xkey.state & (ShiftMask | Mod1Mask)) == (ShiftMask | Mod1Mask)) + spawn(TERMINAL); + break; + case 1: + if ((ev.xkey.state & (ControlMask | ShiftMask)) == (ControlMask | ShiftMask)) + cleanup(0); + break; + case 2: + if ((ev.xkey.state & (ControlMask | Mod1Mask)) == (ControlMask | Mod1Mask)) + center_window(focused_client); + break; + case 3: + if ((ev.xkey.state & (ControlMask | Mod1Mask)) == (ControlMask | Mod1Mask)) + left_half_window(focused_client); + break; + case 4: + if ((ev.xkey.state & (ControlMask | Mod1Mask)) == (ControlMask | Mod1Mask)) + right_half_window(focused_client); + break; + case 5: + if ((ev.xkey.state & (ControlMask | Mod1Mask)) == (ControlMask | Mod1Mask)) + small_window(focused_client); + break; + case 6: + if ((ev.xkey.state & (ControlMask | Mod1Mask)) == (ControlMask | Mod1Mask)) + maximize_window(focused_client); + break; + case 7: + if ((ev.xkey.state & (ControlMask | Mod1Mask)) == (ControlMask | Mod1Mask)) + focus_next(); + break; + case 8: + if ((ev.xkey.state & (ControlMask | Mod1Mask)) == (ControlMask | Mod1Mask)) + focus_prev(); + break; + case 9: + if ((ev.xkey.state & (ControlMask | Mod1Mask)) == (ControlMask | Mod1Mask)) + close_window(focused_client); + break; + case 10: + if ((ev.xkey.state & (ShiftMask | Mod1Mask)) == (ShiftMask | Mod1Mask)) + spawn(LAUNCHPROGRAM); + break; + } + goto processed; + } + } + processed:; + } else if (ev.type == MapRequest) { + manage(ev.xmaprequest.window); + } else if (ev.type == DestroyNotify) { + unmanage(ev.xdestroywindow.window); + } else if (ev.type == PropertyNotify) { + if (ev.xproperty.atom == net_wm_strut_partial) { + update_workarea(); + } + } + } + } + + // Handle socket commands + if (fds[1].revents & POLLIN) { + handle_socket_command(server_fd); + } + } + + // 5. Final Cleanup cleanup(0); + close(server_fd); + XCloseDisplay(dpy); return 0; } @@ -108,6 +262,51 @@ logmsg(const char *fmt, ...) { static void logmsg(const char *fmt, ...) { (void)fmt; } #endif +int socket_init(void) { + struct sockaddr_un addr; + int server_fd; + char socket_path[256]; + + // Build the socket path + char *home = getenv("HOME"); + if (home == NULL) { + fprintf(stderr, "bxwm: HOME not set\n"); + exit(1); + } + snprintf(socket_path, sizeof(socket_path), "%s%s", home, SOCKET_SUFFIX); + + // Remove stale socket from previous session + unlink(socket_path); + + // Create the socket + server_fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (server_fd < 0) { + perror("bxwm: socket"); + exit(1); + } + + // Bind the socket to the path + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1); + + if (bind(server_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + perror("bxwm: bind"); + close(server_fd); + exit(1); + } + + // Listen for incoming connections + if (listen(server_fd, 5) < 0) { + perror("bxwm: listen"); + close(server_fd); + unlink(socket_path); + exit(1); + } + + return server_fd; +} + static void setup(void) { XSetWindowAttributes wa; @@ -727,118 +926,6 @@ xerror(Display *dpy, XErrorEvent *ee) { } static void -run(void) { - XEvent ev; - KeyCode kcodes[11]; - KeyCode ws_codes[10]; - int i, w; - - /* Cache keycodes */ - for (i = 0; i < 11; i++) - kcodes[i] = XKeysymToKeycode(dpy, XStringToKeysym( - i == 0 ? "Return" : - i == 1 ? "Q" : - i == 2 ? "C" : - i == 3 ? "H" : - i == 4 ? "L" : - i == 5 ? "S" : - i == 6 ? "M" : - i == 7 ? "J" : - i == 8 ? "K" : - i == 9 ? "X" : "P")); - - for (w = 0; w < 10; w++) { - KeySym ks = (w == 9) ? XK_0 : (XK_1 + w); - ws_codes[w] = XKeysymToKeycode(dpy, ks); - } - - for (;;) { - XNextEvent(dpy, &ev); - if (ev.type == KeyPress) { - /* Workspace view keys (Super+Shift+1..0) */ - for (w = 0; w < 10; w++) { - if (ev.xkey.keycode == ws_codes[w] && - (ev.xkey.state & Mod4Mask) && - !(ev.xkey.state & ShiftMask)) { - view(w); - goto processed; - } - } - /* Workspace move keys (Super+Shift+1..0) */ - for (w = 0; w < 10; w++) { - if (ev.xkey.keycode == ws_codes[w] && - (ev.xkey.state & (Mod4Mask | ShiftMask)) == - (Mod4Mask | ShiftMask)) { - movetows(w); - goto processed; - } - } - /* Existing key handling */ - for (i = 0; i < 11; i++) { - if (ev.xkey.keycode == kcodes[i]) { - switch (i) { - case 0: - if ((ev.xkey.state & (ShiftMask | Mod1Mask)) == (ShiftMask | Mod1Mask)) - spawn(TERMINAL); - break; - case 1: - if ((ev.xkey.state & (ControlMask | ShiftMask)) == (ControlMask | ShiftMask)) - cleanup(0); - break; - case 2: - if ((ev.xkey.state & (ControlMask | Mod1Mask)) == (ControlMask | Mod1Mask)) - center_window(focused_client); - break; - case 3: - if ((ev.xkey.state & (ControlMask | Mod1Mask)) == (ControlMask | Mod1Mask)) - left_half_window(focused_client); - break; - case 4: - if ((ev.xkey.state & (ControlMask | Mod1Mask)) == (ControlMask | Mod1Mask)) - right_half_window(focused_client); - break; - case 5: - if ((ev.xkey.state & (ControlMask | Mod1Mask)) == (ControlMask | Mod1Mask)) - small_window(focused_client); - break; - case 6: - if ((ev.xkey.state & (ControlMask | Mod1Mask)) == (ControlMask | Mod1Mask)) - maximize_window(focused_client); - break; - case 7: - if ((ev.xkey.state & (ControlMask | Mod1Mask)) == (ControlMask | Mod1Mask)) - focus_next(); - break; - case 8: - if ((ev.xkey.state & (ControlMask | Mod1Mask)) == (ControlMask | Mod1Mask)) - focus_prev(); - break; - case 9: - if ((ev.xkey.state & (ControlMask | Mod1Mask)) == (ControlMask | Mod1Mask)) - close_window(focused_client); - break; - case 10: - if ((ev.xkey.state & (ShiftMask | Mod1Mask)) == (ShiftMask | Mod1Mask)) - spawn(LAUNCHPROGRAM); - break; - } - goto processed; - } - } - processed:; - } else if (ev.type == MapRequest) { - manage(ev.xmaprequest.window); - } else if (ev.type == DestroyNotify) { - unmanage(ev.xdestroywindow.window); - } else if (ev.type == PropertyNotify) { - if (ev.xproperty.atom == net_wm_strut_partial) { - update_workarea(); - } - } - } -} - -static void update_client_list(void) { Window *wins = NULL; unsigned int i; @@ -931,14 +1018,65 @@ update_workarea(void) { arrange(); } +static void +handle_socket_command(int server_fd) +{ + int client_fd = accept(server_fd, NULL, NULL); + if (client_fd < 0) return; -static void + char buffer[256]; + memset(buffer, 0, sizeof(buffer)); + + if (read(client_fd, buffer, sizeof(buffer) - 1) > 0) { + buffer[strcspn(buffer, "\n")] = '\0'; + fprintf(stderr, "bxwm: socket received '%s'\n", buffer); + + // Map command strings to functions + if (strcmp(buffer, "center_window") == 0) { + center_window(focused_client); + } else if (strcmp(buffer, "left_half") == 0) { + left_half_window(focused_client); + } else if (strcmp(buffer, "right_half") == 0) { + right_half_window(focused_client); + } else if (strcmp(buffer, "small_window") == 0) { + small_window(focused_client); + } else if (strcmp(buffer, "maximize_window") == 0) { + maximize_window(focused_client); + } else if (strcmp(buffer, "focus_next") == 0) { + focus_next(); + } else if (strcmp(buffer, "focus_prev") == 0) { + focus_prev(); + } else if (strcmp(buffer, "close_window") == 0) { + close_window(focused_client); + } else if (strcmp(buffer, "spawn_terminal") == 0) { + spawn(TERMINAL); + } else if (strcmp(buffer, "spawn_launcher") == 0) { + spawn(LAUNCHPROGRAM); + } else if (strcmp(buffer, "quit") == 0) { + running = 0; + } else if (strncmp(buffer, "view ", 5) == 0) { + int ws = atoi(buffer + 5); + if (ws >= 0 && ws <= 9) + view(ws); + } else if (strncmp(buffer, "movetows ", 9) == 0) { + int ws = atoi(buffer + 9); + if (ws >= 0 && ws <= 9) + movetows(ws); + } + } + XSync(dpy, False); + close(client_fd); +} + +static void cleanup(int status) { if (dpy) { if (clients) free(clients); XCloseDisplay(dpy); } + unlink(socket_path); + close(server_fd); fprintf(stderr, "bxwm: exit\n"); exit(status); }