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