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