]> Pileus Git - wmpus/blob - sys-x11.c
Make settings configurable
[wmpus] / sys-x11.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <search.h>
4
5 #include <X11/Xlib.h>
6 #include <X11/Xproto.h>
7 #include <X11/Xatom.h>
8 #include <X11/keysym.h>
9 #include <X11/extensions/Xinerama.h>
10
11 #include "util.h"
12 #include "sys.h"
13 #include "wm.h"
14
15 #ifndef BORDER
16 #define BORDER 2
17 #endif
18
19 /* Internal structures */
20 struct win_sys {
21         Window   xid;
22         Display *dpy;
23         struct {
24                 int left, right, top, bottom;
25         } strut;
26         state_t  state;
27 };
28
29 typedef struct {
30         Key_t key;
31         int   sym;
32 } keymap_t;
33
34 typedef enum {
35         wm_proto, wm_focus, net_strut, natoms
36 } atom_t;
37
38 typedef enum {
39         clr_focus, clr_unfocus, clr_urgent, ncolors
40 } color_t;
41
42 /* Global data */
43 static void *cache;
44 static Atom atoms[natoms];
45 static int (*xerrorxlib)(Display *, XErrorEvent *);
46 static unsigned long colors[ncolors];
47
48 /* Conversion functions */
49 static keymap_t key2sym[] = {
50         {key_left    , XK_Left },
51         {key_right   , XK_Right},
52         {key_up      , XK_Up   },
53         {key_down    , XK_Down },
54         {key_home    , XK_Home },
55         {key_end     , XK_End  },
56         {key_pageup  , XK_Prior},
57         {key_pagedown, XK_Next },
58         {key_f1      , XK_F1   },
59         {key_f2      , XK_F2   },
60         {key_f3      , XK_F3   },
61         {key_f4      , XK_F4   },
62         {key_f5      , XK_F5   },
63         {key_f6      , XK_F6   },
64         {key_f7      , XK_F7   },
65         {key_f8      , XK_F8   },
66         {key_f9      , XK_F9   },
67         {key_f10     , XK_F10  },
68         {key_f11     , XK_F11  },
69         {key_f12     , XK_F12  },
70 };
71
72 /* - Modifiers */
73 static mod_t x2mod(unsigned int state, int up)
74 {
75         return (mod_t){
76                .alt   = !!(state & Mod1Mask   ),
77                .ctrl  = !!(state & ControlMask),
78                .shift = !!(state & ShiftMask  ),
79                .win   = !!(state & Mod4Mask   ),
80                .up    = up,
81         };
82 }
83
84 static unsigned int mod2x(mod_t mod)
85 {
86         return (mod.alt   ? Mod1Mask    : 0)
87              | (mod.ctrl  ? ControlMask : 0)
88              | (mod.shift ? ShiftMask   : 0)
89              | (mod.win   ? Mod4Mask    : 0);
90 }
91
92 /* - Keycodes */
93 static Key_t x2key(KeySym sym)
94 {
95         keymap_t *km = map_getr(key2sym,sym);
96         return km ? km->key : sym;
97 }
98
99 static KeySym key2x(Key_t key)
100 {
101         keymap_t *km = map_get(key2sym,key);
102         return km ? km->sym : key;
103 }
104
105 static Key_t x2btn(int btn)
106 {
107         return btn + key_mouse0;
108 }
109
110 static int btn2x(Key_t key)
111 {
112         return key - key_mouse0;
113 }
114
115 /* - Pointers */
116 static ptr_t x2ptr(XEvent *_ev)
117 {
118         XKeyEvent *ev = &_ev->xkey;
119         return (ptr_t){ev->x, ev->y, ev->x_root, ev->y_root};
120 }
121
122 static Window getfocus(win_t *root, XEvent *event)
123 {
124         int revert;
125         Window focus = PointerRoot;
126         if (event->type == KeyPress || event->type == KeyRelease)
127                 XGetInputFocus(root->sys->dpy, &focus, &revert);
128         if (focus == PointerRoot)
129                 focus = event->xkey.subwindow;
130         if (focus == None)
131                 focus = event->xkey.window;
132         return focus;
133 }
134
135 /* Helpers */
136 static int add_strut(win_t *root, win_t *win)
137 {
138         /* Get X11 strut data */
139         Atom ret_type;
140         int ret_size;
141         unsigned long ret_items, bytes_left;
142         unsigned char *xdata;
143         int status = XGetWindowProperty(win->sys->dpy, win->sys->xid,
144                         atoms[net_strut], 0L, 4L, False, XA_CARDINAL,
145                         &ret_type, &ret_size, &ret_items, &bytes_left, &xdata);
146         if (status != Success || ret_size != 32 || ret_items != 4)
147                 return 0;
148
149         int left   = ((int*)xdata)[0];
150         int right  = ((int*)xdata)[1];
151         int top    = ((int*)xdata)[2];
152         int bottom = ((int*)xdata)[3];
153         if (left == 0 && right == 0 && top == 0 && bottom == 0)
154                 return 0;
155
156         win->sys->strut.left   = left;
157         win->sys->strut.right  = right;
158         win->sys->strut.top    = top;
159         win->sys->strut.bottom = bottom;
160         root->x += left;
161         root->y += top;
162         root->w -= left+right;
163         root->h -= top+bottom;
164         return 1;
165 }
166
167 static int del_strut(win_t *root, win_t *win)
168 {
169         int left   = win->sys->strut.left;
170         int right  = win->sys->strut.right;
171         int top    = win->sys->strut.top;
172         int bottom = win->sys->strut.bottom;
173         if (left == 0 && right == 0 && top == 0 && bottom == 0)
174                 return 0;
175
176         root->x -= left;
177         root->y -= top;
178         root->w += left+right;
179         root->h += top+bottom;
180         return 1;
181 }
182
183 /* Window functions */
184 static win_t *win_new(Display *dpy, Window xid)
185 {
186         XWindowAttributes attr;
187         if (XGetWindowAttributes(dpy, xid, &attr))
188                 if (attr.override_redirect)
189                         return NULL;
190         win_t *win    = new0(win_t);
191         win->x        = attr.x;
192         win->y        = attr.y;
193         win->w        = attr.width;
194         win->h        = attr.height;
195         win->sys      = new0(win_sys_t);
196         win->sys->dpy = dpy;
197         win->sys->xid = xid;
198         printf("win_new: %p = %p, %d (%d,%d %dx%d)\n",
199                         win, dpy, (int)xid,
200                         win->x, win->y, win->w, win->h);
201         return win;
202 }
203
204 static int win_cmp(const void *_a, const void *_b)
205 {
206         const win_t *a = _a, *b = _b;
207         if (a->sys->dpy < b->sys->dpy) return -1;
208         if (a->sys->dpy > b->sys->dpy) return  1;
209         if (a->sys->xid < b->sys->xid) return -1;
210         if (a->sys->xid > b->sys->xid) return  1;
211         return 0;
212 }
213
214 static win_t *win_find(Display *dpy, Window xid, int create)
215 {
216         if (!dpy || !xid)
217                 return NULL;
218         //printf("win_find: %p, %d\n", dpy, (int)xid);
219         win_sys_t sys = {.dpy=dpy, .xid=xid};
220         win_t     tmp = {.sys=&sys};
221         win_t **old = NULL, *new = NULL;
222         if ((old = tfind(&tmp, &cache, win_cmp)))
223                 return *old;
224         if (create && (new = win_new(dpy,xid)))
225                 tsearch(new, &cache, win_cmp);
226         return new;
227 }
228
229 static void win_remove(win_t *win)
230 {
231         tdelete(win, &cache, win_cmp);
232         free(win->sys);
233         free(win);
234 }
235
236 static int win_viewable(win_t *win)
237 {
238         XWindowAttributes attr;
239         if (XGetWindowAttributes(win->sys->dpy, win->sys->xid, &attr))
240                 return attr.map_state == IsViewable;
241         else
242                 return True;
243 }
244
245 /* Drawing functions */
246 unsigned long get_color(Display *dpy, const char *name)
247 {
248         XColor color;
249         int screen = DefaultScreen(dpy);
250         Colormap cmap = DefaultColormap(dpy, screen);
251         XAllocNamedColor(dpy, cmap, name, &color, &color);
252         return color.pixel;
253 }
254
255 /* Callbacks */
256 static void process_event(int type, XEvent *ev, win_t *root)
257 {
258         Display  *dpy = root->sys->dpy;
259         win_t *win = NULL;
260         //printf("event: %d\n", type);
261
262         /* Common data for all these events ... */
263         ptr_t ptr; mod_t mod;
264         if (type == KeyPress    || type == KeyRelease    ||
265             type == ButtonPress || type == ButtonRelease ||
266             type == MotionNotify) {
267                 Window xid = getfocus(root, ev);
268                 if (!(win = win_find(dpy,xid,0)))
269                         return;
270                 ptr = x2ptr(ev);
271                 mod = x2mod(ev->xkey.state, type==KeyRelease||type==ButtonRelease);
272         }
273
274         /* Split based on event */
275         if (type == KeyPress) {
276                 while (XCheckTypedEvent(dpy, KeyPress, ev));
277                 KeySym sym = XKeycodeToKeysym(dpy, ev->xkey.keycode, 0);
278                 printf("got key %c %hhx\n", x2key(sym), mod2int(mod));
279                 wm_handle_key(win, x2key(sym), mod, ptr);
280         }
281         else if (type == KeyRelease) {
282                 //printf("release: %d\n", type);
283         }
284         else if (type == ButtonPress) {
285                 if (wm_handle_key(win, x2btn(ev->xbutton.button), mod, ptr))
286                         XGrabPointer(dpy, ev->xbutton.root, True, PointerMotionMask|ButtonReleaseMask,
287                                         GrabModeAsync, GrabModeAsync, None, None, CurrentTime);
288                 else {
289                         printf("resending event\n");
290                         XSendEvent(win->sys->dpy, ev->xbutton.window,    True,  NoEventMask, ev);
291                         XSendEvent(win->sys->dpy, ev->xbutton.window,    False, NoEventMask, ev);
292                         XSendEvent(win->sys->dpy, ev->xbutton.root,      True,  NoEventMask, ev);
293                         XSendEvent(win->sys->dpy, ev->xbutton.root,      False, NoEventMask, ev);
294                         XSendEvent(win->sys->dpy, ev->xbutton.subwindow, True,  NoEventMask, ev);
295                         XSendEvent(win->sys->dpy, ev->xbutton.subwindow, False, NoEventMask, ev);
296                 }
297         }
298         else if (type == ButtonRelease) {
299                 XUngrabPointer(dpy, CurrentTime);
300                 wm_handle_key(win, x2btn(ev->xbutton.button), mod, ptr);
301         }
302         else if (type == MotionNotify) {
303                 while (XCheckTypedEvent(dpy, MotionNotify, ev));
304                 wm_handle_ptr(win, ptr);
305         }
306         else if (type == EnterNotify || type == LeaveNotify) {
307                 printf("enter: %d\n", type);
308                 key_t key = EnterNotify ? key_enter : key_leave;
309                 if ((win = win_find(dpy,ev->xcrossing.window,0)))
310                         wm_handle_key(win, key, MOD(), PTR());
311         }
312         else if (type == FocusIn || type == FocusOut) {
313                 //printf("focus: %d\n", type);
314                 key_t key = FocusIn ? key_focus : key_unfocus;
315                 if ((win = win_find(dpy,ev->xfocus.window,0)))
316                         wm_handle_key(win, key, MOD(), PTR());
317         }
318         else if (type == ConfigureNotify) {
319                 printf("configure: %d\n", type);
320         }
321         else if (type == MapNotify) {
322                 printf("map: %d\n", type);
323         }
324         else if (type == UnmapNotify) {
325                 if ((win = win_find(dpy,ev->xunmap.window,0)) &&
326                      win->sys->state == st_show) {
327                         if (!del_strut(root, win))
328                                 wm_remove(win);
329                         else
330                                 wm_update();
331                         win->sys->state = st_hide;
332                 }
333         }
334         else if (type == DestroyNotify) {
335                 //printf("destroy: %d\n", type);
336                 if ((win = win_find(dpy,ev->xdestroywindow.window,0)))
337                         win_remove(win);
338         }
339         else if (type == ConfigureRequest) {
340                 XConfigureRequestEvent *cre = &ev->xconfigurerequest;
341                 printf("configure_req: %d - %x, (0x%lx) %dx%d @ %d,%d\n",
342                                 type, (int)cre->window, cre->value_mask,
343                                 cre->height, cre->width, cre->x, cre->y);
344                 XConfigureWindow(dpy, cre->window, cre->value_mask, &(XWindowChanges){
345                         .x      = cre->x,
346                         .y      = cre->y,
347                         .width  = cre->width,
348                         .height = cre->height,
349                 });
350
351                 /* This seems necessasairy for, but causes flicker
352                  * there could be a better way to do this */
353                 if ((win = win_find(dpy,ev->xmaprequest.window,0)))
354                         sys_move(win, win->x, win->y, win->w, win->h);
355         }
356         else if (type == MapRequest) {
357                 printf("map_req: %d\n", type);
358                 if ((win = win_find(dpy,ev->xmaprequest.window,1))) {
359                         if (!add_strut(root, win))
360                                 wm_insert(win);
361                         else
362                                 wm_update();
363                 }
364                 XMapWindow(dpy,ev->xmaprequest.window);
365         }
366         else {
367                 printf("unknown event: %d\n", type);
368         }
369 }
370
371 static int xerror(Display *dpy, XErrorEvent *err)
372 {
373         if (err->error_code == BadWindow ||
374             (err->request_code == X_SetInputFocus     && err->error_code == BadMatch   ) ||
375             (err->request_code == X_PolyText8         && err->error_code == BadDrawable) ||
376             (err->request_code == X_PolyFillRectangle && err->error_code == BadDrawable) ||
377             (err->request_code == X_PolySegment       && err->error_code == BadDrawable) ||
378             (err->request_code == X_ConfigureWindow   && err->error_code == BadMatch   ) ||
379             (err->request_code == X_GrabButton        && err->error_code == BadAccess  ) ||
380             (err->request_code == X_GrabKey           && err->error_code == BadAccess  ) ||
381             (err->request_code == X_CopyArea          && err->error_code == BadDrawable))
382                 return 0;
383         return xerrorxlib(dpy, err);
384 }
385
386 /*****************
387  * Sys functions *
388  *****************/
389 void sys_move(win_t *win, int x, int y, int w, int h)
390 {
391         //printf("sys_move: %p - %d,%d  %dx%d\n", win, x, y, w, h);
392         int b = 2*BORDER;
393         win->x = x; win->y = y;
394         win->w = MAX(w,1+b); win->h = MAX(h,1+b);
395         w      = MAX(w-b,1); h      = MAX(h-b,1);
396         XMoveResizeWindow(win->sys->dpy, win->sys->xid, x, y, w, h);
397
398         /* Flush events, so moving window doesn't cuase re-focus
399          * There's probably a better way to do this */
400         XEvent ev;
401         XSync(win->sys->dpy, False);
402         while (XCheckMaskEvent(win->sys->dpy, EnterWindowMask|LeaveWindowMask, &ev))
403                 printf("Skipping enter/leave event\n");
404 }
405
406 void sys_raise(win_t *win)
407 {
408         //printf("sys_raise: %p\n", win);
409         XRaiseWindow(win->sys->dpy, win->sys->xid);
410 }
411
412 void sys_focus(win_t *win)
413 {
414         //printf("sys_focus: %p\n", win);
415
416         /* Set border on focused window */
417         static win_t *last = NULL;
418         if (last)
419                 XSetWindowBorder(last->sys->dpy, last->sys->xid, colors[clr_unfocus]);
420         XSetWindowBorder(win->sys->dpy, win->sys->xid, colors[clr_focus]);
421         XSetWindowBorderWidth(win->sys->dpy, win->sys->xid, BORDER);
422         last = win;
423
424         /* Set actual focus */
425         XSetInputFocus(win->sys->dpy, win->sys->xid,
426                         RevertToPointerRoot, CurrentTime);
427         XSendEvent(win->sys->dpy, win->sys->xid, False, NoEventMask, &(XEvent){
428                 .type                 = ClientMessage,
429                 .xclient.window       = win->sys->xid,
430                 .xclient.message_type = atoms[wm_proto],
431                 .xclient.format       = 32,
432                 .xclient.data.l[0]    = atoms[wm_focus],
433                 .xclient.data.l[1]    = CurrentTime,
434         });
435 }
436
437 void sys_show(win_t *win, state_t state)
438 {
439         win->sys->state = state;
440         switch (state) {
441         case st_show:
442                 printf("sys_show: show\n");
443                 XMapWindow(win->sys->dpy, win->sys->xid);
444                 return;
445         case st_full:
446                 printf("sys_show: full\n");
447                 return;
448         case st_shade:
449                 printf("sys_show: shade\n");
450                 return;
451         case st_icon:
452                 printf("sys_show: icon\n");
453                 return;
454         case st_hide:
455                 printf("sys_show: hide\n");
456                 XUnmapWindow(win->sys->dpy, win->sys->xid);
457                 return;
458         }
459 }
460
461 void sys_watch(win_t *win, Key_t key, mod_t mod)
462 {
463         //printf("sys_watch: %p - %x %hhx\n", win, key, mod);
464         XWindowAttributes attr;
465         XGetWindowAttributes(win->sys->dpy, win->sys->xid, &attr);
466         long mask = attr.your_event_mask;
467         if (key_mouse0 <= key && key <= key_mouse7)
468                 XGrabButton(win->sys->dpy, btn2x(key), mod2x(mod), win->sys->xid, False,
469                                 mod.up ? ButtonReleaseMask : ButtonPressMask,
470                                 GrabModeAsync, GrabModeAsync, None, None);
471         else if (key == key_enter)
472                 XSelectInput(win->sys->dpy, win->sys->xid, EnterWindowMask|mask);
473         else if (key == key_leave)
474                 XSelectInput(win->sys->dpy, win->sys->xid, LeaveWindowMask|mask);
475         else if (key == key_focus || key == key_unfocus)
476                 XSelectInput(win->sys->dpy, win->sys->xid, FocusChangeMask|mask);
477         else
478                 XGrabKey(win->sys->dpy, XKeysymToKeycode(win->sys->dpy, key2x(key)),
479                                 mod2x(mod), win->sys->xid, True, GrabModeAsync, GrabModeAsync);
480 }
481
482 void sys_unwatch(win_t *win, Key_t key, mod_t mod)
483 {
484         if (key_mouse0 <= key && key <= key_mouse7)
485                 XUngrabButton(win->sys->dpy, btn2x(key), mod2x(mod), win->sys->xid);
486 }
487
488 list_t *sys_info(win_t *win)
489 {
490         int n;
491         XineramaScreenInfo *info = NULL;
492         if (XineramaIsActive(win->sys->dpy))
493                 info = XineramaQueryScreens(win->sys->dpy, &n);
494         if (!info) {
495                 win_t *screen = new0(win_t);
496                 *screen = *win;
497                 return list_insert(NULL, screen);
498         }
499         list_t *screens = NULL;
500         for (int i = 0; i < n; i++) {
501                 win_t *screen = new0(win_t);
502                 screen->x = info[i].x_org;
503                 screen->y = info[i].y_org;
504                 screen->w = info[i].width;
505                 screen->h = info[i].height;
506                 screens = list_append(screens, screen);
507         }
508         return screens;
509 }
510
511 win_t *sys_init(void)
512 {
513         Display *dpy;
514         Window   xid;
515
516         /* Open the display */
517         if (!(dpy = XOpenDisplay(NULL)))
518                 error("Unable to get display");
519         if (!(xid = DefaultRootWindow(dpy)))
520                 error("Unable to get root window");
521
522         /* Setup X11 data */
523         atoms[wm_proto]  = XInternAtom(dpy, "WM_PROTOCOLS",  False);
524         atoms[wm_focus]  = XInternAtom(dpy, "WM_TAKE_FOCUS", False);
525         atoms[net_strut] = XInternAtom(dpy, "_NET_WM_STRUT", False);
526
527         colors[clr_focus]   = get_color(dpy, "#a0a0ff");
528         colors[clr_unfocus] = get_color(dpy, "#101066");
529         colors[clr_urgent]  = get_color(dpy, "#ff0000");
530         printf("colors = #%06lx #%06lx #%06lx\n", colors[0], colors[1], colors[2]);
531
532         /* Selec Window Managmenet events */
533         XSelectInput(dpy, xid, SubstructureRedirectMask|SubstructureNotifyMask);
534         XSetInputFocus(dpy, None, RevertToNone, CurrentTime);
535         xerrorxlib = XSetErrorHandler(xerror);
536
537         return win_find(dpy, xid, 1);
538 }
539
540 void sys_run(win_t *root)
541 {
542         /* Add each initial window */
543         unsigned int nkids;
544         Window par, xid, *kids = NULL;
545         if (XQueryTree(root->sys->dpy, root->sys->xid,
546                                 &par, &xid, &kids, &nkids))
547                 for(int i = 0; i < nkids; i++) {
548                         win_t *win = win_find(root->sys->dpy, kids[i], 1);
549                         if (win && win_viewable(win) && !add_strut(root,win))
550                                 wm_insert(win);
551                 }
552         wm_update(); // For struts
553
554         /* Main loop */
555         for(;;)
556         {
557                 XEvent ev;
558                 XNextEvent(root->sys->dpy, &ev);
559                 process_event(ev.type, &ev, root);
560         }
561 }