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