commit 66bbd5426a0538e0a9b8bb43bb50204ef13fdc41 from: Brett Fisher date: Wed May 13 18:47:37 2026 UTC bxwm.c: - Implement basic EWMH/ICCCM compliance. - Set EWMH root window properties for workspace and client state so external tools (panels, pagers) can read bxwm state. - Replace XKillClient with WM_DELETE_WINDOW protocol for graceful window closing. - Atoms supported: _NET_SUPPORTED, _NET_NUMBER_OF_DESKTOPS, _NET_CURRENT_DESKTOP, _NET_CLIENT_LIST, _NET_ACTIVE_WINDOW, _NET_WM_DESKTOP. commit - ac5b7bb7f2b6a4fa8bb0dc85e94f181feeac1886 commit + 66bbd5426a0538e0a9b8bb43bb50204ef13fdc41 blob - 3ba07a044fb7c4f06008b9e9195aabc1a5fa1b74 blob + 600e835e7dbe1e8824b3cca52d858609b5fa586e --- bxwm.c +++ bxwm.c @@ -13,6 +13,7 @@ #include #include #include +#include #include "config.h" @@ -36,6 +37,13 @@ static unsigned long unfocus_pixel; static int curws = INITIAL_WORKSPACE; static int ws_focus_idx[NUM_WORKSPACES] = {-1}; /* Index of last focused per WS */ +/* EWMH atoms */ +static Atom net_supported, net_number_of_desktops, net_current_desktop; +static Atom net_client_list, net_active_window, net_wm_desktop; + +/* ICCCM atoms */ +static Atom wm_protocols, wm_delete_window; + /* Function declarations */ static void run(void); static void cleanup(int status); @@ -55,6 +63,7 @@ static void focus_prev(void); static void view(int ws); static void movetows(int ws); static void unmanage(Window w); +static void update_client_list(void); int main(void) { signal(SIGTERM, cleanup); @@ -153,6 +162,40 @@ static void setup(void) { root, True, GrabModeAsync, GrabModeAsync); /* Super+Shift+N: move */ } + /* Intern EWMH/ICCCM atoms */ + net_supported = XInternAtom(dpy, "_NET_SUPPORTED", False); + net_number_of_desktops = XInternAtom(dpy, "_NET_NUMBER_OF_DESKTOPS", False); + net_current_desktop = XInternAtom(dpy, "_NET_CURRENT_DESKTOP", False); + net_client_list = XInternAtom(dpy, "_NET_CLIENT_LIST", False); + net_active_window = XInternAtom(dpy, "_NET_ACTIVE_WINDOW", False); + 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); + + /* 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 + }; + XChangeProperty(dpy, root, net_supported, XA_ATOM, 32, + PropModeReplace, (unsigned char *)supported, + sizeof(supported) / sizeof(Atom)); + + long ndesktops = NUM_WORKSPACES; + XChangeProperty(dpy, root, net_number_of_desktops, XA_CARDINAL, 32, + PropModeReplace, (unsigned char *)&ndesktops, 1); + + long current_desktop = INITIAL_WORKSPACE; + XChangeProperty(dpy, root, net_current_desktop, XA_CARDINAL, 32, + PropModeReplace, (unsigned char *)¤t_desktop, 1); + + XChangeProperty(dpy, root, net_client_list, XA_WINDOW, 32, + PropModeReplace, NULL, 0); + + long active = None; + XChangeProperty(dpy, root, net_active_window, XA_WINDOW, 32, + PropModeReplace, (unsigned char *)&active, 1); + #ifdef DEBUG fprintf(stderr, "bxwm: setup complete\n"); #endif @@ -212,6 +255,11 @@ static void manage(Window w) { XMapWindow(dpy, w); /* Now map (configure already applied) */ focus_client(c); /* Border color + XRaise redundant (already Above), but keeps focus/input */ + + long ws = curws; + XChangeProperty(dpy, w, net_wm_desktop, XA_CARDINAL, 32, + PropModeReplace, (unsigned char *)&ws, 1); + update_client_list(); } static void focus_client(Client *c) { @@ -228,6 +276,9 @@ static void focus_client(Client *c) { XSetWindowBorder(dpy, c->win, focus_pixel); XSetInputFocus(dpy, c->win, RevertToParent, CurrentTime); XRaiseWindow(dpy, c->win); + long active = c->win; + XChangeProperty(dpy, root, net_active_window, XA_WINDOW, 32, + PropModeReplace, (unsigned char *)&active, 1); #ifdef DEBUG fprintf(stderr, "bxwm: focused 0x%lx\n", c->win); #endif @@ -388,10 +439,25 @@ static void maximize_window(Client *c) { static void close_window(Client *c) { if (!c) return; - #ifdef DEBUG - fprintf(stderr, "bxwm: close 0x%lx\n", c->win); - #endif - + int n; + Atom *protocols; + if (XGetWMProtocols(dpy, c->win, &protocols, &n)) { + for (int i = 0; i < n; i++) { + if (protocols[i] == wm_delete_window) { + XEvent ev; + ev.type = ClientMessage; + ev.xclient.window = c->win; + ev.xclient.message_type = wm_protocols; + ev.xclient.format = 32; + ev.xclient.data.l[0] = wm_delete_window; + ev.xclient.data.l[1] = CurrentTime; + XSendEvent(dpy, c->win, False, NoEventMask, &ev); + XFree(protocols); + return; + } + } + XFree(protocols); + } XKillClient(dpy, c->win); } @@ -489,6 +555,11 @@ static void view(int ws) { curws = ws; + long current_desktop = curws; + XChangeProperty(dpy, root, net_current_desktop, XA_CARDINAL, 32, + PropModeReplace, (unsigned char *)¤t_desktop, 1); + + /* Map new workspace windows */ for (i = 0; i < num_clients; i++) { if (clients[i].ws == curws) @@ -512,7 +583,11 @@ static void movetows(int ws) { old_ws = c->ws; c->ws = ws; - + + long new_ws = ws; + XChangeProperty(dpy, c->win, net_wm_desktop, XA_CARDINAL, 32, + PropModeReplace, (unsigned char *)&new_ws, 1); + if (old_ws == curws) { /* Moving away from visible workspace */ XUnmapWindow(dpy, c->win); @@ -574,6 +649,14 @@ static void unmanage(Window w) { if (!focused_client && num_clients) focus_client(&clients[num_clients - 1]); + + update_client_list(); + + if (!focused_client) { + long active = None; + XChangeProperty(dpy, root, net_active_window, XA_WINDOW, 32, + PropModeReplace, (unsigned char *)&active, 1); + } } static void spawn(const char *cmd) { @@ -695,6 +778,21 @@ static void run(void) { } } +static void update_client_list(void) { + Window *wins = NULL; + unsigned int i; + + if (num_clients > 0) { + wins = malloc(num_clients * sizeof(Window)); + for (i = 0; i < num_clients; i++) + wins[i] = clients[i].win; + } + XChangeProperty(dpy, root, net_client_list, XA_WINDOW, 32, + PropModeReplace, (unsigned char *)wins, num_clients); + if (wins) + free(wins); +} + static void cleanup(int status) { if (dpy) { if (clients)