commit a2df4b2f53b80bce268b88f369f1c4fda6f06ca0 from: Brett Fisher date: Wed May 13 18:47:47 2026 UTC bxwm.c: - Implement detection of status bar (dock) and geometry for window layouts to allow for status bar (dock). - Add support for _NET_WM_STRUT_PARTIAL and _NET_WM_STRUT. - Implement update_workarea() to track usable screen geometry. - Add _NET_WM_WINDOW_TYPE_DOCK support to identify bar/dock windows. - Refactor layout functions (center, half, small, maximize) to use work area. - Wire PropertyNotify events to automatically update geometry when struts change. - Disable placement logic for dock windows in manage(). commit - 66bbd5426a0538e0a9b8bb43bb50204ef13fdc41 commit + a2df4b2f53b80bce268b88f369f1c4fda6f06ca0 blob - 600e835e7dbe1e8824b3cca52d858609b5fa586e blob + f40494588161c557dbdd73e9ce09016bcd9beb81 --- bxwm.c +++ bxwm.c @@ -1,7 +1,5 @@ -/* bxwm.c - * +/* * A very basic X window manager, a.k.a. Brett's X window manager. - * * See LICENSE.md and README.md for details. */ @@ -22,6 +20,7 @@ typedef struct { Window win; int focused; int ws; + int is_dock; /* dock/bar window: no border, no positioning, no focus */ } Client; /* Globals */ @@ -29,6 +28,7 @@ static Display *dpy; static Window root; static int screen; static unsigned long screen_w, screen_h; +static unsigned long wa_x, wa_y, wa_w, wa_h; /* work area (screen minus struts) */ static Client *clients = NULL; static unsigned int num_clients = 0; static Client *focused_client = NULL; @@ -40,6 +40,8 @@ static int ws_focus_idx[NUM_WORKSPACES] = {-1}; /* Ind /* EWMH atoms */ static Atom net_supported, net_number_of_desktops, net_current_desktop; static Atom net_client_list, net_active_window, net_wm_desktop; +static Atom net_wm_strut_partial, net_wm_strut, net_workarea; +static Atom net_wm_window_type, net_wm_window_type_dock; /* ICCCM atoms */ static Atom wm_protocols, wm_delete_window; @@ -49,6 +51,7 @@ static void run(void); static void cleanup(int status); static void setup(void); static int xerror(Display *dpy, XErrorEvent *ee); +static void update_workarea(void); static void manage(Window w); static void spawn(const char *cmd); static void focus_client(Client *c); @@ -109,6 +112,11 @@ static void setup(void) { screen_w = DisplayWidth(dpy, screen); screen_h = DisplayHeight(dpy, screen); + wa_x = 0; + wa_y = 0; + wa_w = screen_w; + wa_h = screen_h; + XSetErrorHandler(xerror); if (XGetSelectionOwner(dpy, XInternAtom(dpy, "WM_S0", False))) { @@ -134,7 +142,8 @@ static void setup(void) { /* Root window event mask */ wa.event_mask = SubstructureRedirectMask | SubstructureNotifyMask | ButtonPressMask | PointerMotionMask | EnterWindowMask | - LeaveWindowMask | KeyPressMask | KeyReleaseMask; + LeaveWindowMask | KeyPressMask | KeyReleaseMask | + PropertyChangeMask; XChangeWindowAttributes(dpy, root, CWEventMask, &wa); /* Set root cursor */ @@ -171,12 +180,20 @@ static void setup(void) { net_wm_desktop = XInternAtom(dpy, "_NET_WM_DESKTOP", False); wm_protocols = XInternAtom(dpy, "WM_PROTOCOLS", False); wm_delete_window = XInternAtom(dpy, "WM_DELETE_WINDOW", False); + net_wm_strut_partial = XInternAtom(dpy, "_NET_WM_STRUT_PARTIAL", False); + net_wm_strut = XInternAtom(dpy, "_NET_WM_STRUT", False); + net_workarea = XInternAtom(dpy, "_NET_WORKAREA", False); + net_wm_window_type = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", False); + net_wm_window_type_dock = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_DOCK", False); /* Set EWMH properties on root window */ Atom supported[] = { net_supported, net_number_of_desktops, net_current_desktop, - net_client_list, net_active_window, net_wm_desktop + net_client_list, net_active_window, net_wm_desktop, + net_wm_strut_partial, net_wm_strut, net_workarea, + net_wm_window_type, net_wm_window_type_dock }; + XChangeProperty(dpy, root, net_supported, XA_ATOM, 32, PropModeReplace, (unsigned char *)supported, sizeof(supported) / sizeof(Atom)); @@ -196,6 +213,8 @@ static void setup(void) { XChangeProperty(dpy, root, net_active_window, XA_WINDOW, 32, PropModeReplace, (unsigned char *)&active, 1); + update_workarea(); + #ifdef DEBUG fprintf(stderr, "bxwm: setup complete\n"); #endif @@ -215,6 +234,25 @@ static void manage(Window w) { if (!XGetWindowAttributes(dpy, w, &wa)) return; + /* Check if window is a dock/bar */ + int is_dock = 0; + Atom actual_type; + int actual_format; + unsigned long nitems, bytes_after; + unsigned char *data = NULL; + + if (XGetWindowProperty(dpy, w, net_wm_window_type, 0, 1, False, + XA_ATOM, &actual_type, &actual_format, &nitems, &bytes_after, + &data) == Success && data) { + if (nitems >= 1 && ((Atom *)data)[0] == net_wm_window_type_dock) + is_dock = 1; + XFree(data); + } + + /* Events only (border pixel set via Configure later) */ + attrs.event_mask = StructureNotifyMask | EnterWindowMask | PropertyChangeMask; + XChangeWindowAttributes(dpy, w, CWEventMask, &attrs); + clients = realloc(clients, ++num_clients * sizeof(Client)); if (!clients) { fprintf(stderr, "bxwm: realloc failed\n"); @@ -222,24 +260,20 @@ static void manage(Window w) { } c = &clients[num_clients - 1]; c->win = w; - c->ws = curws; /* Assign to current workspace */ + c->ws = curws; + c->is_dock = is_dock; - /* Events only (border pixel set via Configure later) */ - attrs.event_mask = StructureNotifyMask | EnterWindowMask | PropertyChangeMask; - XChangeWindowAttributes(dpy, w, CWEventMask, &attrs); + if (!is_dock) { + /* Golden ratio initial placement, centered */ + win_h = (wa_h * INITIAL_HEIGHT) - (2 * BORDER_WIDTH); + win_w = (win_h * PHI) - (2 * BORDER_WIDTH); + if (win_w > (wa_w * 0.8) - (2 * BORDER_WIDTH)) { + win_w = (wa_w * 0.8) - (2 * BORDER_WIDTH); + win_h = (win_w / PHI) - (2 * BORDER_WIDTH); + } + win_x = wa_x + (wa_w - (win_w + 2 * BORDER_WIDTH)) / 2; + win_y = wa_y + (wa_h - (win_h + 2 * BORDER_WIDTH)) / 2; - /* Golden ratio initial placement, centered */ - win_h = (screen_h * INITIAL_HEIGHT) - (2 * BORDER_WIDTH); - win_w = (win_h * PHI) - (2 * BORDER_WIDTH); - if (win_w > (screen_w * 0.8) - (2 * BORDER_WIDTH)) { - win_w = (screen_w * 0.8) - (2 * BORDER_WIDTH); - win_h = (win_w / PHI) - (2 * BORDER_WIDTH); - } - win_x = (screen_w - (win_w + 2 * BORDER_WIDTH)) / 2; - win_y = (screen_h - (win_h + 2 * BORDER_WIDTH)) / 2; - - /* Batch: pos + size + border_width + stack Above */ - { XWindowChanges wc = { .x = win_x, .y = win_y, @@ -252,13 +286,16 @@ static void manage(Window w) { XConfigureWindow(dpy, w, mask, &wc); } - XMapWindow(dpy, w); /* Now map (configure already applied) */ + XMapWindow(dpy, w); - focus_client(c); /* Border color + XRaise redundant (already Above), but keeps focus/input */ + if (!is_dock) { + focus_client(c); + long ws = curws; + XChangeProperty(dpy, w, net_wm_desktop, XA_CARDINAL, 32, + PropModeReplace, (unsigned char *)&ws, 1); + } - long ws = curws; - XChangeProperty(dpy, w, net_wm_desktop, XA_CARDINAL, 32, - PropModeReplace, (unsigned char *)&ws, 1); + update_workarea(); update_client_list(); } @@ -286,9 +323,9 @@ static void focus_client(Client *c) { } static void center_window(Client *c) { - unsigned int win_w = (screen_w / 2) - (2 * BORDER_WIDTH); - int win_x = (screen_w - (win_w + 2 * BORDER_WIDTH)) / 2; - int win_y = 0; + unsigned int win_w = (wa_w / 2) - (2 * BORDER_WIDTH); + int win_x = wa_x + (wa_w - (win_w + 2 * BORDER_WIDTH)) / 2; + int win_y = wa_y; if (!c) return; @@ -301,7 +338,7 @@ static void center_window(Client *c) { .x = win_x, .y = win_y, .width = win_w, - .height = screen_h - (2 * BORDER_WIDTH), + .height = wa_h - (2 * BORDER_WIDTH), .border_width = BORDER_WIDTH, .stack_mode = Above }; @@ -312,9 +349,9 @@ static void center_window(Client *c) { } static void left_half_window(Client *c) { - unsigned int win_w = (screen_w / 2) - (2 * BORDER_WIDTH); - int win_x = 0; - int win_y = 0; + unsigned int win_w = (wa_w / 2) - (2 * BORDER_WIDTH); + int win_x = wa_x; + int win_y = wa_y; if (!c) return; @@ -327,7 +364,7 @@ static void left_half_window(Client *c) { .x = win_x, .y = win_y, .width = win_w, - .height = screen_h - (2 * BORDER_WIDTH), + .height = wa_h - (2 * BORDER_WIDTH), .border_width = BORDER_WIDTH, .stack_mode = Above }; @@ -338,9 +375,9 @@ static void left_half_window(Client *c) { } static void right_half_window(Client *c) { - unsigned int win_w = (screen_w / 2) - (2 * BORDER_WIDTH); - int win_x = screen_w - win_w - (2 * BORDER_WIDTH); - int win_y = 0; + unsigned int win_w = (wa_w / 2) - (2 * BORDER_WIDTH); + int win_x = wa_x + wa_w - win_w - (2 * BORDER_WIDTH); + int win_y = wa_y; if (!c) return; @@ -353,7 +390,7 @@ static void right_half_window(Client *c) { .x = win_x, .y = win_y, .width = win_w, - .height = screen_h - (2 * BORDER_WIDTH), + .height = wa_h - (2 * BORDER_WIDTH), .border_width = BORDER_WIDTH, .stack_mode = Above }; @@ -370,24 +407,24 @@ static void small_window(Client *c) { if (!c) return; /* Step 1: Calculate height first (50% of screen, minus borders) */ - win_h = (screen_h * INITIAL_HEIGHT) - (2 * BORDER_WIDTH); + win_h = (wa_h * INITIAL_HEIGHT) - (2 * BORDER_WIDTH); /* Step 2: Calculate width using golden ratio (width = height * PHI) */ win_w = (win_h * PHI) - (2 * BORDER_WIDTH); /* Step 3: Safety check - if too wide (>80% of screen), cap it */ - if (win_w > (screen_w * 0.8) - (2 * BORDER_WIDTH)) { - win_w = (screen_w * 0.8) - (2 * BORDER_WIDTH); + if (win_w > (wa_w * 0.8) - (2 * BORDER_WIDTH)) { + win_w = (wa_w * 0.8) - (2 * BORDER_WIDTH); win_h = (win_w / PHI) - (2 * BORDER_WIDTH); } /* Step 4: Center the window horizontally (screen width - total window width including borders) / 2 */ - win_x = (screen_w - (win_w + 2 * BORDER_WIDTH)) / 2; + win_x = wa_x + (wa_w - (win_w + 2 * BORDER_WIDTH)) / 2; /* Step 5: Center the window vertically (screen height - total window height including borders) / 2 */ - win_y = (screen_h - (win_h + 2 * BORDER_WIDTH)) / 2; + win_y = wa_y + (wa_h - (win_h + 2 * BORDER_WIDTH)) / 2; #ifdef DEBUG @@ -399,21 +436,21 @@ static void small_window(Client *c) { .x = win_x, .y = win_y, .width = win_w, - .height = win_h, /* You had screen_h here - that's maximize! */ + .height = win_h, .border_width = BORDER_WIDTH, .stack_mode = Above }; - unsigned int mask = CWX | CWY | CWWidth | CWHeight | CWBorderWidth | CWStackMode; /* Fix line break */ + unsigned int mask = CWX | CWY | CWWidth | CWHeight | CWBorderWidth | CWStackMode; XConfigureWindow(dpy, c->win, mask, &wc); } focus_client(c); } static void maximize_window(Client *c) { - unsigned int win_w = screen_w - (2 * BORDER_WIDTH); - unsigned int win_h = screen_h - (2 * BORDER_WIDTH); - int win_x = 0; - int win_y = 0; + unsigned int win_w = wa_w - (2 * BORDER_WIDTH); + unsigned int win_h = wa_h - (2 * BORDER_WIDTH); + int win_x = wa_x; + int win_y = wa_y; if (!c) return; @@ -650,6 +687,8 @@ static void unmanage(Window w) { if (!focused_client && num_clients) focus_client(&clients[num_clients - 1]); + update_workarea(); + update_client_list(); if (!focused_client) { @@ -774,6 +813,10 @@ static void run(void) { 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(); + } } } } @@ -793,6 +836,65 @@ static void update_client_list(void) { free(wins); } +static void update_workarea(void) { + unsigned long left = 0, right = 0, top = 0, bottom = 0; + unsigned int i; + Atom actual_type; + int actual_format; + unsigned long nitems, bytes_after; + unsigned char *data = NULL; + + for (i = 0; i < num_clients; i++) { + /* Try _NET_WM_STRUT_PARTIAL (12 CARDINALs) */ + if (XGetWindowProperty(dpy, clients[i].win, net_wm_strut_partial, + 0, 12, False, XA_CARDINAL, &actual_type, &actual_format, + &nitems, &bytes_after, &data) == Success && data) { + if (actual_type == XA_CARDINAL && nitems >= 4) { + unsigned long *s = (unsigned long *)data; + if (s[0] > left) left = s[0]; + if (s[1] > right) right = s[1]; + if (s[2] > top) top = s[2]; + if (s[3] > bottom) bottom = s[3]; + } + XFree(data); + data = NULL; + continue; + } + /* Fall back to _NET_WM_STRUT (4 CARDINALs) */ + if (XGetWindowProperty(dpy, clients[i].win, net_wm_strut, + 0, 4, False, XA_CARDINAL, &actual_type, &actual_format, + &nitems, &bytes_after, &data) == Success && data) { + if (actual_type == XA_CARDINAL && nitems >= 4) { + unsigned long *s = (unsigned long *)data; + if (s[0] > left) left = s[0]; + if (s[1] > right) right = s[1]; + if (s[2] > top) top = s[2]; + if (s[3] > bottom) bottom = s[3]; + } + XFree(data); + } + } + + wa_x = left; + wa_y = top; + wa_w = screen_w - left - right; + wa_h = screen_h - top - bottom; + + /* Set _NET_WORKAREA (4 values per desktop) */ + long *workareas = calloc(NUM_WORKSPACES * 4, sizeof(long)); + for (i = 0; i < NUM_WORKSPACES; i++) { + workareas[i * 4 + 0] = wa_x; + workareas[i * 4 + 1] = wa_y; + workareas[i * 4 + 2] = wa_w; + workareas[i * 4 + 3] = wa_h; + } + XChangeProperty(dpy, root, net_workarea, XA_CARDINAL, 32, + PropModeReplace, (unsigned char *)workareas, + NUM_WORKSPACES * 4); + free(workareas); +} + + static void cleanup(int status) { if (dpy) { if (clients)