1#include <stdlib.h>
  2
  3// Comment 1
  4// Comment 2
  5
  6#include <X11/Xlib.h>
  7#include <X11/Xatom.h>
  8#include <X11/keysym.h>
  9#include <X11/XF86keysym.h>
 10#include <X11/cursorfont.h>
 11
 12#include "glitch.h"
 13#include "config.h"
 14
 15#define MAX 100
 16
 17extern WindowManager wm;
 18
 19static Atom _NET_WM_DESKTOP;
 20static Atom _NET_CURRENT_DESKTOP;
 21static Atom _NET_NUMBER_OF_DESKTOPS;
 22static Atom _NET_CLIENT_LIST;
 23static Atom _NET_WM_STATE;
 24static Atom _NET_WM_STATE_FULLSCREEN;
 25static Atom _NET_ACTIVE_WINDOW;
 26
 27void init_window_manager(void) {
 28    wm.dpy = XOpenDisplay(NULL);
 29    if (!wm.dpy) {
 30        log_message(stdout, LOG_ERROR, "Cannot open display");
 31        abort();
 32    }
 33
 34    wm.screen = DefaultScreen(wm.dpy);
 35    wm.root = RootWindow(wm.dpy, wm.screen);
 36
 37    // Create and sets up cursors.
 38    wm.cursor_default = XCreateFontCursor(wm.dpy, XC_left_ptr);
 39    wm.cursor_move = XCreateFontCursor(wm.dpy, XC_fleur);
 40    wm.cursor_resize  = XCreateFontCursor(wm.dpy, XC_sizing);
 41    XDefineCursor(wm.dpy, wm.root, wm.cursor_default);
 42    log_message(stdout, LOG_DEBUG, "Setting up default cursors");
 43
 44    // Root window input selection masks.
 45    XSelectInput(wm.dpy, wm.root,
 46            SubstructureRedirectMask | SubstructureNotifyMask |
 47            FocusChangeMask | EnterWindowMask | LeaveWindowMask |
 48            ButtonPressMask | ExposureMask | PropertyChangeMask);
 49
 50    // Initialize EWMH atoms.
 51    _NET_WM_DESKTOP = XInternAtom(wm.dpy, "_NET_WM_DESKTOP", False);
 52    _NET_CURRENT_DESKTOP = XInternAtom(wm.dpy, "_NET_CURRENT_DESKTOP", False);
 53    _NET_NUMBER_OF_DESKTOPS = XInternAtom(wm.dpy, "_NET_NUMBER_OF_DESKTOPS", False);
 54    _NET_CLIENT_LIST = XInternAtom(wm.dpy, "_NET_CLIENT_LIST", False);
 55    _NET_WM_STATE = XInternAtom(wm.dpy, "_NET_WM_STATE", False);
 56    _NET_WM_STATE_FULLSCREEN = XInternAtom(wm.dpy, "_NET_WM_STATE_FULLSCREEN", False);
 57    _NET_ACTIVE_WINDOW = XInternAtom(wm.dpy, "_NET_ACTIVE_WINDOW", False);
 58
 59    // Set number of desktops and current desktop.
 60    static unsigned long num_desktops = NUM_DESKTOPS;
 61    XChangeProperty(wm.dpy, wm.root, _NET_NUMBER_OF_DESKTOPS, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&num_desktops, 1);
 62    XChangeProperty(wm.dpy, wm.root, _NET_CURRENT_DESKTOP, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&num_desktops, 1);
 63    log_message(stdout, LOG_DEBUG, "Registering %d desktops", NUM_DESKTOPS);
 64
 65    // Grab keys for keybinds.
 66    for (unsigned int i = 0; i < LENGTH(keybinds); i++) {
 67        KeyCode keycode = XKeysymToKeycode(wm.dpy, keybinds[i].keysym);
 68        if (keycode) {
 69            XGrabKey(wm.dpy, keycode, keybinds[i].mod, wm.root, True, GrabModeAsync, GrabModeAsync);
 70            log_message(stdout, LOG_DEBUG, "Grabbed key: mod=0x%x, keysym=0x%lx", keybinds[i].mod, keybinds[i].keysym);
 71        }
 72    }
 73
 74    // Grab keys for shortcuts.
 75    for (unsigned int i = 0; i < LENGTH(shortcuts); i++) {
 76        KeyCode keycode = XKeysymToKeycode(wm.dpy, shortcuts[i].keysym);
 77        if (keycode) {
 78            XGrabKey(wm.dpy, keycode, shortcuts[i].mod, wm.root, True, GrabModeAsync, GrabModeAsync);
 79            log_message(stdout, LOG_DEBUG, "Grabbed shortcut: mod=0x%x, keysym=0x%lx, command=%s", shortcuts[i].mod, shortcuts[i].keysym, shortcuts[i].cmd);
 80        }
 81    }
 82
 83    // Grab keys for window dragging (with MODKEY).
 84    XGrabButton(wm.dpy, 1, MODKEY, wm.root, True, ButtonPressMask|ButtonReleaseMask|PointerMotionMask, GrabModeAsync, GrabModeAsync, None, None);
 85    XGrabButton(wm.dpy, 3, MODKEY, wm.root, True, ButtonPressMask|ButtonReleaseMask|PointerMotionMask, GrabModeAsync, GrabModeAsync, None, None);
 86    log_message(stdout, LOG_DEBUG, "Registering grab keys for window dragging");
 87
 88    // Prepare border colors.
 89    wm.cmap = DefaultColormap(wm.dpy, wm.screen);
 90    XColor active_color, inactive_color, sticky_active_color, sticky_inactive_color, dummy;
 91
 92    wm.borders.normal_active = BlackPixel(wm.dpy, wm.screen);
 93    wm.borders.normal_inactive = BlackPixel(wm.dpy, wm.screen);
 94    wm.borders.sticky_active = BlackPixel(wm.dpy, wm.screen);
 95    wm.borders.sticky_inactive = BlackPixel(wm.dpy, wm.screen);
 96
 97    if (XAllocNamedColor(wm.dpy, wm.cmap, active_border_color, &active_color, &dummy)) {
 98        wm.borders.normal_active = active_color.pixel;
 99    }
100
101    if (XAllocNamedColor(wm.dpy, wm.cmap, inactive_border_color, &inactive_color, &dummy)) {
102        wm.borders.normal_inactive = inactive_color.pixel;
103    }
104
105    if (XAllocNamedColor(wm.dpy, wm.cmap, sticky_active_border_color, &sticky_active_color, &dummy)) {
106        wm.borders.sticky_active = sticky_active_color.pixel;
107    }
108
109    if (XAllocNamedColor(wm.dpy, wm.cmap, sticky_inactive_border_color, &sticky_inactive_color, &dummy)) {
110        wm.borders.sticky_inactive = sticky_inactive_color.pixel;
111    }
112    
113    XSync(wm.dpy, False);
114}
115
116void deinit_window_manager(void) {
117    XFreeCursor(wm.dpy, wm.cursor_default);
118    XFreeCursor(wm.dpy, wm.cursor_move);
119    XFreeCursor(wm.dpy, wm.cursor_resize);
120}
121
122static int ignore_x_error(Display *dpy, XErrorEvent *err) {
123    (void)dpy;
124    (void)err;
125    return 0;
126}
127
128int window_exists(Window window) {
129    if (window == None) return 0;
130    XErrorHandler old = XSetErrorHandler(ignore_x_error);
131    XWindowAttributes attr;
132    Status status = XGetWindowAttributes(wm.dpy, window, &attr);
133    XSync(wm.dpy, False);
134    XSetErrorHandler(old);
135    return status != 0;
136}
137
138void set_active_window(Window window) {
139    if (window != None) {
140        XChangeProperty(wm.dpy, wm.root, _NET_ACTIVE_WINDOW, XA_WINDOW, 32, PropModeReplace, (unsigned char *)&window, 1);
141        wm.active = window;
142    } else {
143        XDeleteProperty(wm.dpy, wm.root, _NET_ACTIVE_WINDOW);
144    }
145    XFlush(wm.dpy);
146}
147
148Window get_active_window(void) {
149    Atom _NET_ACTIVE_WINDOW = XInternAtom(wm.dpy, "_NET_ACTIVE_WINDOW", False);
150    Atom actual_type;
151    int actual_format;
152    unsigned long nitems, bytes_after;
153    unsigned char *prop = NULL;
154    Window active = None;
155
156    if (XGetWindowProperty(wm.dpy, wm.root, _NET_ACTIVE_WINDOW, 0, (~0L), False, AnyPropertyType, &actual_type, &actual_format, &nitems, &bytes_after, &prop) == Success) {
157        if (prop && nitems >= 1) {
158            active = *(Window *)prop;
159        }
160    }
161
162    if (prop) XFree(prop);
163    return active;
164}
165
166void get_cursor_offset(Window window, int *dx, int *dy) {
167    Window root, child;
168    int root_x, root_y;
169    unsigned int mask;
170    XQueryPointer(wm.dpy, window, &root, &child, &root_x, &root_y, dx, dy, &mask);
171}
172
173// https://tronche.com/gui/x/xlib/events/structure-control/map.html
174void handle_map_request(void) {
175    Window window = wm.ev.xmaprequest.window;
176
177    // Move window under cursor position and clamps inside the screen bounds.
178    XWindowAttributes check_attr;
179    if (XGetWindowAttributes(wm.dpy, window, &check_attr)) {
180        XSelectInput(wm.dpy, window, EnterWindowMask | LeaveWindowMask);
181
182        Window root_return, child_return;
183        int root_x, root_y, win_x, win_y;
184        unsigned int mask;
185
186        if (XQueryPointer(wm.dpy, wm.root, &root_return, &child_return, &root_x, &root_y, &win_x, &win_y, &mask)) {
187            int new_x = root_x - (check_attr.width / 2);
188            int new_y = root_y - (check_attr.height / 2);
189            int screen_width = DisplayWidth(wm.dpy, wm.screen);
190            int screen_height = DisplayHeight(wm.dpy, wm.screen);
191
192            if (new_x < 0) new_x = 0;
193            if (new_y < 0) new_y = 0;
194            if (new_x + check_attr.width > screen_width) new_x = screen_width - check_attr.width;
195            if (new_y + check_attr.height > screen_height) new_y = screen_height - check_attr.height;
196
197            XMoveWindow(wm.dpy, window, new_x, new_y);
198            log_message(stdout, LOG_DEBUG, "Positioned new window 0x%lx at cursor (%d, %d)", window, root_x, root_y);
199        }
200    }
201
202    // Shows, raises and focuses the window.
203    set_active_border(window);
204    set_active_window(window);
205
206    XMapWindow(wm.dpy, window);
207    XRaiseWindow(wm.dpy, window);
208    XSetInputFocus(wm.dpy, window, RevertToPointerRoot, CurrentTime);
209
210    log_message(stdout, LOG_DEBUG, "Window 0x%lx mapped", window);
211}
212
213// https://tronche.com/gui/x/xlib/events/window-state-change/unmap.html
214void handle_unmap_notify(void) {
215    Window window = wm.ev.xunmap.window;
216    log_message(stdout, LOG_DEBUG, "Window 0x%lx unmapped", window);
217}
218
219// https://tronche.com/gui/x/xlib/events/window-state-change/destroy.html
220void handle_destroy_notify(void) {
221    Window window = wm.ev.xdestroywindow.window;
222    log_message(stdout, LOG_DEBUG, "Window 0x%lx destroyed", window);
223}
224
225// https://tronche.com/gui/x/xlib/events/client-communication/property.html
226void handle_property_notify(void) {
227    Window window = wm.ev.xproperty.window;
228    Atom prop = wm.ev.xproperty.atom;
229    char *name = XGetAtomName(wm.dpy, prop);
230    log_message(stdout, LOG_DEBUG, "Window 0x%lx got property notification %s", window, name);
231}
232
233// https://tronche.com/gui/x/xlib/events/keyboard-pointer/keyboard-pointer.html
234void handle_motion_notify(void) {
235    if (wm.start.subwindow != None && (wm.start.state & MODKEY)) {
236        int xdiff = wm.ev.xmotion.x_root - wm.start.x_root;
237        int ydiff = wm.ev.xmotion.y_root - wm.start.y_root;
238
239        XMoveResizeWindow(wm.dpy, wm.start.subwindow,
240                wm.attr.x + (wm.start.button == 1 ? xdiff : 0),
241                wm.attr.y + (wm.start.button == 1 ? ydiff : 0),
242                MAX(100, wm.attr.width  + (wm.start.button == 3 ? xdiff : 0)),
243                MAX(100, wm.attr.height + (wm.start.button == 3 ? ydiff : 0)));
244    }
245}
246
247// https://tronche.com/gui/x/xlib/events/client-communication/client-message.html
248void handle_client_message(void) {
249    Window window = wm.ev.xclient.window;
250    int message_type = wm.ev.xclient.message_type;
251    log_message(stdout, LOG_DEBUG, "Window 0x%lx got message type of %d", window, message_type);
252}
253
254// https://tronche.com/gui/x/xlib/events/keyboard-pointer/keyboard-pointer.html
255void handle_button_press(void) {
256    Window window = wm.ev.xbutton.subwindow;
257    if (window == None) return;
258
259    if (wm.ev.xbutton.state & MODKEY) {
260        XRaiseWindow(wm.dpy, window);
261        XGetWindowAttributes(wm.dpy, window, &wm.attr);
262        wm.start = wm.ev.xbutton;
263
264        set_active_border(window);
265        set_active_window(window);
266
267        switch (wm.ev.xbutton.button) {
268            case 1: {
269                XDefineCursor(wm.dpy, window, wm.cursor_move);
270                log_message(stdout, LOG_DEBUG, "Setting cursor to move");
271            } break;
272            case 3: {
273                XDefineCursor(wm.dpy, window, wm.cursor_resize);
274                log_message(stdout, LOG_DEBUG, "Setting cursor to resize");
275            } break;
276        }
277
278        log_message(stdout, LOG_DEBUG, "Window 0x%lx got button press press", window);
279        XFlush(wm.dpy);
280    }
281}
282
283// https://tronche.com/gui/x/xlib/events/keyboard-pointer/keyboard-pointer.html
284void handle_button_release(void) {
285    Window window = wm.ev.xbutton.subwindow;
286    if (window == None) return;
287
288    if (wm.start.state & MODKEY) {
289        XDefineCursor(wm.dpy, wm.start.subwindow, None);
290        log_message(stdout, LOG_DEBUG, "Setting cursor to resize");
291    }
292
293    log_message(stdout, LOG_DEBUG, "Window 0x%lx got button release", window);
294    XFlush(wm.dpy);
295}
296
297// https://tronche.com/gui/x/xlib/events/keyboard-pointer/keyboard-pointer.html
298void handle_key_press(void) {
299    log_message(stdout, LOG_DEBUG, ">> Key pressed > active window 0x%lx", wm.ev.xkey.subwindow);
300    if (wm.ev.type != KeyPress) return;
301    if (wm.ev.xkey.subwindow == None) return;
302
303    // TODO: Check why XkbKeycodeToKeysym worked in previous version.
304    /* KeySym keysym = XkbKeycodeToKeysym(wm.dpy, wm.ev.xkey.keycode, 0, 0); */
305    KeySym keysym = XLookupKeysym(&wm.ev.xkey, 0);
306
307    // Check keybinds first.
308    for (unsigned int i = 0; i < LENGTH(keybinds); i++) {
309        if (keysym == keybinds[i].keysym && (wm.ev.xkey.state & (Mod1Mask|Mod2Mask|Mod3Mask|Mod4Mask|ControlMask|ShiftMask)) == keybinds[i].mod) {
310            keybinds[i].func(&keybinds[i].arg);
311            break;
312        }
313    }
314
315    XFlush(wm.dpy);
316}
317
318void handle_key_release(void) {}
319
320void handle_focus_in(void) {
321    Window window = wm.ev.xfocus.window;
322    if (window != wm.root) {
323        log_message(stdout, LOG_DEBUG, "Window 0x%lx focus in", window);
324    }
325}
326
327void handle_focus_out(void) {
328    Window window = wm.ev.xfocus.window;
329    if (window != wm.root) {
330        log_message(stdout, LOG_DEBUG, "Window 0x%lx focus out", window);
331    }
332}
333
334void handle_enter_notify(void) {
335    Window window = wm.ev.xcrossing.window;
336    if (window != wm.root) {
337        set_active_border(window);
338        set_active_window(window);
339        log_message(stdout, LOG_DEBUG, "Window 0x%lx enter notify", window);
340    }
341}
342
343void set_active_border(Window window) {
344    if (window == None) return;
345
346    // Setting current active window to inactive.
347    if (wm.active != None) {
348        XSetWindowBorderWidth(wm.dpy, wm.active, border_size);
349        XSetWindowBorder(wm.dpy, wm.active, wm.borders.normal_inactive);
350        log_message(stdout, LOG_DEBUG, "Active window 0x%lx border set to inactive", window);
351    }
352
353    // Setting desired window to active.
354    XSetWindowBorderWidth(wm.dpy, window, border_size);
355    XSetWindowBorder(wm.dpy, window, wm.borders.normal_active);
356    XFlush(wm.dpy);
357
358    log_message(stdout, LOG_DEBUG, "Desired window 0x%lx border set to active", window);
359}
360
361void move_window_x(const Arg *arg) {
362    if (wm.active == None) return;
363
364    XWindowAttributes attr;
365    XGetWindowAttributes(wm.dpy, wm.active, &attr);
366    XMoveWindow(wm.dpy, wm.active, attr.x + arg->i, attr.y);
367    log_message(stdout, LOG_DEBUG, "Move window 0x%lx on X by %d", wm.active, arg->i);
368
369    int rel_x, rel_y;
370    get_cursor_offset(wm.active, &rel_x, &rel_y);
371    XWarpPointer(wm.dpy, None, wm.active, 0, 0, 0, 0, rel_x + arg->i, rel_y);
372
373    XSync(wm.dpy, True);
374    XFlush(wm.dpy);
375}
376
377void move_window_y(const Arg *arg) {
378    if (wm.active == None) return;
379
380    XWindowAttributes attr;
381    XGetWindowAttributes(wm.dpy, wm.active, &attr);
382    XMoveWindow(wm.dpy, wm.active, attr.x, attr.y + arg->i);
383    log_message(stdout, LOG_DEBUG, "Move window 0x%lx on Y by %d", wm.active, arg->i);
384
385    int rel_x, rel_y;
386    get_cursor_offset(wm.active, &rel_x, &rel_y);
387    XWarpPointer(wm.dpy, None, wm.active, 0, 0, 0, 0, rel_x, rel_y + arg->i);
388
389    XSync(wm.dpy, True);
390    XFlush(wm.dpy);
391}
392
393void resize_window_x(const Arg *arg) {
394    if (wm.active == None) return;
395
396    XWindowAttributes attr;
397    XGetWindowAttributes(wm.dpy, wm.active, &attr);
398    XResizeWindow(wm.dpy, wm.active, MAX(1, attr.width + arg->i), attr.height);
399    XFlush(wm.dpy);
400
401    log_message(stdout, LOG_DEBUG, "Resize window 0x%lx on X by %d", wm.active, arg->i);
402}
403
404void resize_window_y(const Arg *arg) {
405    if (wm.active == None) return;
406
407    XWindowAttributes attr;
408    XGetWindowAttributes(wm.dpy, wm.active, &attr);
409    XResizeWindow(wm.dpy, wm.active, attr.width, MAX(1, attr.height + arg->i));
410    XFlush(wm.dpy);
411
412    log_message(stdout, LOG_DEBUG, "Resize window 0x%lx on Y by %d", wm.active, arg->i);
413}
414
415void window_snap_up(const Arg *arg) {
416    (void)arg;
417    if (wm.active == None) return;
418
419    XWindowAttributes attr;
420    if (!XGetWindowAttributes(wm.dpy, wm.active, &attr)) {
421        log_message(stdout, LOG_DEBUG, "Failed to get window attributes for 0x%lx", wm.active);
422        return;
423    }
424
425    int rel_x, rel_y;
426    get_cursor_offset(wm.active, &rel_x, &rel_y);
427
428    XMoveWindow(wm.dpy, wm.active, attr.x, 0);
429    XWarpPointer(wm.dpy, None, wm.active, 0, 0, 0, 0, rel_x, rel_y);
430    XFlush(wm.dpy);
431
432    log_message(stdout, LOG_DEBUG, "Snapped window 0x%lx to top edge", wm.active);
433}
434
435void window_snap_down(const Arg *arg) {
436    (void)arg;
437    if (wm.active == None) return;
438
439    XWindowAttributes attr;
440    if (!XGetWindowAttributes(wm.dpy, wm.active, &attr)) {
441        log_message(stdout, LOG_DEBUG, "Failed to get window attributes for 0x%lx", wm.active);
442        return;
443    }
444
445    int rel_x, rel_y;
446    int y = DisplayHeight(wm.dpy, DefaultScreen(wm.dpy)) - attr.height - (2 * attr.border_width);
447    get_cursor_offset(wm.active, &rel_x, &rel_y);
448    
449    XMoveWindow(wm.dpy, wm.active, attr.x, y);
450    XWarpPointer(wm.dpy, None, wm.active, 0, 0, 0, 0, rel_x, rel_y);
451    XFlush(wm.dpy);
452
453    log_message(stdout, LOG_DEBUG, "Snapped window 0x%lx to bottom edge", wm.active);
454}
455
456void window_snap_left(const Arg *arg) {
457    (void)arg;
458    if (wm.active == None) return;
459
460    XWindowAttributes attr;
461    if (!XGetWindowAttributes(wm.dpy, wm.active, &attr)) {
462        log_message(stdout, LOG_DEBUG, "Failed to get window attributes for 0x%lx", wm.active);
463        return;
464    }
465
466    int rel_x, rel_y;
467    get_cursor_offset(wm.active, &rel_x, &rel_y);
468    
469    XMoveWindow(wm.dpy, wm.active, 0, attr.y);
470    XWarpPointer(wm.dpy, None, wm.active, 0, 0, 0, 0, rel_x, rel_y);
471    XFlush(wm.dpy);
472
473    log_message(stdout, LOG_DEBUG, "Snapped window 0x%lx to left edge", wm.active);
474}
475
476void window_snap_right(const Arg *arg) {
477    (void)arg;
478    if (wm.active == None) return;
479
480    XWindowAttributes attr;
481    if (!XGetWindowAttributes(wm.dpy, wm.active, &attr)) {
482        log_message(stdout, LOG_DEBUG, "Failed to get window attributes for 0x%lx", wm.active);
483        return;
484    }
485
486    int rel_x, rel_y;
487    int x = DisplayWidth(wm.dpy, DefaultScreen(wm.dpy)) - attr.width - (2 * attr.border_width);
488    get_cursor_offset(wm.active, &rel_x, &rel_y);
489    
490    XMoveWindow(wm.dpy, wm.active, x, attr.y);
491    XWarpPointer(wm.dpy, None, wm.active, 0, 0, 0, 0, rel_x, rel_y);
492    XFlush(wm.dpy);
493
494    log_message(stdout, LOG_DEBUG, "Snapped window 0x%lx to right edge", wm.active);
495}