| 1 | 1 /* gbwm - grid-based tiling window manager */ | 
| 0 | 2 #include <X11/Xlib.h> | 
|  | 3 #include <X11/Xatom.h> | 
|  | 4 #include <X11/Xft/Xft.h> | 
|  | 5 #include <X11/Xutil.h> | 
|  | 6 #include <X11/keysym.h> | 
|  | 7 #include <stdio.h> | 
|  | 8 #include <stdlib.h> | 
|  | 9 #include <string.h> | 
|  | 10 #include <unistd.h> | 
|  | 11 #include <signal.h> | 
|  | 12 #include <sys/wait.h> | 
|  | 13 | 
|  | 14 #define MOD Mod4Mask | 
|  | 15 #define GRID_ROWS 4 | 
|  | 16 #define GRID_COLS 7 | 
|  | 17 | 
|  | 18 // Grid 4 by 7 | 
|  | 19 static const char grid_chars[GRID_ROWS][GRID_COLS+1] = { | 
|  | 20     "1234567", | 
|  | 21     "qwertyu", | 
|  | 22     "asdfghj", | 
|  | 23     "zxcvbnm" | 
|  | 24 }; | 
|  | 25 | 
|  | 26 typedef struct Client Client; | 
|  | 27 struct Client { | 
|  | 28     Window win; | 
|  | 29     int x, y, w, h; | 
|  | 30     int saved_x, saved_y, saved_w, saved_h;  // Saved position before fullscreen | 
|  | 31     int isfullscreen; | 
| 1 | 32     int workspace; | 
| 0 | 33     Client *next; | 
|  | 34 }; | 
|  | 35 | 
|  | 36 typedef union { | 
|  | 37     int i; | 
|  | 38     unsigned int ui; | 
|  | 39     float f; | 
|  | 40     const void *v; | 
|  | 41 } Arg; | 
|  | 42 | 
|  | 43 typedef struct { | 
|  | 44     unsigned int mod; | 
|  | 45     KeySym keysym; | 
|  | 46     void (*func)(const Arg *); | 
|  | 47     const Arg arg; | 
|  | 48 } Key; | 
|  | 49 | 
|  | 50 // Configuration | 
|  | 51 static const int padding = 8; | 
|  | 52 static const int border_width = 2; | 
|  | 53 static const char *col_border_normal = "#444444"; | 
|  | 54 static const char *col_border_focused = "#4a90e2"; | 
|  | 55 static const char *col_bg = "#000000"; | 
|  | 56 static const char *col_fg = "#ffffff"; | 
|  | 57 static const char *col_sel = "#4a90e2"; | 
|  | 58 | 
|  | 59 static Display *dpy; | 
|  | 60 static Window root; | 
| 1 | 61 static Client *workspaces[9] = {NULL};  // 9 workspaces | 
|  | 62 static int current_ws = 0; | 
| 0 | 63 static Client *focused = NULL; | 
|  | 64 static int sw, sh; | 
|  | 65 static int overlay_mode = 0; | 
|  | 66 static char overlay_input[3] = {0}; | 
|  | 67 static Window overlay_win = 0; | 
|  | 68 static GC gc; | 
|  | 69 static XftDraw *xftdraw = NULL; | 
|  | 70 static XftFont *font = NULL; | 
|  | 71 static XftColor xft_col_bg, xft_col_fg, xft_col_sel; | 
|  | 72 static unsigned long border_normal, border_focused; | 
|  | 73 | 
|  | 74 // ICCCM atoms | 
|  | 75 static Atom wm_protocols, wm_delete_window, wm_state, wm_take_focus; | 
|  | 76 | 
|  | 77 // Forward decls | 
|  | 78 static void arrange(void); | 
|  | 79 static void resize(Client *c, int x, int y, int w, int h); | 
|  | 80 static void focus(Client *c); | 
|  | 81 static void spawn(const Arg *arg); | 
|  | 82 static void killclient(const Arg *arg); | 
|  | 83 static void toggle_fullscreen(const Arg *arg); | 
|  | 84 static void enter_overlay(const Arg *arg); | 
|  | 85 static void process_overlay_input(void); | 
|  | 86 static void draw_overlay(void); | 
|  | 87 static void hide_overlay(void); | 
|  | 88 static void quit(const Arg *arg); | 
|  | 89 static void cycle_focus(const Arg *arg); | 
|  | 90 static void grabkeys(void); | 
|  | 91 static void setfullscreen(Client *c, int fullscreen); | 
|  | 92 static int sendevent(Client *c, Atom proto); | 
|  | 93 static void updateborder(Client *c); | 
|  | 94 static void find_next_free_cell(int *out_r, int *out_c); | 
| 1 | 95 static void switchws(const Arg *arg); | 
|  | 96 static void movewin_to_ws(const Arg *arg); | 
| 0 | 97 | 
|  | 98 // Commands | 
|  | 99 static const char *termcmd[] = { "alacritty", NULL }; | 
|  | 100 static const char *menucmd[] = { "dmenu_run", NULL }; | 
| 1 | 101 static const char *scrotcmd[] = { "scrot", NULL }; | 
| 0 | 102 | 
|  | 103 // Key bindings | 
|  | 104 static Key keys[] = { | 
|  | 105     /* modifier         key              function         argument */ | 
|  | 106     { MOD,              XK_t,            enter_overlay,   {0} }, | 
|  | 107     { MOD,              XK_Return,       spawn,           {.v = termcmd} }, | 
|  | 108     { MOD,              XK_p,            spawn,           {.v = menucmd} }, | 
| 1 | 109     { 0,                XK_Print,        spawn,           {.v = scrotcmd} }, | 
| 0 | 110     { MOD,              XK_q,            killclient,      {0} }, | 
|  | 111     { MOD,              XK_f,            toggle_fullscreen, {0} }, | 
|  | 112     { MOD,              XK_Tab,          cycle_focus,     {0} }, | 
|  | 113     { MOD|ShiftMask,    XK_q,            quit,            {0} }, | 
| 1 | 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} }, | 
| 0 | 136 }; | 
|  | 137 | 
|  | 138 // Event handlers | 
|  | 139 static void buttonpress(XEvent *e) { | 
| 1 | 140     for (Client *c = workspaces[current_ws]; c; c = c->next) | 
| 0 | 141         if (c->win == e->xbutton.subwindow) { | 
|  | 142             focus(c); | 
|  | 143             break; | 
|  | 144         } | 
|  | 145 } | 
|  | 146 | 
|  | 147 static void clientmessage(XEvent *e) { | 
|  | 148     XClientMessageEvent *cme = &e->xclient; | 
|  | 149     Client *c; | 
| 1 | 150     for (c = workspaces[current_ws]; c; c = c->next) | 
| 0 | 151         if (c->win == cme->window) | 
|  | 152             break; | 
|  | 153     if (!c) | 
|  | 154         return; | 
|  | 155 | 
|  | 156     if (cme->message_type == wm_state && cme->data.l[1] == (long)XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", False)) { | 
|  | 157         setfullscreen(c, cme->data.l[0] == 1 || (cme->data.l[0] == 2 && !c->isfullscreen)); | 
|  | 158     } | 
|  | 159 } | 
|  | 160 | 
|  | 161 static void maprequest(XEvent *e) { | 
|  | 162     XWindowAttributes wa; | 
|  | 163     if (!XGetWindowAttributes(dpy, e->xmaprequest.window, &wa)) return; | 
|  | 164     if (wa.override_redirect) return; | 
|  | 165 | 
|  | 166     Client *c = calloc(1, sizeof(Client)); | 
|  | 167     c->win = e->xmaprequest.window; | 
| 1 | 168     c->workspace = current_ws; | 
|  | 169     c->next = workspaces[current_ws]; | 
|  | 170     workspaces[current_ws] = c; | 
| 0 | 171 | 
|  | 172     // ICCCM setup | 
|  | 173     XSetWindowBorderWidth(dpy, c->win, border_width); | 
|  | 174     XSelectInput(dpy, c->win, EnterWindowMask | FocusChangeMask | PropertyChangeMask | StructureNotifyMask); | 
|  | 175 | 
|  | 176     // Set WM_STATE | 
|  | 177     long data[] = { NormalState, None }; | 
|  | 178     XChangeProperty(dpy, c->win, wm_state, wm_state, 32, PropModeReplace, (unsigned char *)data, 2); | 
|  | 179 | 
|  | 180     XMapWindow(dpy, c->win); | 
|  | 181     focus(c); | 
|  | 182     arrange(); | 
|  | 183 } | 
|  | 184 | 
| 1 | 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; | 
| 0 | 198         } | 
|  | 199     } | 
|  | 200 } | 
|  | 201 | 
| 1 | 202 static void unmapnotify(XEvent *e) { | 
|  | 203     removeclient(e->xunmap.window); | 
|  | 204 } | 
|  | 205 | 
| 0 | 206 static void destroynotify(XEvent *e) { | 
| 1 | 207     removeclient(e->xdestroywindow.window); | 
| 0 | 208 } | 
|  | 209 | 
|  | 210 static void enternotify(XEvent *e) { | 
|  | 211     if (e->xcrossing.mode != NotifyNormal || e->xcrossing.detail == NotifyInferior) | 
|  | 212         return; | 
| 1 | 213     for (Client *c = workspaces[current_ws]; c; c = c->next) | 
| 0 | 214         if (c->win == e->xcrossing.window) { | 
|  | 215             focus(c); | 
|  | 216             break; | 
|  | 217         } | 
|  | 218 } | 
|  | 219 | 
|  | 220 static void expose(XEvent *e) { | 
|  | 221     if (e->xexpose.window == overlay_win && overlay_mode) { | 
|  | 222         draw_overlay(); | 
|  | 223     } | 
|  | 224 } | 
|  | 225 | 
|  | 226 static void keypress(XEvent *e) { | 
|  | 227     if (overlay_mode) { | 
|  | 228         KeySym k = XLookupKeysym(&e->xkey, 0); | 
|  | 229         if (k == XK_Escape) { | 
|  | 230             hide_overlay(); | 
|  | 231             return; | 
|  | 232         } | 
|  | 233 | 
|  | 234         char ch = 0; | 
|  | 235         if (k >= '0' && k <= '9') ch = (char)k; | 
|  | 236         else if (k >= 'a' && k <= 'z') ch = (char)k; | 
|  | 237         else if (k >= 'A' && k <= 'Z') ch = (char)(k - 'A' + 'a'); | 
|  | 238         else return; | 
|  | 239 | 
|  | 240         int found = 0; | 
|  | 241         for (int r = 0; r < GRID_ROWS && !found; r++) | 
|  | 242             for (int c = 0; c < GRID_COLS && !found; c++) | 
|  | 243                 if (grid_chars[r][c] == ch) found = 1; | 
|  | 244         if (!found) return; | 
|  | 245 | 
|  | 246         if (overlay_input[0] == 0) { | 
|  | 247             overlay_input[0] = ch; | 
|  | 248             draw_overlay(); | 
|  | 249         } else if (overlay_input[1] == 0) { | 
|  | 250             overlay_input[1] = ch; | 
|  | 251             draw_overlay(); | 
|  | 252             usleep(150000); | 
|  | 253             process_overlay_input(); | 
|  | 254             hide_overlay(); | 
|  | 255         } | 
|  | 256         return; | 
|  | 257     } | 
|  | 258 | 
|  | 259     KeySym keysym = XLookupKeysym(&e->xkey, 0); | 
|  | 260     unsigned int state = e->xkey.state & ~(LockMask | Mod2Mask); | 
|  | 261 | 
|  | 262     for (unsigned int i = 0; i < sizeof(keys) / sizeof(Key); i++) { | 
|  | 263         if (keysym == keys[i].keysym && state == keys[i].mod && keys[i].func) { | 
|  | 264             keys[i].func(&keys[i].arg); | 
|  | 265             return; | 
|  | 266         } | 
|  | 267     } | 
|  | 268 } | 
|  | 269 | 
|  | 270 // Core logic | 
|  | 271 static void resize(Client *c, int x, int y, int w, int h) { | 
|  | 272     c->x = x; c->y = y; c->w = w; c->h = h; | 
|  | 273     XMoveResizeWindow(dpy, c->win, x, y, w, h); | 
|  | 274 } | 
|  | 275 | 
|  | 276 static void updateborder(Client *c) { | 
|  | 277     XSetWindowBorder(dpy, c->win, c == focused ? border_focused : border_normal); | 
|  | 278 } | 
|  | 279 | 
|  | 280 static void focus(Client *c) { | 
|  | 281     if (!c) return; | 
|  | 282 | 
|  | 283     Client *old = focused; | 
|  | 284     focused = c; | 
|  | 285 | 
|  | 286     if (old && old != c) | 
|  | 287         updateborder(old); | 
|  | 288 | 
|  | 289     updateborder(c); | 
|  | 290     XRaiseWindow(dpy, c->win); | 
|  | 291     XSetInputFocus(dpy, c->win, RevertToPointerRoot, CurrentTime); | 
|  | 292     sendevent(c, wm_take_focus); | 
|  | 293 } | 
|  | 294 | 
|  | 295 static int is_cell_free(int r, int c, int cell_w, int cell_h) { | 
|  | 296     int cell_x = padding + c * (cell_w + padding); | 
|  | 297     int cell_y = padding + r * (cell_h + padding); | 
|  | 298 | 
|  | 299     // Check if any window overlaps with this cell | 
| 1 | 300     for (Client *cl = workspaces[current_ws]; cl; cl = cl->next) { | 
| 0 | 301         if (cl->isfullscreen) continue; | 
|  | 302 | 
|  | 303         // Check for overlap | 
|  | 304         int cl_right = cl->x + cl->w; | 
|  | 305         int cl_bottom = cl->y + cl->h; | 
|  | 306         int cell_right = cell_x + cell_w; | 
|  | 307         int cell_bottom = cell_y + cell_h; | 
|  | 308 | 
|  | 309         // If rectangles overlap | 
|  | 310         if (!(cl_right <= cell_x || cl->x >= cell_right || | 
|  | 311               cl_bottom <= cell_y || cl->y >= cell_bottom)) { | 
|  | 312             return 0;  // Cell is occupied | 
|  | 313         } | 
|  | 314     } | 
|  | 315 | 
|  | 316     return 1;  // Cell is free | 
|  | 317 } | 
|  | 318 | 
|  | 319 static void find_next_free_cell(int *out_r, int *out_c) { | 
|  | 320     int cell_w = (sw - padding * (GRID_COLS + 1)) / GRID_COLS; | 
|  | 321     int cell_h = (sh - padding * (GRID_ROWS + 1)) / GRID_ROWS; | 
|  | 322 | 
|  | 323     // First pass: look for any completely free cell | 
|  | 324     for (int r = 0; r < GRID_ROWS; r++) { | 
|  | 325         for (int c = 0; c < GRID_COLS; c++) { | 
|  | 326             if (is_cell_free(r, c, cell_w, cell_h)) { | 
|  | 327                 *out_r = r; | 
|  | 328                 *out_c = c; | 
|  | 329                 return; | 
|  | 330             } | 
|  | 331         } | 
|  | 332     } | 
|  | 333 | 
|  | 334     // Second pass: no free space found, check top-left for 1x1 windows | 
|  | 335     int cell_x = padding; | 
|  | 336     int cell_y = padding; | 
|  | 337 | 
| 1 | 338     for (Client *cl = workspaces[current_ws]; cl; cl = cl->next) { | 
| 0 | 339         if (cl->isfullscreen) continue; | 
|  | 340         if (cl->x == cell_x && cl->y == cell_y && | 
|  | 341             cl->w == cell_w && cl->h == cell_h) { | 
|  | 342             // Found a 1x1 window at top-left, find next free cell | 
|  | 343             for (int r = 0; r < GRID_ROWS; r++) { | 
|  | 344                 for (int c = 0; c < GRID_COLS; c++) { | 
|  | 345                     int check_x = padding + c * (cell_w + padding); | 
|  | 346                     int check_y = padding + r * (cell_h + padding); | 
|  | 347 | 
|  | 348                     int found_1x1 = 0; | 
| 1 | 349                     for (Client *check = workspaces[current_ws]; check; check = check->next) { | 
| 0 | 350                         if (check->isfullscreen) continue; | 
|  | 351                         if (check->x == check_x && check->y == check_y && | 
|  | 352                             check->w == cell_w && check->h == cell_h) { | 
|  | 353                             found_1x1 = 1; | 
|  | 354                             break; | 
|  | 355                         } | 
|  | 356                     } | 
|  | 357 | 
|  | 358                     if (!found_1x1) { | 
|  | 359                         *out_r = r; | 
|  | 360                         *out_c = c; | 
|  | 361                         return; | 
|  | 362                     } | 
|  | 363                 } | 
|  | 364             } | 
|  | 365             break; | 
|  | 366         } | 
|  | 367     } | 
|  | 368 | 
|  | 369     // Fallback to top-left | 
|  | 370     *out_r = 0; | 
|  | 371     *out_c = 0; | 
|  | 372 } | 
|  | 373 | 
|  | 374 static void arrange(void) { | 
| 1 | 375     if (!workspaces[current_ws]) return; | 
| 0 | 376 | 
| 1 | 377     if (!focused) focused = workspaces[current_ws]; | 
| 0 | 378 | 
|  | 379     if (focused->isfullscreen) { | 
|  | 380         return; | 
|  | 381     } | 
|  | 382 | 
|  | 383     // Default window location - find next free cell | 
|  | 384     int cell_w = (sw - padding * (GRID_COLS + 1)) / GRID_COLS; | 
|  | 385     int cell_h = (sh - padding * (GRID_ROWS + 1)) / GRID_ROWS; | 
|  | 386 | 
|  | 387     if (focused->w == 0 || focused->h == 0) { | 
|  | 388         int r, c; | 
|  | 389         find_next_free_cell(&r, &c); | 
|  | 390         int x = padding + c * (cell_w + padding); | 
|  | 391         int y = padding + r * (cell_h + padding); | 
|  | 392         resize(focused, x, y, cell_w, cell_h); | 
|  | 393     } | 
|  | 394 | 
|  | 395     // Update all borders | 
| 1 | 396     for (Client *c = workspaces[current_ws]; c; c = c->next) | 
| 0 | 397         updateborder(c); | 
|  | 398 } | 
|  | 399 | 
|  | 400 static void draw_overlay(void) { | 
|  | 401     if (!overlay_win) return; | 
|  | 402 | 
|  | 403     XClearWindow(dpy, overlay_win); | 
|  | 404 | 
|  | 405     int cell_w = (sw - padding * (GRID_COLS + 1)) / GRID_COLS; | 
|  | 406     int cell_h = (sh - padding * (GRID_ROWS + 1)) / GRID_ROWS; | 
|  | 407 | 
|  | 408     int r1 = -1, c1 = -1, r2 = -1, c2 = -1; | 
|  | 409     if (overlay_input[0]) { | 
|  | 410         for (int r = 0; r < GRID_ROWS; r++) | 
|  | 411             for (int c = 0; c < GRID_COLS; c++) | 
|  | 412                 if (grid_chars[r][c] == overlay_input[0]) { r1 = r; c1 = c; } | 
|  | 413     } | 
|  | 414     if (overlay_input[1]) { | 
|  | 415         for (int r = 0; r < GRID_ROWS; r++) | 
|  | 416             for (int c = 0; c < GRID_COLS; c++) | 
|  | 417                 if (grid_chars[r][c] == overlay_input[1]) { r2 = r; c2 = c; } | 
|  | 418     } | 
|  | 419 | 
|  | 420     for (int r = 0; r < GRID_ROWS; r++) { | 
|  | 421         for (int c = 0; c < GRID_COLS; c++) { | 
|  | 422             int x = padding + c * (cell_w + padding); | 
|  | 423             int y = padding + r * (cell_h + padding); | 
|  | 424 | 
|  | 425             int is_selected = 0; | 
|  | 426             if (r1 >= 0 && c1 >= 0) { | 
|  | 427                 if (r2 >= 0 && c2 >= 0) { | 
|  | 428                     int min_r = r1 < r2 ? r1 : r2; | 
|  | 429                     int max_r = r1 > r2 ? r1 : r2; | 
|  | 430                     int min_c = c1 < c2 ? c1 : c2; | 
|  | 431                     int max_c = c1 > c2 ? c1 : c2; | 
|  | 432                     if (r >= min_r && r <= max_r && c >= min_c && c <= max_c) | 
|  | 433                         is_selected = 1; | 
|  | 434                 } else if (r == r1 && c == c1) { | 
|  | 435                     is_selected = 1; | 
|  | 436                 } | 
|  | 437             } | 
|  | 438 | 
|  | 439             if (is_selected) { | 
|  | 440                 XSetForeground(dpy, gc, xft_col_sel.pixel); | 
|  | 441                 XFillRectangle(dpy, overlay_win, gc, x, y, cell_w, cell_h); | 
|  | 442             } | 
|  | 443 | 
|  | 444             XSetForeground(dpy, gc, xft_col_fg.pixel); | 
|  | 445             XDrawRectangle(dpy, overlay_win, gc, x, y, cell_w, cell_h); | 
|  | 446 | 
|  | 447             if (font && xftdraw) { | 
|  | 448                 char txt[2] = {grid_chars[r][c], 0}; | 
|  | 449                 XGlyphInfo extents; | 
|  | 450                 XftTextExtentsUtf8(dpy, font, (FcChar8*)txt, strlen(txt), &extents); | 
|  | 451 | 
|  | 452                 int tx = x + (cell_w - extents.width) / 2; | 
|  | 453                 int ty = y + (cell_h - extents.height) / 2 + extents.y; | 
|  | 454 | 
|  | 455                 XftDrawStringUtf8(xftdraw, &xft_col_fg, font, tx, ty, | 
|  | 456                                 (FcChar8*)txt, strlen(txt)); | 
|  | 457             } | 
|  | 458         } | 
|  | 459     } | 
|  | 460 | 
|  | 461     if (overlay_input[0] || overlay_input[1]) { | 
|  | 462         char status[64]; | 
|  | 463         snprintf(status, sizeof(status), "Input: %c%c", | 
|  | 464                 overlay_input[0] ? overlay_input[0] : ' ', | 
|  | 465                 overlay_input[1] ? overlay_input[1] : ' '); | 
|  | 466 | 
|  | 467         if (font && xftdraw) { | 
|  | 468             XftDrawStringUtf8(xftdraw, &xft_col_fg, font, 20, sh - 20, | 
|  | 469                             (FcChar8*)status, strlen(status)); | 
|  | 470         } | 
|  | 471     } | 
|  | 472 | 
|  | 473     XFlush(dpy); | 
|  | 474 } | 
|  | 475 | 
|  | 476 static void enter_overlay(const Arg *arg) { | 
|  | 477     if (!focused) return; | 
|  | 478 | 
|  | 479     overlay_mode = 1; | 
|  | 480     memset(overlay_input, 0, sizeof(overlay_input)); | 
|  | 481 | 
|  | 482     if (!overlay_win) { | 
|  | 483         XSetWindowAttributes wa = { | 
|  | 484             .override_redirect = True, | 
|  | 485             .background_pixel = BlackPixel(dpy, DefaultScreen(dpy)), | 
|  | 486             .event_mask = ExposureMask | KeyPressMask | 
|  | 487         }; | 
|  | 488         overlay_win = XCreateWindow(dpy, root, 0, 0, sw, sh, 0, | 
|  | 489             CopyFromParent, InputOutput, CopyFromParent, | 
|  | 490             CWOverrideRedirect | CWBackPixel | CWEventMask, &wa); | 
|  | 491 | 
|  | 492         gc = XCreateGC(dpy, overlay_win, 0, NULL); | 
|  | 493 | 
|  | 494         Visual *visual = DefaultVisual(dpy, DefaultScreen(dpy)); | 
|  | 495         Colormap cmap = DefaultColormap(dpy, DefaultScreen(dpy)); | 
|  | 496         xftdraw = XftDrawCreate(dpy, overlay_win, visual, cmap); | 
|  | 497 | 
|  | 498         unsigned long opacity = (unsigned long)(0.85 * 0xffffffff); | 
|  | 499         Atom atom = XInternAtom(dpy, "_NET_WM_WINDOW_OPACITY", False); | 
|  | 500         XChangeProperty(dpy, overlay_win, atom, XA_CARDINAL, 32, | 
|  | 501                        PropModeReplace, (unsigned char *)&opacity, 1); | 
|  | 502     } | 
|  | 503 | 
|  | 504     XMapRaised(dpy, overlay_win); | 
|  | 505     XSetInputFocus(dpy, overlay_win, RevertToPointerRoot, CurrentTime); | 
|  | 506     draw_overlay(); | 
|  | 507 } | 
|  | 508 | 
|  | 509 static void hide_overlay(void) { | 
|  | 510     overlay_mode = 0; | 
|  | 511     memset(overlay_input, 0, sizeof(overlay_input)); | 
|  | 512     if (overlay_win) { | 
|  | 513         XUnmapWindow(dpy, overlay_win); | 
|  | 514     } | 
|  | 515     if (focused) { | 
|  | 516         XSetInputFocus(dpy, focused->win, RevertToPointerRoot, CurrentTime); | 
|  | 517     } | 
|  | 518 } | 
|  | 519 | 
|  | 520 static void process_overlay_input(void) { | 
|  | 521     if (!focused || overlay_input[0] == 0 || overlay_input[1] == 0) return; | 
|  | 522 | 
|  | 523     int r1 = -1, c1 = -1, r2 = -1, c2 = -1; | 
|  | 524     for (int r = 0; r < GRID_ROWS; r++) { | 
|  | 525         for (int c = 0; c < GRID_COLS; c++) { | 
|  | 526             if (grid_chars[r][c] == overlay_input[0]) { r1 = r; c1 = c; } | 
|  | 527             if (grid_chars[r][c] == overlay_input[1]) { r2 = r; c2 = c; } | 
|  | 528         } | 
|  | 529     } | 
|  | 530     if (r1 == -1 || r2 == -1) return; | 
|  | 531 | 
|  | 532     if (r1 > r2) { int t = r1; r1 = r2; r2 = t; } | 
|  | 533     if (c1 > c2) { int t = c1; c1 = c2; c2 = t; } | 
|  | 534 | 
|  | 535     int cols_span = c2 - c1 + 1; | 
|  | 536     int rows_span = r2 - r1 + 1; | 
|  | 537 | 
|  | 538     int cell_w = (sw - padding * (GRID_COLS + 1)) / GRID_COLS; | 
|  | 539     int cell_h = (sh - padding * (GRID_ROWS + 1)) / GRID_ROWS; | 
|  | 540 | 
|  | 541     int x = padding + c1 * (cell_w + padding); | 
|  | 542     int y = padding + r1 * (cell_h + padding); | 
|  | 543     int w = cols_span * cell_w + (cols_span - 1) * padding; | 
|  | 544     int h = rows_span * cell_h + (rows_span - 1) * padding; | 
|  | 545 | 
|  | 546     resize(focused, x, y, w, h); | 
|  | 547 } | 
|  | 548 | 
| 1 | 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 | 
| 0 | 609 // Action functions | 
|  | 610 static int sendevent(Client *c, Atom proto) { | 
|  | 611     int n; | 
|  | 612     Atom *protocols; | 
|  | 613     int exists = 0; | 
|  | 614     XEvent ev; | 
|  | 615 | 
|  | 616     if (XGetWMProtocols(dpy, c->win, &protocols, &n)) { | 
|  | 617         while (!exists && n--) | 
|  | 618             exists = protocols[n] == proto; | 
|  | 619         XFree(protocols); | 
|  | 620     } | 
|  | 621     if (exists) { | 
|  | 622         ev.type = ClientMessage; | 
|  | 623         ev.xclient.window = c->win; | 
|  | 624         ev.xclient.message_type = wm_protocols; | 
|  | 625         ev.xclient.format = 32; | 
|  | 626         ev.xclient.data.l[0] = proto; | 
|  | 627         ev.xclient.data.l[1] = CurrentTime; | 
|  | 628         XSendEvent(dpy, c->win, False, NoEventMask, &ev); | 
|  | 629     } | 
|  | 630     return exists; | 
|  | 631 } | 
|  | 632 | 
|  | 633 static void killclient(const Arg *arg) { | 
|  | 634     if (!focused) return; | 
|  | 635     if (!sendevent(focused, wm_delete_window)) { | 
|  | 636         XGrabServer(dpy); | 
|  | 637         XSetCloseDownMode(dpy, DestroyAll); | 
|  | 638         XKillClient(dpy, focused->win); | 
|  | 639         XSync(dpy, False); | 
|  | 640         XUngrabServer(dpy); | 
|  | 641     } | 
|  | 642 } | 
|  | 643 | 
|  | 644 static void setfullscreen(Client *c, int fullscreen) { | 
|  | 645     if (!c) return; | 
|  | 646 | 
|  | 647     if (fullscreen && !c->isfullscreen) { | 
|  | 648         // Save current position before going fullscreen | 
|  | 649         c->saved_x = c->x; | 
|  | 650         c->saved_y = c->y; | 
|  | 651         c->saved_w = c->w; | 
|  | 652         c->saved_h = c->h; | 
|  | 653 | 
|  | 654         c->isfullscreen = 1; | 
|  | 655 | 
|  | 656         // Remove border and set to full screen | 
|  | 657         XSetWindowBorderWidth(dpy, c->win, 0); | 
|  | 658         resize(c, 0, 0, sw, sh); | 
|  | 659         XRaiseWindow(dpy, c->win); | 
|  | 660 | 
|  | 661     } else if (!fullscreen && c->isfullscreen) { | 
|  | 662         // Restore saved position | 
|  | 663         c->isfullscreen = 0; | 
|  | 664 | 
|  | 665         // Restore border | 
|  | 666         XSetWindowBorderWidth(dpy, c->win, border_width); | 
|  | 667 | 
|  | 668         // Restore original position | 
|  | 669         resize(c, c->saved_x, c->saved_y, c->saved_w, c->saved_h); | 
|  | 670     } | 
|  | 671 | 
|  | 672     // Update _NET_WM_STATE | 
|  | 673     XEvent ev; | 
|  | 674     ev.type = ClientMessage; | 
|  | 675     ev.xclient.window = c->win; | 
|  | 676     ev.xclient.message_type = XInternAtom(dpy, "_NET_WM_STATE", False); | 
|  | 677     ev.xclient.format = 32; | 
|  | 678     ev.xclient.data.l[0] = fullscreen ? 1 : 0; | 
|  | 679     ev.xclient.data.l[1] = XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", False); | 
|  | 680     ev.xclient.data.l[2] = 0; | 
|  | 681     XSendEvent(dpy, root, False, SubstructureNotifyMask | SubstructureRedirectMask, &ev); | 
|  | 682 } | 
|  | 683 | 
|  | 684 static void toggle_fullscreen(const Arg *arg) { | 
|  | 685     if (!focused) return; | 
|  | 686     setfullscreen(focused, !focused->isfullscreen); | 
|  | 687 } | 
|  | 688 | 
|  | 689 static void spawn(const Arg *arg) { | 
|  | 690     if (fork() == 0) { | 
|  | 691         if (dpy) | 
|  | 692             close(ConnectionNumber(dpy)); | 
|  | 693         setsid(); | 
|  | 694         execvp(((char **)arg->v)[0], (char **)arg->v); | 
|  | 695         fprintf(stderr, "eowm: execvp %s failed\n", ((char **)arg->v)[0]); | 
|  | 696         exit(1); | 
|  | 697     } | 
|  | 698 } | 
|  | 699 | 
|  | 700 static void quit(const Arg *arg) { | 
|  | 701     exit(0); | 
|  | 702 } | 
|  | 703 | 
|  | 704 static void cycle_focus(const Arg *arg) { | 
| 1 | 705     if (!workspaces[current_ws]) return; | 
| 0 | 706 | 
|  | 707     if (!focused) { | 
| 1 | 708         focus(workspaces[current_ws]); | 
| 0 | 709         return; | 
|  | 710     } | 
|  | 711 | 
|  | 712     Client *next = focused->next; | 
| 1 | 713     if (!next) next = workspaces[current_ws]; | 
| 0 | 714 | 
|  | 715     focus(next); | 
|  | 716 } | 
|  | 717 | 
|  | 718 static void grabkeys(void) { | 
|  | 719     XUngrabKey(dpy, AnyKey, AnyModifier, root); | 
|  | 720     for (unsigned int i = 0; i < sizeof(keys) / sizeof(Key); i++) { | 
|  | 721         KeyCode code = XKeysymToKeycode(dpy, keys[i].keysym); | 
|  | 722         if (code) { | 
|  | 723             XGrabKey(dpy, code, keys[i].mod, root, True, | 
|  | 724                      GrabModeAsync, GrabModeAsync); | 
|  | 725             XGrabKey(dpy, code, keys[i].mod | Mod2Mask, root, True, | 
|  | 726                      GrabModeAsync, GrabModeAsync); | 
|  | 727         } | 
|  | 728     } | 
|  | 729 } | 
|  | 730 | 
|  | 731 static void sigchld(int s) { | 
|  | 732     (void)s; | 
|  | 733     while (waitpid(-1, NULL, WNOHANG) > 0); | 
|  | 734 } | 
|  | 735 | 
|  | 736 int xerror_handler(Display *dpy, XErrorEvent *ee) { | 
|  | 737     return 0; | 
|  | 738 } | 
|  | 739 | 
|  | 740 static void setup_colors(void) { | 
|  | 741     Visual *visual = DefaultVisual(dpy, DefaultScreen(dpy)); | 
|  | 742     Colormap cmap = DefaultColormap(dpy, DefaultScreen(dpy)); | 
|  | 743 | 
|  | 744     XftColorAllocName(dpy, visual, cmap, col_bg, &xft_col_bg); | 
|  | 745     XftColorAllocName(dpy, visual, cmap, col_fg, &xft_col_fg); | 
|  | 746     XftColorAllocName(dpy, visual, cmap, col_sel, &xft_col_sel); | 
|  | 747 | 
|  | 748     font = XftFontOpenName(dpy, DefaultScreen(dpy), "monospace:size=48"); | 
|  | 749 | 
|  | 750     // Allocate border colors | 
|  | 751     XColor color; | 
|  | 752     XParseColor(dpy, cmap, col_border_normal, &color); | 
|  | 753     XAllocColor(dpy, cmap, &color); | 
|  | 754     border_normal = color.pixel; | 
|  | 755 | 
|  | 756     XParseColor(dpy, cmap, col_border_focused, &color); | 
|  | 757     XAllocColor(dpy, cmap, &color); | 
|  | 758     border_focused = color.pixel; | 
|  | 759 } | 
|  | 760 | 
|  | 761 static void setup_icccm(void) { | 
|  | 762     wm_protocols = XInternAtom(dpy, "WM_PROTOCOLS", False); | 
|  | 763     wm_delete_window = XInternAtom(dpy, "WM_DELETE_WINDOW", False); | 
|  | 764     wm_state = XInternAtom(dpy, "WM_STATE", False); | 
|  | 765     wm_take_focus = XInternAtom(dpy, "WM_TAKE_FOCUS", False); | 
|  | 766 } | 
|  | 767 | 
|  | 768 int main(void) { | 
|  | 769     signal(SIGCHLD, sigchld); | 
|  | 770     if (!(dpy = XOpenDisplay(NULL))) { | 
|  | 771         fprintf(stderr, "eowm: cannot open display\n"); | 
|  | 772         exit(1); | 
|  | 773     } | 
|  | 774     XSetErrorHandler(xerror_handler); | 
|  | 775 | 
|  | 776     sw = DisplayWidth(dpy, DefaultScreen(dpy)); | 
|  | 777     sh = DisplayHeight(dpy, DefaultScreen(dpy)); | 
|  | 778     root = RootWindow(dpy, DefaultScreen(dpy)); | 
|  | 779 | 
|  | 780     setup_colors(); | 
|  | 781     setup_icccm(); | 
|  | 782 | 
|  | 783     XSelectInput(dpy, root, | 
|  | 784         SubstructureRedirectMask | SubstructureNotifyMask | | 
|  | 785         EnterWindowMask | LeaveWindowMask | FocusChangeMask); | 
|  | 786 | 
|  | 787     grabkeys(); | 
|  | 788 | 
|  | 789     XEvent ev; | 
|  | 790     while (1) { | 
|  | 791         XNextEvent(dpy, &ev); | 
|  | 792         switch (ev.type) { | 
|  | 793             case ButtonPress: buttonpress(&ev); break; | 
|  | 794             case ClientMessage: clientmessage(&ev); break; | 
|  | 795             case MapRequest: maprequest(&ev); break; | 
|  | 796             case UnmapNotify: unmapnotify(&ev); break; | 
|  | 797             case DestroyNotify: destroynotify(&ev); break; | 
|  | 798             case EnterNotify: enternotify(&ev); break; | 
|  | 799             case KeyPress: keypress(&ev); break; | 
|  | 800             case Expose: expose(&ev); break; | 
|  | 801         } | 
|  | 802     } | 
|  | 803 | 
|  | 804     XCloseDisplay(dpy); | 
|  | 805     return 0; | 
| 1 | 806 } |