Mercurial > gbwm
comparison gbwm.c @ 1:286d4af85ebe
Ported workspaces from eowm
| author | Atarwn Gard <a@qwa.su> |
|---|---|
| date | Fri, 10 Oct 2025 22:52:01 +0500 |
| parents | ee781c291790 |
| children | 3ad7c3ab949e |
comparison
equal
deleted
inserted
replaced
| 0:ee781c291790 | 1:286d4af85ebe |
|---|---|
| 1 /* eowm - grid-based tiling window manager */ | 1 /* gbwm - grid-based tiling window manager */ |
| 2 #include <X11/Xlib.h> | 2 #include <X11/Xlib.h> |
| 3 #include <X11/Xatom.h> | 3 #include <X11/Xatom.h> |
| 4 #include <X11/Xft/Xft.h> | 4 #include <X11/Xft/Xft.h> |
| 5 #include <X11/Xutil.h> | 5 #include <X11/Xutil.h> |
| 6 #include <X11/keysym.h> | 6 #include <X11/keysym.h> |
| 27 struct Client { | 27 struct Client { |
| 28 Window win; | 28 Window win; |
| 29 int x, y, w, h; | 29 int x, y, w, h; |
| 30 int saved_x, saved_y, saved_w, saved_h; // Saved position before fullscreen | 30 int saved_x, saved_y, saved_w, saved_h; // Saved position before fullscreen |
| 31 int isfullscreen; | 31 int isfullscreen; |
| 32 int workspace; | |
| 32 Client *next; | 33 Client *next; |
| 33 }; | 34 }; |
| 34 | 35 |
| 35 typedef union { | 36 typedef union { |
| 36 int i; | 37 int i; |
| 55 static const char *col_fg = "#ffffff"; | 56 static const char *col_fg = "#ffffff"; |
| 56 static const char *col_sel = "#4a90e2"; | 57 static const char *col_sel = "#4a90e2"; |
| 57 | 58 |
| 58 static Display *dpy; | 59 static Display *dpy; |
| 59 static Window root; | 60 static Window root; |
| 60 static Client *clients = NULL; | 61 static Client *workspaces[9] = {NULL}; // 9 workspaces |
| 62 static int current_ws = 0; | |
| 61 static Client *focused = NULL; | 63 static Client *focused = NULL; |
| 62 static int sw, sh; | 64 static int sw, sh; |
| 63 static int overlay_mode = 0; | 65 static int overlay_mode = 0; |
| 64 static char overlay_input[3] = {0}; | 66 static char overlay_input[3] = {0}; |
| 65 static Window overlay_win = 0; | 67 static Window overlay_win = 0; |
| 88 static void grabkeys(void); | 90 static void grabkeys(void); |
| 89 static void setfullscreen(Client *c, int fullscreen); | 91 static void setfullscreen(Client *c, int fullscreen); |
| 90 static int sendevent(Client *c, Atom proto); | 92 static int sendevent(Client *c, Atom proto); |
| 91 static void updateborder(Client *c); | 93 static void updateborder(Client *c); |
| 92 static void find_next_free_cell(int *out_r, int *out_c); | 94 static void find_next_free_cell(int *out_r, int *out_c); |
| 95 static void switchws(const Arg *arg); | |
| 96 static void movewin_to_ws(const Arg *arg); | |
| 93 | 97 |
| 94 // Commands | 98 // Commands |
| 95 static const char *termcmd[] = { "alacritty", NULL }; | 99 static const char *termcmd[] = { "alacritty", NULL }; |
| 96 static const char *menucmd[] = { "dmenu_run", NULL }; | 100 static const char *menucmd[] = { "dmenu_run", NULL }; |
| 101 static const char *scrotcmd[] = { "scrot", NULL }; | |
| 97 | 102 |
| 98 // Key bindings | 103 // Key bindings |
| 99 static Key keys[] = { | 104 static Key keys[] = { |
| 100 /* modifier key function argument */ | 105 /* modifier key function argument */ |
| 101 { MOD, XK_t, enter_overlay, {0} }, | 106 { MOD, XK_t, enter_overlay, {0} }, |
| 102 { MOD, XK_Return, spawn, {.v = termcmd} }, | 107 { MOD, XK_Return, spawn, {.v = termcmd} }, |
| 103 { MOD, XK_p, spawn, {.v = menucmd} }, | 108 { MOD, XK_p, spawn, {.v = menucmd} }, |
| 109 { 0, XK_Print, spawn, {.v = scrotcmd} }, | |
| 104 { MOD, XK_q, killclient, {0} }, | 110 { MOD, XK_q, killclient, {0} }, |
| 105 { MOD, XK_f, toggle_fullscreen, {0} }, | 111 { MOD, XK_f, toggle_fullscreen, {0} }, |
| 106 { MOD, XK_Tab, cycle_focus, {0} }, | 112 { MOD, XK_Tab, cycle_focus, {0} }, |
| 107 { MOD|ShiftMask, XK_q, quit, {0} }, | 113 { MOD|ShiftMask, XK_q, quit, {0} }, |
| 114 | |
| 115 // Workspaces | |
| 116 { MOD, XK_1, switchws, {.i = 0} }, | |
| 117 { MOD, XK_2, switchws, {.i = 1} }, | |
| 118 { MOD, XK_3, switchws, {.i = 2} }, | |
| 119 { MOD, XK_4, switchws, {.i = 3} }, | |
| 120 { MOD, XK_5, switchws, {.i = 4} }, | |
| 121 { MOD, XK_6, switchws, {.i = 5} }, | |
| 122 { MOD, XK_7, switchws, {.i = 6} }, | |
| 123 { MOD, XK_8, switchws, {.i = 7} }, | |
| 124 { MOD, XK_9, switchws, {.i = 8} }, | |
| 125 | |
| 126 // Move window to workspace | |
| 127 { MOD|ShiftMask, XK_1, movewin_to_ws, {.i = 0} }, | |
| 128 { MOD|ShiftMask, XK_2, movewin_to_ws, {.i = 1} }, | |
| 129 { MOD|ShiftMask, XK_3, movewin_to_ws, {.i = 2} }, | |
| 130 { MOD|ShiftMask, XK_4, movewin_to_ws, {.i = 3} }, | |
| 131 { MOD|ShiftMask, XK_5, movewin_to_ws, {.i = 4} }, | |
| 132 { MOD|ShiftMask, XK_6, movewin_to_ws, {.i = 5} }, | |
| 133 { MOD|ShiftMask, XK_7, movewin_to_ws, {.i = 6} }, | |
| 134 { MOD|ShiftMask, XK_8, movewin_to_ws, {.i = 7} }, | |
| 135 { MOD|ShiftMask, XK_9, movewin_to_ws, {.i = 8} }, | |
| 108 }; | 136 }; |
| 109 | 137 |
| 110 // Event handlers | 138 // Event handlers |
| 111 static void buttonpress(XEvent *e) { | 139 static void buttonpress(XEvent *e) { |
| 112 for (Client *c = clients; c; c = c->next) | 140 for (Client *c = workspaces[current_ws]; c; c = c->next) |
| 113 if (c->win == e->xbutton.subwindow) { | 141 if (c->win == e->xbutton.subwindow) { |
| 114 focus(c); | 142 focus(c); |
| 115 break; | 143 break; |
| 116 } | 144 } |
| 117 } | 145 } |
| 118 | 146 |
| 119 static void clientmessage(XEvent *e) { | 147 static void clientmessage(XEvent *e) { |
| 120 XClientMessageEvent *cme = &e->xclient; | 148 XClientMessageEvent *cme = &e->xclient; |
| 121 Client *c; | 149 Client *c; |
| 122 for (c = clients; c; c = c->next) | 150 for (c = workspaces[current_ws]; c; c = c->next) |
| 123 if (c->win == cme->window) | 151 if (c->win == cme->window) |
| 124 break; | 152 break; |
| 125 if (!c) | 153 if (!c) |
| 126 return; | 154 return; |
| 127 | 155 |
| 135 if (!XGetWindowAttributes(dpy, e->xmaprequest.window, &wa)) return; | 163 if (!XGetWindowAttributes(dpy, e->xmaprequest.window, &wa)) return; |
| 136 if (wa.override_redirect) return; | 164 if (wa.override_redirect) return; |
| 137 | 165 |
| 138 Client *c = calloc(1, sizeof(Client)); | 166 Client *c = calloc(1, sizeof(Client)); |
| 139 c->win = e->xmaprequest.window; | 167 c->win = e->xmaprequest.window; |
| 140 c->next = clients; | 168 c->workspace = current_ws; |
| 141 clients = c; | 169 c->next = workspaces[current_ws]; |
| 170 workspaces[current_ws] = c; | |
| 142 | 171 |
| 143 // ICCCM setup | 172 // ICCCM setup |
| 144 XSetWindowBorderWidth(dpy, c->win, border_width); | 173 XSetWindowBorderWidth(dpy, c->win, border_width); |
| 145 XSelectInput(dpy, c->win, EnterWindowMask | FocusChangeMask | PropertyChangeMask | StructureNotifyMask); | 174 XSelectInput(dpy, c->win, EnterWindowMask | FocusChangeMask | PropertyChangeMask | StructureNotifyMask); |
| 146 | 175 |
| 151 XMapWindow(dpy, c->win); | 180 XMapWindow(dpy, c->win); |
| 152 focus(c); | 181 focus(c); |
| 153 arrange(); | 182 arrange(); |
| 154 } | 183 } |
| 155 | 184 |
| 185 static void removeclient(Window win) { | |
| 186 Client *c, **prev; | |
| 187 for (prev = &workspaces[current_ws]; (c = *prev); prev = &c->next) { | |
| 188 if (c->win == win) { | |
| 189 *prev = c->next; | |
| 190 if (focused == c) { | |
| 191 focused = workspaces[current_ws]; | |
| 192 if (focused) | |
| 193 focus(focused); | |
| 194 } | |
| 195 free(c); | |
| 196 arrange(); | |
| 197 return; | |
| 198 } | |
| 199 } | |
| 200 } | |
| 201 | |
| 156 static void unmapnotify(XEvent *e) { | 202 static void unmapnotify(XEvent *e) { |
| 157 Client **p; | 203 removeclient(e->xunmap.window); |
| 158 for (p = &clients; *p && (*p)->win != e->xunmap.window; p = &(*p)->next); | |
| 159 if (*p) { | |
| 160 Client *c = *p; | |
| 161 if (focused == c) { | |
| 162 focused = c->next ? c->next : (clients == c ? NULL : clients); | |
| 163 } | |
| 164 *p = c->next; | |
| 165 free(c); | |
| 166 if (focused) | |
| 167 focus(focused); | |
| 168 arrange(); | |
| 169 } | |
| 170 } | 204 } |
| 171 | 205 |
| 172 static void destroynotify(XEvent *e) { | 206 static void destroynotify(XEvent *e) { |
| 173 unmapnotify(e); | 207 removeclient(e->xdestroywindow.window); |
| 174 } | 208 } |
| 175 | 209 |
| 176 static void enternotify(XEvent *e) { | 210 static void enternotify(XEvent *e) { |
| 177 if (e->xcrossing.mode != NotifyNormal || e->xcrossing.detail == NotifyInferior) | 211 if (e->xcrossing.mode != NotifyNormal || e->xcrossing.detail == NotifyInferior) |
| 178 return; | 212 return; |
| 179 for (Client *c = clients; c; c = c->next) | 213 for (Client *c = workspaces[current_ws]; c; c = c->next) |
| 180 if (c->win == e->xcrossing.window) { | 214 if (c->win == e->xcrossing.window) { |
| 181 focus(c); | 215 focus(c); |
| 182 break; | 216 break; |
| 183 } | 217 } |
| 184 } | 218 } |
| 261 static int is_cell_free(int r, int c, int cell_w, int cell_h) { | 295 static int is_cell_free(int r, int c, int cell_w, int cell_h) { |
| 262 int cell_x = padding + c * (cell_w + padding); | 296 int cell_x = padding + c * (cell_w + padding); |
| 263 int cell_y = padding + r * (cell_h + padding); | 297 int cell_y = padding + r * (cell_h + padding); |
| 264 | 298 |
| 265 // Check if any window overlaps with this cell | 299 // Check if any window overlaps with this cell |
| 266 for (Client *cl = clients; cl; cl = cl->next) { | 300 for (Client *cl = workspaces[current_ws]; cl; cl = cl->next) { |
| 267 if (cl->isfullscreen) continue; | 301 if (cl->isfullscreen) continue; |
| 268 | 302 |
| 269 // Check for overlap | 303 // Check for overlap |
| 270 int cl_right = cl->x + cl->w; | 304 int cl_right = cl->x + cl->w; |
| 271 int cl_bottom = cl->y + cl->h; | 305 int cl_bottom = cl->y + cl->h; |
| 299 | 333 |
| 300 // Second pass: no free space found, check top-left for 1x1 windows | 334 // Second pass: no free space found, check top-left for 1x1 windows |
| 301 int cell_x = padding; | 335 int cell_x = padding; |
| 302 int cell_y = padding; | 336 int cell_y = padding; |
| 303 | 337 |
| 304 for (Client *cl = clients; cl; cl = cl->next) { | 338 for (Client *cl = workspaces[current_ws]; cl; cl = cl->next) { |
| 305 if (cl->isfullscreen) continue; | 339 if (cl->isfullscreen) continue; |
| 306 if (cl->x == cell_x && cl->y == cell_y && | 340 if (cl->x == cell_x && cl->y == cell_y && |
| 307 cl->w == cell_w && cl->h == cell_h) { | 341 cl->w == cell_w && cl->h == cell_h) { |
| 308 // Found a 1x1 window at top-left, find next free cell | 342 // Found a 1x1 window at top-left, find next free cell |
| 309 for (int r = 0; r < GRID_ROWS; r++) { | 343 for (int r = 0; r < GRID_ROWS; r++) { |
| 310 for (int c = 0; c < GRID_COLS; c++) { | 344 for (int c = 0; c < GRID_COLS; c++) { |
| 311 int check_x = padding + c * (cell_w + padding); | 345 int check_x = padding + c * (cell_w + padding); |
| 312 int check_y = padding + r * (cell_h + padding); | 346 int check_y = padding + r * (cell_h + padding); |
| 313 | 347 |
| 314 int found_1x1 = 0; | 348 int found_1x1 = 0; |
| 315 for (Client *check = clients; check; check = check->next) { | 349 for (Client *check = workspaces[current_ws]; check; check = check->next) { |
| 316 if (check->isfullscreen) continue; | 350 if (check->isfullscreen) continue; |
| 317 if (check->x == check_x && check->y == check_y && | 351 if (check->x == check_x && check->y == check_y && |
| 318 check->w == cell_w && check->h == cell_h) { | 352 check->w == cell_w && check->h == cell_h) { |
| 319 found_1x1 = 1; | 353 found_1x1 = 1; |
| 320 break; | 354 break; |
| 336 *out_r = 0; | 370 *out_r = 0; |
| 337 *out_c = 0; | 371 *out_c = 0; |
| 338 } | 372 } |
| 339 | 373 |
| 340 static void arrange(void) { | 374 static void arrange(void) { |
| 341 if (!clients) return; | 375 if (!workspaces[current_ws]) return; |
| 342 | 376 |
| 343 if (!focused) focused = clients; | 377 if (!focused) focused = workspaces[current_ws]; |
| 344 | 378 |
| 345 if (focused->isfullscreen) { | 379 if (focused->isfullscreen) { |
| 346 return; | 380 return; |
| 347 } | 381 } |
| 348 | 382 |
| 357 int y = padding + r * (cell_h + padding); | 391 int y = padding + r * (cell_h + padding); |
| 358 resize(focused, x, y, cell_w, cell_h); | 392 resize(focused, x, y, cell_w, cell_h); |
| 359 } | 393 } |
| 360 | 394 |
| 361 // Update all borders | 395 // Update all borders |
| 362 for (Client *c = clients; c; c = c->next) | 396 for (Client *c = workspaces[current_ws]; c; c = c->next) |
| 363 updateborder(c); | 397 updateborder(c); |
| 364 } | 398 } |
| 365 | 399 |
| 366 static void draw_overlay(void) { | 400 static void draw_overlay(void) { |
| 367 if (!overlay_win) return; | 401 if (!overlay_win) return; |
| 510 int h = rows_span * cell_h + (rows_span - 1) * padding; | 544 int h = rows_span * cell_h + (rows_span - 1) * padding; |
| 511 | 545 |
| 512 resize(focused, x, y, w, h); | 546 resize(focused, x, y, w, h); |
| 513 } | 547 } |
| 514 | 548 |
| 549 // Workspace functions | |
| 550 static void switchws(const Arg *arg) { | |
| 551 int ws = arg->i; | |
| 552 if (ws < 0 || ws >= 9 || ws == current_ws) return; | |
| 553 | |
| 554 current_ws = ws; | |
| 555 | |
| 556 // Hide all windows from all workspaces | |
| 557 for (int i = 0; i < 9; i++) { | |
| 558 for (Client *c = workspaces[i]; c; c = c->next) { | |
| 559 XUnmapWindow(dpy, c->win); | |
| 560 } | |
| 561 } | |
| 562 | |
| 563 // Show current workspace windows | |
| 564 for (Client *c = workspaces[current_ws]; c; c = c->next) { | |
| 565 XMapWindow(dpy, c->win); | |
| 566 } | |
| 567 | |
| 568 focused = workspaces[current_ws]; | |
| 569 if (focused) focus(focused); | |
| 570 arrange(); | |
| 571 } | |
| 572 | |
| 573 static void movewin_to_ws(const Arg *arg) { | |
| 574 int ws = arg->i; | |
| 575 if (!focused || ws < 0 || ws >= 9 || ws == current_ws) return; | |
| 576 | |
| 577 Client *moving = focused; | |
| 578 | |
| 579 // Remove from current workspace | |
| 580 Client **prev; | |
| 581 for (prev = &workspaces[current_ws]; *prev; prev = &(*prev)->next) { | |
| 582 if (*prev == moving) { | |
| 583 *prev = moving->next; | |
| 584 break; | |
| 585 } | |
| 586 } | |
| 587 | |
| 588 // Add to target workspace | |
| 589 moving->workspace = ws; | |
| 590 moving->next = workspaces[ws]; | |
| 591 workspaces[ws] = moving; | |
| 592 moving->isfullscreen = 0; // Reset fullscreen state | |
| 593 | |
| 594 // Hide the window we just moved | |
| 595 XUnmapWindow(dpy, moving->win); | |
| 596 | |
| 597 // Update focus to next available window in current workspace | |
| 598 focused = workspaces[current_ws]; | |
| 599 if (focused) { | |
| 600 focus(focused); | |
| 601 } else { | |
| 602 focused = NULL; | |
| 603 } | |
| 604 | |
| 605 // Re-arrange current workspace | |
| 606 arrange(); | |
| 607 } | |
| 608 | |
| 515 // Action functions | 609 // Action functions |
| 516 static int sendevent(Client *c, Atom proto) { | 610 static int sendevent(Client *c, Atom proto) { |
| 517 int n; | 611 int n; |
| 518 Atom *protocols; | 612 Atom *protocols; |
| 519 int exists = 0; | 613 int exists = 0; |
| 606 static void quit(const Arg *arg) { | 700 static void quit(const Arg *arg) { |
| 607 exit(0); | 701 exit(0); |
| 608 } | 702 } |
| 609 | 703 |
| 610 static void cycle_focus(const Arg *arg) { | 704 static void cycle_focus(const Arg *arg) { |
| 611 if (!clients) return; | 705 if (!workspaces[current_ws]) return; |
| 612 | 706 |
| 613 if (!focused) { | 707 if (!focused) { |
| 614 focus(clients); | 708 focus(workspaces[current_ws]); |
| 615 return; | 709 return; |
| 616 } | 710 } |
| 617 | 711 |
| 618 Client *next = focused->next; | 712 Client *next = focused->next; |
| 619 if (!next) next = clients; | 713 if (!next) next = workspaces[current_ws]; |
| 620 | 714 |
| 621 focus(next); | 715 focus(next); |
| 622 } | 716 } |
| 623 | 717 |
| 624 static void grabkeys(void) { | 718 static void grabkeys(void) { |