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}