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) {