| 0 | 1 /* eowm - grid-based tiling window manager */ | 
|  | 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; | 
|  | 32     Client *next; | 
|  | 33 }; | 
|  | 34 | 
|  | 35 typedef union { | 
|  | 36     int i; | 
|  | 37     unsigned int ui; | 
|  | 38     float f; | 
|  | 39     const void *v; | 
|  | 40 } Arg; | 
|  | 41 | 
|  | 42 typedef struct { | 
|  | 43     unsigned int mod; | 
|  | 44     KeySym keysym; | 
|  | 45     void (*func)(const Arg *); | 
|  | 46     const Arg arg; | 
|  | 47 } Key; | 
|  | 48 | 
|  | 49 // Configuration | 
|  | 50 static const int padding = 8; | 
|  | 51 static const int border_width = 2; | 
|  | 52 static const char *col_border_normal = "#444444"; | 
|  | 53 static const char *col_border_focused = "#4a90e2"; | 
|  | 54 static const char *col_bg = "#000000"; | 
|  | 55 static const char *col_fg = "#ffffff"; | 
|  | 56 static const char *col_sel = "#4a90e2"; | 
|  | 57 | 
|  | 58 static Display *dpy; | 
|  | 59 static Window root; | 
|  | 60 static Client *clients = NULL; | 
|  | 61 static Client *focused = NULL; | 
|  | 62 static int sw, sh; | 
|  | 63 static int overlay_mode = 0; | 
|  | 64 static char overlay_input[3] = {0}; | 
|  | 65 static Window overlay_win = 0; | 
|  | 66 static GC gc; | 
|  | 67 static XftDraw *xftdraw = NULL; | 
|  | 68 static XftFont *font = NULL; | 
|  | 69 static XftColor xft_col_bg, xft_col_fg, xft_col_sel; | 
|  | 70 static unsigned long border_normal, border_focused; | 
|  | 71 | 
|  | 72 // ICCCM atoms | 
|  | 73 static Atom wm_protocols, wm_delete_window, wm_state, wm_take_focus; | 
|  | 74 | 
|  | 75 // Forward decls | 
|  | 76 static void arrange(void); | 
|  | 77 static void resize(Client *c, int x, int y, int w, int h); | 
|  | 78 static void focus(Client *c); | 
|  | 79 static void spawn(const Arg *arg); | 
|  | 80 static void killclient(const Arg *arg); | 
|  | 81 static void toggle_fullscreen(const Arg *arg); | 
|  | 82 static void enter_overlay(const Arg *arg); | 
|  | 83 static void process_overlay_input(void); | 
|  | 84 static void draw_overlay(void); | 
|  | 85 static void hide_overlay(void); | 
|  | 86 static void quit(const Arg *arg); | 
|  | 87 static void cycle_focus(const Arg *arg); | 
|  | 88 static void grabkeys(void); | 
|  | 89 static void setfullscreen(Client *c, int fullscreen); | 
|  | 90 static int sendevent(Client *c, Atom proto); | 
|  | 91 static void updateborder(Client *c); | 
|  | 92 static void find_next_free_cell(int *out_r, int *out_c); | 
|  | 93 | 
|  | 94 // Commands | 
|  | 95 static const char *termcmd[] = { "alacritty", NULL }; | 
|  | 96 static const char *menucmd[] = { "dmenu_run", NULL }; | 
|  | 97 | 
|  | 98 // Key bindings | 
|  | 99 static Key keys[] = { | 
|  | 100     /* modifier         key              function         argument */ | 
|  | 101     { MOD,              XK_t,            enter_overlay,   {0} }, | 
|  | 102     { MOD,              XK_Return,       spawn,           {.v = termcmd} }, | 
|  | 103     { MOD,              XK_p,            spawn,           {.v = menucmd} }, | 
|  | 104     { MOD,              XK_q,            killclient,      {0} }, | 
|  | 105     { MOD,              XK_f,            toggle_fullscreen, {0} }, | 
|  | 106     { MOD,              XK_Tab,          cycle_focus,     {0} }, | 
|  | 107     { MOD|ShiftMask,    XK_q,            quit,            {0} }, | 
|  | 108 }; | 
|  | 109 | 
|  | 110 // Event handlers | 
|  | 111 static void buttonpress(XEvent *e) { | 
|  | 112     for (Client *c = clients; c; c = c->next) | 
|  | 113         if (c->win == e->xbutton.subwindow) { | 
|  | 114             focus(c); | 
|  | 115             break; | 
|  | 116         } | 
|  | 117 } | 
|  | 118 | 
|  | 119 static void clientmessage(XEvent *e) { | 
|  | 120     XClientMessageEvent *cme = &e->xclient; | 
|  | 121     Client *c; | 
|  | 122     for (c = clients; c; c = c->next) | 
|  | 123         if (c->win == cme->window) | 
|  | 124             break; | 
|  | 125     if (!c) | 
|  | 126         return; | 
|  | 127 | 
|  | 128     if (cme->message_type == wm_state && cme->data.l[1] == (long)XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", False)) { | 
|  | 129         setfullscreen(c, cme->data.l[0] == 1 || (cme->data.l[0] == 2 && !c->isfullscreen)); | 
|  | 130     } | 
|  | 131 } | 
|  | 132 | 
|  | 133 static void maprequest(XEvent *e) { | 
|  | 134     XWindowAttributes wa; | 
|  | 135     if (!XGetWindowAttributes(dpy, e->xmaprequest.window, &wa)) return; | 
|  | 136     if (wa.override_redirect) return; | 
|  | 137 | 
|  | 138     Client *c = calloc(1, sizeof(Client)); | 
|  | 139     c->win = e->xmaprequest.window; | 
|  | 140     c->next = clients; | 
|  | 141     clients = c; | 
|  | 142 | 
|  | 143     // ICCCM setup | 
|  | 144     XSetWindowBorderWidth(dpy, c->win, border_width); | 
|  | 145     XSelectInput(dpy, c->win, EnterWindowMask | FocusChangeMask | PropertyChangeMask | StructureNotifyMask); | 
|  | 146 | 
|  | 147     // Set WM_STATE | 
|  | 148     long data[] = { NormalState, None }; | 
|  | 149     XChangeProperty(dpy, c->win, wm_state, wm_state, 32, PropModeReplace, (unsigned char *)data, 2); | 
|  | 150 | 
|  | 151     XMapWindow(dpy, c->win); | 
|  | 152     focus(c); | 
|  | 153     arrange(); | 
|  | 154 } | 
|  | 155 | 
|  | 156 static void unmapnotify(XEvent *e) { | 
|  | 157     Client **p; | 
|  | 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 } | 
|  | 171 | 
|  | 172 static void destroynotify(XEvent *e) { | 
|  | 173     unmapnotify(e); | 
|  | 174 } | 
|  | 175 | 
|  | 176 static void enternotify(XEvent *e) { | 
|  | 177     if (e->xcrossing.mode != NotifyNormal || e->xcrossing.detail == NotifyInferior) | 
|  | 178         return; | 
|  | 179     for (Client *c = clients; c; c = c->next) | 
|  | 180         if (c->win == e->xcrossing.window) { | 
|  | 181             focus(c); | 
|  | 182             break; | 
|  | 183         } | 
|  | 184 } | 
|  | 185 | 
|  | 186 static void expose(XEvent *e) { | 
|  | 187     if (e->xexpose.window == overlay_win && overlay_mode) { | 
|  | 188         draw_overlay(); | 
|  | 189     } | 
|  | 190 } | 
|  | 191 | 
|  | 192 static void keypress(XEvent *e) { | 
|  | 193     if (overlay_mode) { | 
|  | 194         KeySym k = XLookupKeysym(&e->xkey, 0); | 
|  | 195         if (k == XK_Escape) { | 
|  | 196             hide_overlay(); | 
|  | 197             return; | 
|  | 198         } | 
|  | 199 | 
|  | 200         char ch = 0; | 
|  | 201         if (k >= '0' && k <= '9') ch = (char)k; | 
|  | 202         else if (k >= 'a' && k <= 'z') ch = (char)k; | 
|  | 203         else if (k >= 'A' && k <= 'Z') ch = (char)(k - 'A' + 'a'); | 
|  | 204         else return; | 
|  | 205 | 
|  | 206         int found = 0; | 
|  | 207         for (int r = 0; r < GRID_ROWS && !found; r++) | 
|  | 208             for (int c = 0; c < GRID_COLS && !found; c++) | 
|  | 209                 if (grid_chars[r][c] == ch) found = 1; | 
|  | 210         if (!found) return; | 
|  | 211 | 
|  | 212         if (overlay_input[0] == 0) { | 
|  | 213             overlay_input[0] = ch; | 
|  | 214             draw_overlay(); | 
|  | 215         } else if (overlay_input[1] == 0) { | 
|  | 216             overlay_input[1] = ch; | 
|  | 217             draw_overlay(); | 
|  | 218             usleep(150000); | 
|  | 219             process_overlay_input(); | 
|  | 220             hide_overlay(); | 
|  | 221         } | 
|  | 222         return; | 
|  | 223     } | 
|  | 224 | 
|  | 225     KeySym keysym = XLookupKeysym(&e->xkey, 0); | 
|  | 226     unsigned int state = e->xkey.state & ~(LockMask | Mod2Mask); | 
|  | 227 | 
|  | 228     for (unsigned int i = 0; i < sizeof(keys) / sizeof(Key); i++) { | 
|  | 229         if (keysym == keys[i].keysym && state == keys[i].mod && keys[i].func) { | 
|  | 230             keys[i].func(&keys[i].arg); | 
|  | 231             return; | 
|  | 232         } | 
|  | 233     } | 
|  | 234 } | 
|  | 235 | 
|  | 236 // Core logic | 
|  | 237 static void resize(Client *c, int x, int y, int w, int h) { | 
|  | 238     c->x = x; c->y = y; c->w = w; c->h = h; | 
|  | 239     XMoveResizeWindow(dpy, c->win, x, y, w, h); | 
|  | 240 } | 
|  | 241 | 
|  | 242 static void updateborder(Client *c) { | 
|  | 243     XSetWindowBorder(dpy, c->win, c == focused ? border_focused : border_normal); | 
|  | 244 } | 
|  | 245 | 
|  | 246 static void focus(Client *c) { | 
|  | 247     if (!c) return; | 
|  | 248 | 
|  | 249     Client *old = focused; | 
|  | 250     focused = c; | 
|  | 251 | 
|  | 252     if (old && old != c) | 
|  | 253         updateborder(old); | 
|  | 254 | 
|  | 255     updateborder(c); | 
|  | 256     XRaiseWindow(dpy, c->win); | 
|  | 257     XSetInputFocus(dpy, c->win, RevertToPointerRoot, CurrentTime); | 
|  | 258     sendevent(c, wm_take_focus); | 
|  | 259 } | 
|  | 260 | 
|  | 261 static int is_cell_free(int r, int c, int cell_w, int cell_h) { | 
|  | 262     int cell_x = padding + c * (cell_w + padding); | 
|  | 263     int cell_y = padding + r * (cell_h + padding); | 
|  | 264 | 
|  | 265     // Check if any window overlaps with this cell | 
|  | 266     for (Client *cl = clients; cl; cl = cl->next) { | 
|  | 267         if (cl->isfullscreen) continue; | 
|  | 268 | 
|  | 269         // Check for overlap | 
|  | 270         int cl_right = cl->x + cl->w; | 
|  | 271         int cl_bottom = cl->y + cl->h; | 
|  | 272         int cell_right = cell_x + cell_w; | 
|  | 273         int cell_bottom = cell_y + cell_h; | 
|  | 274 | 
|  | 275         // If rectangles overlap | 
|  | 276         if (!(cl_right <= cell_x || cl->x >= cell_right || | 
|  | 277               cl_bottom <= cell_y || cl->y >= cell_bottom)) { | 
|  | 278             return 0;  // Cell is occupied | 
|  | 279         } | 
|  | 280     } | 
|  | 281 | 
|  | 282     return 1;  // Cell is free | 
|  | 283 } | 
|  | 284 | 
|  | 285 static void find_next_free_cell(int *out_r, int *out_c) { | 
|  | 286     int cell_w = (sw - padding * (GRID_COLS + 1)) / GRID_COLS; | 
|  | 287     int cell_h = (sh - padding * (GRID_ROWS + 1)) / GRID_ROWS; | 
|  | 288 | 
|  | 289     // First pass: look for any completely free cell | 
|  | 290     for (int r = 0; r < GRID_ROWS; r++) { | 
|  | 291         for (int c = 0; c < GRID_COLS; c++) { | 
|  | 292             if (is_cell_free(r, c, cell_w, cell_h)) { | 
|  | 293                 *out_r = r; | 
|  | 294                 *out_c = c; | 
|  | 295                 return; | 
|  | 296             } | 
|  | 297         } | 
|  | 298     } | 
|  | 299 | 
|  | 300     // Second pass: no free space found, check top-left for 1x1 windows | 
|  | 301     int cell_x = padding; | 
|  | 302     int cell_y = padding; | 
|  | 303 | 
|  | 304     for (Client *cl = clients; cl; cl = cl->next) { | 
|  | 305         if (cl->isfullscreen) continue; | 
|  | 306         if (cl->x == cell_x && cl->y == cell_y && | 
|  | 307             cl->w == cell_w && cl->h == cell_h) { | 
|  | 308             // Found a 1x1 window at top-left, find next free cell | 
|  | 309             for (int r = 0; r < GRID_ROWS; r++) { | 
|  | 310                 for (int c = 0; c < GRID_COLS; c++) { | 
|  | 311                     int check_x = padding + c * (cell_w + padding); | 
|  | 312                     int check_y = padding + r * (cell_h + padding); | 
|  | 313 | 
|  | 314                     int found_1x1 = 0; | 
|  | 315                     for (Client *check = clients; check; check = check->next) { | 
|  | 316                         if (check->isfullscreen) continue; | 
|  | 317                         if (check->x == check_x && check->y == check_y && | 
|  | 318                             check->w == cell_w && check->h == cell_h) { | 
|  | 319                             found_1x1 = 1; | 
|  | 320                             break; | 
|  | 321                         } | 
|  | 322                     } | 
|  | 323 | 
|  | 324                     if (!found_1x1) { | 
|  | 325                         *out_r = r; | 
|  | 326                         *out_c = c; | 
|  | 327                         return; | 
|  | 328                     } | 
|  | 329                 } | 
|  | 330             } | 
|  | 331             break; | 
|  | 332         } | 
|  | 333     } | 
|  | 334 | 
|  | 335     // Fallback to top-left | 
|  | 336     *out_r = 0; | 
|  | 337     *out_c = 0; | 
|  | 338 } | 
|  | 339 | 
|  | 340 static void arrange(void) { | 
|  | 341     if (!clients) return; | 
|  | 342 | 
|  | 343     if (!focused) focused = clients; | 
|  | 344 | 
|  | 345     if (focused->isfullscreen) { | 
|  | 346         return; | 
|  | 347     } | 
|  | 348 | 
|  | 349     // Default window location - find next free cell | 
|  | 350     int cell_w = (sw - padding * (GRID_COLS + 1)) / GRID_COLS; | 
|  | 351     int cell_h = (sh - padding * (GRID_ROWS + 1)) / GRID_ROWS; | 
|  | 352 | 
|  | 353     if (focused->w == 0 || focused->h == 0) { | 
|  | 354         int r, c; | 
|  | 355         find_next_free_cell(&r, &c); | 
|  | 356         int x = padding + c * (cell_w + padding); | 
|  | 357         int y = padding + r * (cell_h + padding); | 
|  | 358         resize(focused, x, y, cell_w, cell_h); | 
|  | 359     } | 
|  | 360 | 
|  | 361     // Update all borders | 
|  | 362     for (Client *c = clients; c; c = c->next) | 
|  | 363         updateborder(c); | 
|  | 364 } | 
|  | 365 | 
|  | 366 static void draw_overlay(void) { | 
|  | 367     if (!overlay_win) return; | 
|  | 368 | 
|  | 369     XClearWindow(dpy, overlay_win); | 
|  | 370 | 
|  | 371     int cell_w = (sw - padding * (GRID_COLS + 1)) / GRID_COLS; | 
|  | 372     int cell_h = (sh - padding * (GRID_ROWS + 1)) / GRID_ROWS; | 
|  | 373 | 
|  | 374     int r1 = -1, c1 = -1, r2 = -1, c2 = -1; | 
|  | 375     if (overlay_input[0]) { | 
|  | 376         for (int r = 0; r < GRID_ROWS; r++) | 
|  | 377             for (int c = 0; c < GRID_COLS; c++) | 
|  | 378                 if (grid_chars[r][c] == overlay_input[0]) { r1 = r; c1 = c; } | 
|  | 379     } | 
|  | 380     if (overlay_input[1]) { | 
|  | 381         for (int r = 0; r < GRID_ROWS; r++) | 
|  | 382             for (int c = 0; c < GRID_COLS; c++) | 
|  | 383                 if (grid_chars[r][c] == overlay_input[1]) { r2 = r; c2 = c; } | 
|  | 384     } | 
|  | 385 | 
|  | 386     for (int r = 0; r < GRID_ROWS; r++) { | 
|  | 387         for (int c = 0; c < GRID_COLS; c++) { | 
|  | 388             int x = padding + c * (cell_w + padding); | 
|  | 389             int y = padding + r * (cell_h + padding); | 
|  | 390 | 
|  | 391             int is_selected = 0; | 
|  | 392             if (r1 >= 0 && c1 >= 0) { | 
|  | 393                 if (r2 >= 0 && c2 >= 0) { | 
|  | 394                     int min_r = r1 < r2 ? r1 : r2; | 
|  | 395                     int max_r = r1 > r2 ? r1 : r2; | 
|  | 396                     int min_c = c1 < c2 ? c1 : c2; | 
|  | 397                     int max_c = c1 > c2 ? c1 : c2; | 
|  | 398                     if (r >= min_r && r <= max_r && c >= min_c && c <= max_c) | 
|  | 399                         is_selected = 1; | 
|  | 400                 } else if (r == r1 && c == c1) { | 
|  | 401                     is_selected = 1; | 
|  | 402                 } | 
|  | 403             } | 
|  | 404 | 
|  | 405             if (is_selected) { | 
|  | 406                 XSetForeground(dpy, gc, xft_col_sel.pixel); | 
|  | 407                 XFillRectangle(dpy, overlay_win, gc, x, y, cell_w, cell_h); | 
|  | 408             } | 
|  | 409 | 
|  | 410             XSetForeground(dpy, gc, xft_col_fg.pixel); | 
|  | 411             XDrawRectangle(dpy, overlay_win, gc, x, y, cell_w, cell_h); | 
|  | 412 | 
|  | 413             if (font && xftdraw) { | 
|  | 414                 char txt[2] = {grid_chars[r][c], 0}; | 
|  | 415                 XGlyphInfo extents; | 
|  | 416                 XftTextExtentsUtf8(dpy, font, (FcChar8*)txt, strlen(txt), &extents); | 
|  | 417 | 
|  | 418                 int tx = x + (cell_w - extents.width) / 2; | 
|  | 419                 int ty = y + (cell_h - extents.height) / 2 + extents.y; | 
|  | 420 | 
|  | 421                 XftDrawStringUtf8(xftdraw, &xft_col_fg, font, tx, ty, | 
|  | 422                                 (FcChar8*)txt, strlen(txt)); | 
|  | 423             } | 
|  | 424         } | 
|  | 425     } | 
|  | 426 | 
|  | 427     if (overlay_input[0] || overlay_input[1]) { | 
|  | 428         char status[64]; | 
|  | 429         snprintf(status, sizeof(status), "Input: %c%c", | 
|  | 430                 overlay_input[0] ? overlay_input[0] : ' ', | 
|  | 431                 overlay_input[1] ? overlay_input[1] : ' '); | 
|  | 432 | 
|  | 433         if (font && xftdraw) { | 
|  | 434             XftDrawStringUtf8(xftdraw, &xft_col_fg, font, 20, sh - 20, | 
|  | 435                             (FcChar8*)status, strlen(status)); | 
|  | 436         } | 
|  | 437     } | 
|  | 438 | 
|  | 439     XFlush(dpy); | 
|  | 440 } | 
|  | 441 | 
|  | 442 static void enter_overlay(const Arg *arg) { | 
|  | 443     if (!focused) return; | 
|  | 444 | 
|  | 445     overlay_mode = 1; | 
|  | 446     memset(overlay_input, 0, sizeof(overlay_input)); | 
|  | 447 | 
|  | 448     if (!overlay_win) { | 
|  | 449         XSetWindowAttributes wa = { | 
|  | 450             .override_redirect = True, | 
|  | 451             .background_pixel = BlackPixel(dpy, DefaultScreen(dpy)), | 
|  | 452             .event_mask = ExposureMask | KeyPressMask | 
|  | 453         }; | 
|  | 454         overlay_win = XCreateWindow(dpy, root, 0, 0, sw, sh, 0, | 
|  | 455             CopyFromParent, InputOutput, CopyFromParent, | 
|  | 456             CWOverrideRedirect | CWBackPixel | CWEventMask, &wa); | 
|  | 457 | 
|  | 458         gc = XCreateGC(dpy, overlay_win, 0, NULL); | 
|  | 459 | 
|  | 460         Visual *visual = DefaultVisual(dpy, DefaultScreen(dpy)); | 
|  | 461         Colormap cmap = DefaultColormap(dpy, DefaultScreen(dpy)); | 
|  | 462         xftdraw = XftDrawCreate(dpy, overlay_win, visual, cmap); | 
|  | 463 | 
|  | 464         unsigned long opacity = (unsigned long)(0.85 * 0xffffffff); | 
|  | 465         Atom atom = XInternAtom(dpy, "_NET_WM_WINDOW_OPACITY", False); | 
|  | 466         XChangeProperty(dpy, overlay_win, atom, XA_CARDINAL, 32, | 
|  | 467                        PropModeReplace, (unsigned char *)&opacity, 1); | 
|  | 468     } | 
|  | 469 | 
|  | 470     XMapRaised(dpy, overlay_win); | 
|  | 471     XSetInputFocus(dpy, overlay_win, RevertToPointerRoot, CurrentTime); | 
|  | 472     draw_overlay(); | 
|  | 473 } | 
|  | 474 | 
|  | 475 static void hide_overlay(void) { | 
|  | 476     overlay_mode = 0; | 
|  | 477     memset(overlay_input, 0, sizeof(overlay_input)); | 
|  | 478     if (overlay_win) { | 
|  | 479         XUnmapWindow(dpy, overlay_win); | 
|  | 480     } | 
|  | 481     if (focused) { | 
|  | 482         XSetInputFocus(dpy, focused->win, RevertToPointerRoot, CurrentTime); | 
|  | 483     } | 
|  | 484 } | 
|  | 485 | 
|  | 486 static void process_overlay_input(void) { | 
|  | 487     if (!focused || overlay_input[0] == 0 || overlay_input[1] == 0) return; | 
|  | 488 | 
|  | 489     int r1 = -1, c1 = -1, r2 = -1, c2 = -1; | 
|  | 490     for (int r = 0; r < GRID_ROWS; r++) { | 
|  | 491         for (int c = 0; c < GRID_COLS; c++) { | 
|  | 492             if (grid_chars[r][c] == overlay_input[0]) { r1 = r; c1 = c; } | 
|  | 493             if (grid_chars[r][c] == overlay_input[1]) { r2 = r; c2 = c; } | 
|  | 494         } | 
|  | 495     } | 
|  | 496     if (r1 == -1 || r2 == -1) return; | 
|  | 497 | 
|  | 498     if (r1 > r2) { int t = r1; r1 = r2; r2 = t; } | 
|  | 499     if (c1 > c2) { int t = c1; c1 = c2; c2 = t; } | 
|  | 500 | 
|  | 501     int cols_span = c2 - c1 + 1; | 
|  | 502     int rows_span = r2 - r1 + 1; | 
|  | 503 | 
|  | 504     int cell_w = (sw - padding * (GRID_COLS + 1)) / GRID_COLS; | 
|  | 505     int cell_h = (sh - padding * (GRID_ROWS + 1)) / GRID_ROWS; | 
|  | 506 | 
|  | 507     int x = padding + c1 * (cell_w + padding); | 
|  | 508     int y = padding + r1 * (cell_h + padding); | 
|  | 509     int w = cols_span * cell_w + (cols_span - 1) * padding; | 
|  | 510     int h = rows_span * cell_h + (rows_span - 1) * padding; | 
|  | 511 | 
|  | 512     resize(focused, x, y, w, h); | 
|  | 513 } | 
|  | 514 | 
|  | 515 // Action functions | 
|  | 516 static int sendevent(Client *c, Atom proto) { | 
|  | 517     int n; | 
|  | 518     Atom *protocols; | 
|  | 519     int exists = 0; | 
|  | 520     XEvent ev; | 
|  | 521 | 
|  | 522     if (XGetWMProtocols(dpy, c->win, &protocols, &n)) { | 
|  | 523         while (!exists && n--) | 
|  | 524             exists = protocols[n] == proto; | 
|  | 525         XFree(protocols); | 
|  | 526     } | 
|  | 527     if (exists) { | 
|  | 528         ev.type = ClientMessage; | 
|  | 529         ev.xclient.window = c->win; | 
|  | 530         ev.xclient.message_type = wm_protocols; | 
|  | 531         ev.xclient.format = 32; | 
|  | 532         ev.xclient.data.l[0] = proto; | 
|  | 533         ev.xclient.data.l[1] = CurrentTime; | 
|  | 534         XSendEvent(dpy, c->win, False, NoEventMask, &ev); | 
|  | 535     } | 
|  | 536     return exists; | 
|  | 537 } | 
|  | 538 | 
|  | 539 static void killclient(const Arg *arg) { | 
|  | 540     if (!focused) return; | 
|  | 541     if (!sendevent(focused, wm_delete_window)) { | 
|  | 542         XGrabServer(dpy); | 
|  | 543         XSetCloseDownMode(dpy, DestroyAll); | 
|  | 544         XKillClient(dpy, focused->win); | 
|  | 545         XSync(dpy, False); | 
|  | 546         XUngrabServer(dpy); | 
|  | 547     } | 
|  | 548 } | 
|  | 549 | 
|  | 550 static void setfullscreen(Client *c, int fullscreen) { | 
|  | 551     if (!c) return; | 
|  | 552 | 
|  | 553     if (fullscreen && !c->isfullscreen) { | 
|  | 554         // Save current position before going fullscreen | 
|  | 555         c->saved_x = c->x; | 
|  | 556         c->saved_y = c->y; | 
|  | 557         c->saved_w = c->w; | 
|  | 558         c->saved_h = c->h; | 
|  | 559 | 
|  | 560         c->isfullscreen = 1; | 
|  | 561 | 
|  | 562         // Remove border and set to full screen | 
|  | 563         XSetWindowBorderWidth(dpy, c->win, 0); | 
|  | 564         resize(c, 0, 0, sw, sh); | 
|  | 565         XRaiseWindow(dpy, c->win); | 
|  | 566 | 
|  | 567     } else if (!fullscreen && c->isfullscreen) { | 
|  | 568         // Restore saved position | 
|  | 569         c->isfullscreen = 0; | 
|  | 570 | 
|  | 571         // Restore border | 
|  | 572         XSetWindowBorderWidth(dpy, c->win, border_width); | 
|  | 573 | 
|  | 574         // Restore original position | 
|  | 575         resize(c, c->saved_x, c->saved_y, c->saved_w, c->saved_h); | 
|  | 576     } | 
|  | 577 | 
|  | 578     // Update _NET_WM_STATE | 
|  | 579     XEvent ev; | 
|  | 580     ev.type = ClientMessage; | 
|  | 581     ev.xclient.window = c->win; | 
|  | 582     ev.xclient.message_type = XInternAtom(dpy, "_NET_WM_STATE", False); | 
|  | 583     ev.xclient.format = 32; | 
|  | 584     ev.xclient.data.l[0] = fullscreen ? 1 : 0; | 
|  | 585     ev.xclient.data.l[1] = XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", False); | 
|  | 586     ev.xclient.data.l[2] = 0; | 
|  | 587     XSendEvent(dpy, root, False, SubstructureNotifyMask | SubstructureRedirectMask, &ev); | 
|  | 588 } | 
|  | 589 | 
|  | 590 static void toggle_fullscreen(const Arg *arg) { | 
|  | 591     if (!focused) return; | 
|  | 592     setfullscreen(focused, !focused->isfullscreen); | 
|  | 593 } | 
|  | 594 | 
|  | 595 static void spawn(const Arg *arg) { | 
|  | 596     if (fork() == 0) { | 
|  | 597         if (dpy) | 
|  | 598             close(ConnectionNumber(dpy)); | 
|  | 599         setsid(); | 
|  | 600         execvp(((char **)arg->v)[0], (char **)arg->v); | 
|  | 601         fprintf(stderr, "eowm: execvp %s failed\n", ((char **)arg->v)[0]); | 
|  | 602         exit(1); | 
|  | 603     } | 
|  | 604 } | 
|  | 605 | 
|  | 606 static void quit(const Arg *arg) { | 
|  | 607     exit(0); | 
|  | 608 } | 
|  | 609 | 
|  | 610 static void cycle_focus(const Arg *arg) { | 
|  | 611     if (!clients) return; | 
|  | 612 | 
|  | 613     if (!focused) { | 
|  | 614         focus(clients); | 
|  | 615         return; | 
|  | 616     } | 
|  | 617 | 
|  | 618     Client *next = focused->next; | 
|  | 619     if (!next) next = clients; | 
|  | 620 | 
|  | 621     focus(next); | 
|  | 622 } | 
|  | 623 | 
|  | 624 static void grabkeys(void) { | 
|  | 625     XUngrabKey(dpy, AnyKey, AnyModifier, root); | 
|  | 626     for (unsigned int i = 0; i < sizeof(keys) / sizeof(Key); i++) { | 
|  | 627         KeyCode code = XKeysymToKeycode(dpy, keys[i].keysym); | 
|  | 628         if (code) { | 
|  | 629             XGrabKey(dpy, code, keys[i].mod, root, True, | 
|  | 630                      GrabModeAsync, GrabModeAsync); | 
|  | 631             XGrabKey(dpy, code, keys[i].mod | Mod2Mask, root, True, | 
|  | 632                      GrabModeAsync, GrabModeAsync); | 
|  | 633         } | 
|  | 634     } | 
|  | 635 } | 
|  | 636 | 
|  | 637 static void sigchld(int s) { | 
|  | 638     (void)s; | 
|  | 639     while (waitpid(-1, NULL, WNOHANG) > 0); | 
|  | 640 } | 
|  | 641 | 
|  | 642 int xerror_handler(Display *dpy, XErrorEvent *ee) { | 
|  | 643     return 0; | 
|  | 644 } | 
|  | 645 | 
|  | 646 static void setup_colors(void) { | 
|  | 647     Visual *visual = DefaultVisual(dpy, DefaultScreen(dpy)); | 
|  | 648     Colormap cmap = DefaultColormap(dpy, DefaultScreen(dpy)); | 
|  | 649 | 
|  | 650     XftColorAllocName(dpy, visual, cmap, col_bg, &xft_col_bg); | 
|  | 651     XftColorAllocName(dpy, visual, cmap, col_fg, &xft_col_fg); | 
|  | 652     XftColorAllocName(dpy, visual, cmap, col_sel, &xft_col_sel); | 
|  | 653 | 
|  | 654     font = XftFontOpenName(dpy, DefaultScreen(dpy), "monospace:size=48"); | 
|  | 655 | 
|  | 656     // Allocate border colors | 
|  | 657     XColor color; | 
|  | 658     XParseColor(dpy, cmap, col_border_normal, &color); | 
|  | 659     XAllocColor(dpy, cmap, &color); | 
|  | 660     border_normal = color.pixel; | 
|  | 661 | 
|  | 662     XParseColor(dpy, cmap, col_border_focused, &color); | 
|  | 663     XAllocColor(dpy, cmap, &color); | 
|  | 664     border_focused = color.pixel; | 
|  | 665 } | 
|  | 666 | 
|  | 667 static void setup_icccm(void) { | 
|  | 668     wm_protocols = XInternAtom(dpy, "WM_PROTOCOLS", False); | 
|  | 669     wm_delete_window = XInternAtom(dpy, "WM_DELETE_WINDOW", False); | 
|  | 670     wm_state = XInternAtom(dpy, "WM_STATE", False); | 
|  | 671     wm_take_focus = XInternAtom(dpy, "WM_TAKE_FOCUS", False); | 
|  | 672 } | 
|  | 673 | 
|  | 674 int main(void) { | 
|  | 675     signal(SIGCHLD, sigchld); | 
|  | 676     if (!(dpy = XOpenDisplay(NULL))) { | 
|  | 677         fprintf(stderr, "eowm: cannot open display\n"); | 
|  | 678         exit(1); | 
|  | 679     } | 
|  | 680     XSetErrorHandler(xerror_handler); | 
|  | 681 | 
|  | 682     sw = DisplayWidth(dpy, DefaultScreen(dpy)); | 
|  | 683     sh = DisplayHeight(dpy, DefaultScreen(dpy)); | 
|  | 684     root = RootWindow(dpy, DefaultScreen(dpy)); | 
|  | 685 | 
|  | 686     setup_colors(); | 
|  | 687     setup_icccm(); | 
|  | 688 | 
|  | 689     XSelectInput(dpy, root, | 
|  | 690         SubstructureRedirectMask | SubstructureNotifyMask | | 
|  | 691         EnterWindowMask | LeaveWindowMask | FocusChangeMask); | 
|  | 692 | 
|  | 693     grabkeys(); | 
|  | 694 | 
|  | 695     XEvent ev; | 
|  | 696     while (1) { | 
|  | 697         XNextEvent(dpy, &ev); | 
|  | 698         switch (ev.type) { | 
|  | 699             case ButtonPress: buttonpress(&ev); break; | 
|  | 700             case ClientMessage: clientmessage(&ev); break; | 
|  | 701             case MapRequest: maprequest(&ev); break; | 
|  | 702             case UnmapNotify: unmapnotify(&ev); break; | 
|  | 703             case DestroyNotify: destroynotify(&ev); break; | 
|  | 704             case EnterNotify: enternotify(&ev); break; | 
|  | 705             case KeyPress: keypress(&ev); break; | 
|  | 706             case Expose: expose(&ev); break; | 
|  | 707         } | 
|  | 708     } | 
|  | 709 | 
|  | 710     XCloseDisplay(dpy); | 
|  | 711     return 0; | 
|  | 712 } |