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