xenv: add minimize functionality
[libpicofe.git] / linux / xenv.c
1 /*
2  * (C) GraÅžvydas "notaz" Ignotas, 2009-2012
3  *
4  * This work is licensed under the terms of any of these licenses
5  * (at your option):
6  *  - GNU GPL, version 2 or later.
7  *  - GNU LGPL, version 2.1 or later.
8  * See the COPYING file in the top-level directory.
9  */
10
11 #include <stdio.h>
12 #include <string.h>
13 #include <pthread.h>
14
15 #include <dlfcn.h>
16 #include <X11/Xlib.h>
17 #include <X11/Xutil.h>
18 #include <X11/XKBlib.h>
19
20 #include <sys/types.h>
21 #include <sys/stat.h>
22 #include <fcntl.h>
23 #include <unistd.h>
24 #include <sys/ioctl.h>
25 #include <termios.h>
26 #include <linux/kd.h>
27
28 #define PFX "xenv: "
29
30 #define FPTR(f) typeof(f) * p##f
31 #define FPTR_LINK(xf, dl, f) { \
32         xf.p##f = dlsym(dl, #f); \
33         if (xf.p##f == NULL) { \
34                 fprintf(stderr, "missing symbol: %s\n", #f); \
35                 goto fail; \
36         } \
37 }
38
39 struct xstuff {
40         Display *display;
41         Window window;
42         FPTR(XCreateBitmapFromData);
43         FPTR(XCreatePixmapCursor);
44         FPTR(XFreePixmap);
45         FPTR(XOpenDisplay);
46         FPTR(XDisplayName);
47         FPTR(XCloseDisplay);
48         FPTR(XCreateSimpleWindow);
49         FPTR(XChangeWindowAttributes);
50         FPTR(XSelectInput);
51         FPTR(XMapWindow);
52         FPTR(XNextEvent);
53         FPTR(XCheckTypedEvent);
54         FPTR(XWithdrawWindow);
55         FPTR(XGrabKeyboard);
56         FPTR(XPending);
57         FPTR(XLookupKeysym);
58         FPTR(XkbSetDetectableAutoRepeat);
59         FPTR(XStoreName);
60         FPTR(XIconifyWindow);
61         FPTR(XMoveResizeWindow);
62         FPTR(XInternAtom);
63         FPTR(XSetWMHints);
64         FPTR(XSync);
65 };
66
67 static struct xstuff g_xstuff;
68
69 static Cursor transparent_cursor(struct xstuff *xf, Display *display, Window win)
70 {
71         Cursor cursor;
72         Pixmap pix;
73         XColor dummy;
74         char d = 0;
75
76         memset(&dummy, 0, sizeof(dummy));
77         pix = xf->pXCreateBitmapFromData(display, win, &d, 1, 1);
78         cursor = xf->pXCreatePixmapCursor(display, pix, pix,
79                         &dummy, &dummy, 0, 0);
80         xf->pXFreePixmap(display, pix);
81         return cursor;
82 }
83
84 static int x11h_init(const char *window_title)
85 {
86         unsigned int display_width, display_height;
87         Display *display;
88         XSetWindowAttributes attributes;
89         Window win;
90         Visual *visual;
91         void *x11lib;
92         int screen;
93
94         memset(&g_xstuff, 0, sizeof(g_xstuff));
95         x11lib = dlopen("libX11.so.6", RTLD_LAZY);
96         if (x11lib == NULL) {
97                 fprintf(stderr, "libX11.so load failed:\n%s\n", dlerror());
98                 goto fail;
99         }
100         FPTR_LINK(g_xstuff, x11lib, XCreateBitmapFromData);
101         FPTR_LINK(g_xstuff, x11lib, XCreatePixmapCursor);
102         FPTR_LINK(g_xstuff, x11lib, XFreePixmap);
103         FPTR_LINK(g_xstuff, x11lib, XOpenDisplay);
104         FPTR_LINK(g_xstuff, x11lib, XDisplayName);
105         FPTR_LINK(g_xstuff, x11lib, XCloseDisplay);
106         FPTR_LINK(g_xstuff, x11lib, XCreateSimpleWindow);
107         FPTR_LINK(g_xstuff, x11lib, XChangeWindowAttributes);
108         FPTR_LINK(g_xstuff, x11lib, XSelectInput);
109         FPTR_LINK(g_xstuff, x11lib, XMapWindow);
110         FPTR_LINK(g_xstuff, x11lib, XNextEvent);
111         FPTR_LINK(g_xstuff, x11lib, XCheckTypedEvent);
112         FPTR_LINK(g_xstuff, x11lib, XWithdrawWindow);
113         FPTR_LINK(g_xstuff, x11lib, XGrabKeyboard);
114         FPTR_LINK(g_xstuff, x11lib, XPending);
115         FPTR_LINK(g_xstuff, x11lib, XLookupKeysym);
116         FPTR_LINK(g_xstuff, x11lib, XkbSetDetectableAutoRepeat);
117         FPTR_LINK(g_xstuff, x11lib, XStoreName);
118         FPTR_LINK(g_xstuff, x11lib, XIconifyWindow);
119         FPTR_LINK(g_xstuff, x11lib, XMoveResizeWindow);
120         FPTR_LINK(g_xstuff, x11lib, XInternAtom);
121         FPTR_LINK(g_xstuff, x11lib, XSetWMHints);
122         FPTR_LINK(g_xstuff, x11lib, XSync);
123
124         //XInitThreads();
125
126         g_xstuff.display = display = g_xstuff.pXOpenDisplay(NULL);
127         if (display == NULL)
128         {
129                 fprintf(stderr, "cannot connect to X server %s, X handling disabled.\n",
130                                 g_xstuff.pXDisplayName(NULL));
131                 goto fail2;
132         }
133
134         visual = DefaultVisual(display, 0);
135         if (visual->class != TrueColor)
136                 fprintf(stderr, PFX "warning: non true color visual\n");
137
138         printf(PFX "X vendor: %s, rel: %d, display: %s, protocol ver: %d.%d\n", ServerVendor(display),
139                 VendorRelease(display), DisplayString(display), ProtocolVersion(display),
140                 ProtocolRevision(display));
141
142         screen = DefaultScreen(display);
143
144         display_width = DisplayWidth(display, screen);
145         display_height = DisplayHeight(display, screen);
146         printf(PFX "display is %dx%d\n", display_width, display_height);
147
148         g_xstuff.window = win = g_xstuff.pXCreateSimpleWindow(display,
149                 RootWindow(display, screen), 0, 0, display_width, display_height,
150                 0, BlackPixel(display, screen), BlackPixel(display, screen));
151
152         attributes.override_redirect = True;
153         attributes.cursor = transparent_cursor(&g_xstuff, display, win);
154         g_xstuff.pXChangeWindowAttributes(display, win, CWOverrideRedirect | CWCursor, &attributes);
155
156         g_xstuff.pXStoreName(display, win, window_title);
157         g_xstuff.pXSelectInput(display, win, ExposureMask | FocusChangeMask
158                 | KeyPressMask | KeyReleaseMask | ButtonPressMask
159                 | ButtonReleaseMask | PointerMotionMask | PropertyChangeMask);
160         g_xstuff.pXMapWindow(display, win);
161         g_xstuff.pXGrabKeyboard(display, win, False, GrabModeAsync, GrabModeAsync, CurrentTime);
162         g_xstuff.pXkbSetDetectableAutoRepeat(display, 1, NULL);
163         // XSetIOErrorHandler
164
165         // we don't know when event dispatch will be called, so sync now
166         g_xstuff.pXSync(display, False);
167
168         return 0;
169 fail2:
170         dlclose(x11lib);
171 fail:
172         g_xstuff.display = NULL;
173         fprintf(stderr, "x11 handling disabled.\n");
174         return -1;
175 }
176
177 static void x11h_update(int (*key_cb)(void *cb_arg, int kc, int is_pressed),
178                         int (*mouseb_cb)(void *cb_arg, int x, int y, int button, int is_pressed),
179                         int (*mousem_cb)(void *cb_arg, int x, int y),
180                         void *cb_arg)
181 {
182         XEvent evt;
183         int keysym;
184
185         while (g_xstuff.pXPending(g_xstuff.display))
186         {
187                 g_xstuff.pXNextEvent(g_xstuff.display, &evt);
188                 switch (evt.type)
189                 {
190                 case Expose:
191                         while (g_xstuff.pXCheckTypedEvent(g_xstuff.display, Expose, &evt))
192                                 ;
193                         break;
194
195                 case KeyPress:
196                         keysym = g_xstuff.pXLookupKeysym(&evt.xkey, 0);
197                         if (key_cb != NULL)
198                                 key_cb(cb_arg, keysym, 1);
199                         break;
200
201                 case KeyRelease:
202                         keysym = g_xstuff.pXLookupKeysym(&evt.xkey, 0);
203                         if (key_cb != NULL)
204                                 key_cb(cb_arg, keysym, 0);
205                         break;
206
207                 case ButtonPress:
208                         if (mouseb_cb != NULL)
209                                 mouseb_cb(cb_arg, evt.xbutton.x, evt.xbutton.y,
210                                           evt.xbutton.button, 1);
211                         break;
212
213                 case ButtonRelease:
214                         if (mouseb_cb != NULL)
215                                 mouseb_cb(cb_arg, evt.xbutton.x, evt.xbutton.y,
216                                           evt.xbutton.button, 0);
217                         break;
218
219                 case MotionNotify:
220                         if (mousem_cb != NULL)
221                                 mousem_cb(cb_arg, evt.xmotion.x, evt.xmotion.y);
222                         break;
223                 }
224         }
225 }
226
227 static void x11h_wait_vmstate(void)
228 {
229         Atom wm_state = g_xstuff.pXInternAtom(g_xstuff.display, "WM_STATE", False);
230         XEvent evt;
231         int i;
232
233         usleep(20000);
234
235         for (i = 0; i < 20; i++) {
236                 while (g_xstuff.pXPending(g_xstuff.display)) {
237                         g_xstuff.pXNextEvent(g_xstuff.display, &evt);
238                         // printf("w event %d\n", evt.type);
239                         if (evt.type == PropertyNotify && evt.xproperty.atom == wm_state)
240                                 return;
241                 }
242                 usleep(200000);
243         }
244
245         fprintf(stderr, PFX "timeout waiting for wm_state change\n");
246 }
247
248 static int x11h_minimize(void)
249 {
250         XSetWindowAttributes attributes;
251         Display *display = g_xstuff.display;
252         Window window = g_xstuff.window;
253         int screen = DefaultScreen(g_xstuff.display);
254         int display_width, display_height;
255         XWMHints wm_hints;
256         XEvent evt;
257
258         g_xstuff.pXWithdrawWindow(display, window, screen);
259
260         attributes.override_redirect = False;
261         g_xstuff.pXChangeWindowAttributes(display, window,
262                 CWOverrideRedirect, &attributes);
263
264         wm_hints.flags = StateHint;
265         wm_hints.initial_state = IconicState;
266         g_xstuff.pXSetWMHints(display, window, &wm_hints);
267
268         g_xstuff.pXMapWindow(display, window);
269
270         while (g_xstuff.pXNextEvent(display, &evt) == 0)
271         {
272                 // printf("m event %d\n", evt.type);
273                 switch (evt.type)
274                 {
275                         case FocusIn:
276                                 goto out;
277                         default:
278                                 break;
279                 }
280         }
281
282 out:
283         g_xstuff.pXWithdrawWindow(display, window, screen);
284
285         // must wait for some magic vmstate property change before setting override_redirect
286         x11h_wait_vmstate();
287
288         attributes.override_redirect = True;
289         g_xstuff.pXChangeWindowAttributes(display, window,
290                 CWOverrideRedirect, &attributes);
291
292         // fixup window after resize on override_redirect loss
293         display_width = DisplayWidth(display, screen);
294         display_height = DisplayHeight(display, screen);
295         g_xstuff.pXMoveResizeWindow(display, window, 0, 0, display_width, display_height);
296
297         g_xstuff.pXMapWindow(display, window);
298         g_xstuff.pXGrabKeyboard(display, window, False, GrabModeAsync, GrabModeAsync, CurrentTime);
299         g_xstuff.pXkbSetDetectableAutoRepeat(display, 1, NULL);
300
301         // we don't know when event dispatch will be called, so sync now
302         g_xstuff.pXSync(display, False);
303
304         return 0;
305 }
306
307 static struct termios g_kbd_termios_saved;
308 static int g_kbdfd = -1;
309
310 static int tty_init(void)
311 {
312         struct termios kbd_termios;
313         int mode;
314
315         g_kbdfd = open("/dev/tty", O_RDWR);
316         if (g_kbdfd == -1) {
317                 perror(PFX "open /dev/tty");
318                 return -1;
319         }
320
321         if (ioctl(g_kbdfd, KDGETMODE, &mode) == -1) {
322                 perror(PFX "(not hiding FB): KDGETMODE");
323                 goto fail;
324         }
325
326         if (tcgetattr(g_kbdfd, &kbd_termios) == -1) {
327                 perror(PFX "tcgetattr");
328                 goto fail;
329         }
330
331         g_kbd_termios_saved = kbd_termios;
332         kbd_termios.c_lflag &= ~(ICANON | ECHO); // | ISIG);
333         kbd_termios.c_iflag &= ~(ISTRIP | IGNCR | ICRNL | INLCR | IXOFF | IXON);
334         kbd_termios.c_cc[VMIN] = 0;
335         kbd_termios.c_cc[VTIME] = 0;
336
337         if (tcsetattr(g_kbdfd, TCSAFLUSH, &kbd_termios) == -1) {
338                 perror(PFX "tcsetattr");
339                 goto fail;
340         }
341
342         if (ioctl(g_kbdfd, KDSETMODE, KD_GRAPHICS) == -1) {
343                 perror(PFX "KDSETMODE KD_GRAPHICS");
344                 tcsetattr(g_kbdfd, TCSAFLUSH, &g_kbd_termios_saved);
345                 goto fail;
346         }
347
348         return 0;
349
350 fail:
351         close(g_kbdfd);
352         g_kbdfd = -1;
353         return -1;
354 }
355
356 static void tty_end(void)
357 {
358         if (g_kbdfd < 0)
359                 return;
360
361         if (ioctl(g_kbdfd, KDSETMODE, KD_TEXT) == -1)
362                 perror(PFX "KDSETMODE KD_TEXT");
363
364         if (tcsetattr(g_kbdfd, TCSAFLUSH, &g_kbd_termios_saved) == -1)
365                 perror(PFX "tcsetattr");
366
367         close(g_kbdfd);
368         g_kbdfd = -1;
369 }
370
371 int xenv_init(int *have_mouse_events, const char *window_title)
372 {
373         int have_mouse = 0;
374         int ret;
375
376         ret = x11h_init(window_title);
377         if (ret == 0) {
378                 have_mouse = 1;
379                 goto out;
380         }
381
382         ret = tty_init();
383         if (ret == 0)
384                 goto out;
385
386         fprintf(stderr, PFX "error: both x11h_init and tty_init failed\n");
387         ret = -1;
388 out:
389         if (have_mouse_events != NULL)
390                 *have_mouse_events = have_mouse;
391         return ret;
392 }
393
394 int xenv_update(int (*key_cb)(void *cb_arg, int kc, int is_pressed),
395                 int (*mouseb_cb)(void *cb_arg, int x, int y, int button, int is_pressed),
396                 int (*mousem_cb)(void *cb_arg, int x, int y),
397                 void *cb_arg)
398 {
399         if (g_xstuff.display) {
400                 x11h_update(key_cb, mouseb_cb, mousem_cb, cb_arg);
401                 return 0;
402         }
403
404         // TODO: read tty?
405         return -1;
406 }
407
408 /* blocking minimize until user maximizes again */
409 int xenv_minimize(void)
410 {
411         int ret;
412
413         if (g_xstuff.display) {
414                 xenv_update(NULL, NULL, NULL, NULL);
415                 ret = x11h_minimize();
416                 xenv_update(NULL, NULL, NULL, NULL);
417                 return ret;
418         }
419
420         return -1;
421 }
422
423 void xenv_finish(void)
424 {
425         // TODO: cleanup X?
426         tty_end();
427 }
428
429 #if 0
430 int main()
431 {
432         int i, r, d;
433
434         xenv_init("just a test");
435
436         for (i = 0; i < 5; i++) {
437                 while ((r = xenv_update(&d)) > 0)
438                         printf("%d %x %d\n", d, r, r);
439                 sleep(1);
440
441                 if (i == 1)
442                         xenv_minimize();
443                 printf("ll %d\n", i);
444         }
445
446         printf("xenv_finish..\n");
447         xenv_finish();
448
449         return 0;
450 }
451 #endif