f54e57d2c422bb1eb79b198f1b0e19dcf7e055e5
[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 #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,
158                 ExposureMask | FocusChangeMask | KeyPressMask | KeyReleaseMask | PropertyChangeMask);
159         g_xstuff.pXMapWindow(display, win);
160         g_xstuff.pXGrabKeyboard(display, win, False, GrabModeAsync, GrabModeAsync, CurrentTime);
161         g_xstuff.pXkbSetDetectableAutoRepeat(display, 1, NULL);
162         // XSetIOErrorHandler
163
164         // we don't know when event dispatch will be called, so sync now
165         g_xstuff.pXSync(display, False);
166
167         return 0;
168 fail2:
169         dlclose(x11lib);
170 fail:
171         g_xstuff.display = NULL;
172         fprintf(stderr, "x11 handling disabled.\n");
173         return -1;
174 }
175
176 static int x11h_update(int *is_down)
177 {
178         XEvent evt;
179
180         while (g_xstuff.pXPending(g_xstuff.display))
181         {
182                 g_xstuff.pXNextEvent(g_xstuff.display, &evt);
183                 switch (evt.type)
184                 {
185                         case Expose:
186                                 while (g_xstuff.pXCheckTypedEvent(g_xstuff.display, Expose, &evt))
187                                         ;
188                         default:
189                                 // printf("event %d\n", evt.type);
190                                 break;
191
192                         case KeyPress:
193                                 *is_down = 1;
194                                 return g_xstuff.pXLookupKeysym(&evt.xkey, 0);
195
196                         case KeyRelease:
197                                 *is_down = 0;
198                                 return g_xstuff.pXLookupKeysym(&evt.xkey, 0);
199                                 // printf("press %d\n", evt.xkey.keycode);
200                 }
201         }
202
203         return NoSymbol;
204 }
205
206 static void x11h_wait_vmstate(void)
207 {
208         Atom wm_state = g_xstuff.pXInternAtom(g_xstuff.display, "WM_STATE", False);
209         XEvent evt;
210         int i;
211
212         usleep(20000);
213
214         for (i = 0; i < 20; i++) {
215                 while (g_xstuff.pXPending(g_xstuff.display)) {
216                         g_xstuff.pXNextEvent(g_xstuff.display, &evt);
217                         // printf("w event %d\n", evt.type);
218                         if (evt.type == PropertyNotify && evt.xproperty.atom == wm_state)
219                                 return;
220                 }
221                 usleep(200000);
222         }
223
224         printf("timeout waiting for wm_state change\n");
225 }
226
227 static int x11h_minimize(void)
228 {
229         XSetWindowAttributes attributes;
230         Display *display = g_xstuff.display;
231         Window window = g_xstuff.window;
232         int screen = DefaultScreen(g_xstuff.display);
233         int display_width, display_height;
234         XWMHints wm_hints;
235         XEvent evt;
236
237         g_xstuff.pXWithdrawWindow(display, window, screen);
238
239         attributes.override_redirect = False;
240         g_xstuff.pXChangeWindowAttributes(display, window,
241                 CWOverrideRedirect, &attributes);
242
243         wm_hints.flags = StateHint;
244         wm_hints.initial_state = IconicState;
245         g_xstuff.pXSetWMHints(display, window, &wm_hints);
246
247         g_xstuff.pXMapWindow(display, window);
248
249         while (g_xstuff.pXNextEvent(display, &evt) == 0)
250         {
251                 // printf("m event %d\n", evt.type);
252                 switch (evt.type)
253                 {
254                         case FocusIn:
255                                 goto out;
256                         default:
257                                 break;
258                 }
259         }
260
261 out:
262         g_xstuff.pXWithdrawWindow(display, window, screen);
263
264         // must wait for some magic vmstate property change before setting override_redirect
265         x11h_wait_vmstate();
266
267         attributes.override_redirect = True;
268         g_xstuff.pXChangeWindowAttributes(display, window,
269                 CWOverrideRedirect, &attributes);
270
271         // fixup window after resize on override_redirect loss
272         display_width = DisplayWidth(display, screen);
273         display_height = DisplayHeight(display, screen);
274         g_xstuff.pXMoveResizeWindow(display, window, 0, 0, display_width, display_height);
275
276         g_xstuff.pXMapWindow(display, window);
277         g_xstuff.pXGrabKeyboard(display, window, False, GrabModeAsync, GrabModeAsync, CurrentTime);
278         g_xstuff.pXkbSetDetectableAutoRepeat(display, 1, NULL);
279
280         // we don't know when event dispatch will be called, so sync now
281         g_xstuff.pXSync(display, False);
282
283         return 0;
284 }
285
286 static struct termios g_kbd_termios_saved;
287 static int g_kbdfd = -1;
288
289 static int tty_init(void)
290 {
291         struct termios kbd_termios;
292         int mode;
293
294         g_kbdfd = open("/dev/tty", O_RDWR);
295         if (g_kbdfd == -1) {
296                 perror(PFX "open /dev/tty");
297                 return -1;
298         }
299
300         if (ioctl(g_kbdfd, KDGETMODE, &mode) == -1) {
301                 perror(PFX "(not hiding FB): KDGETMODE");
302                 goto fail;
303         }
304
305         if (tcgetattr(g_kbdfd, &kbd_termios) == -1) {
306                 perror(PFX "tcgetattr");
307                 goto fail;
308         }
309
310         g_kbd_termios_saved = kbd_termios;
311         kbd_termios.c_lflag &= ~(ICANON | ECHO); // | ISIG);
312         kbd_termios.c_iflag &= ~(ISTRIP | IGNCR | ICRNL | INLCR | IXOFF | IXON);
313         kbd_termios.c_cc[VMIN] = 0;
314         kbd_termios.c_cc[VTIME] = 0;
315
316         if (tcsetattr(g_kbdfd, TCSAFLUSH, &kbd_termios) == -1) {
317                 perror(PFX "tcsetattr");
318                 goto fail;
319         }
320
321         if (ioctl(g_kbdfd, KDSETMODE, KD_GRAPHICS) == -1) {
322                 perror(PFX "KDSETMODE KD_GRAPHICS");
323                 tcsetattr(g_kbdfd, TCSAFLUSH, &g_kbd_termios_saved);
324                 goto fail;
325         }
326
327         return 0;
328
329 fail:
330         close(g_kbdfd);
331         g_kbdfd = -1;
332         return -1;
333 }
334
335 static void tty_end(void)
336 {
337         if (g_kbdfd < 0)
338                 return;
339
340         if (ioctl(g_kbdfd, KDSETMODE, KD_TEXT) == -1)
341                 perror(PFX "KDSETMODE KD_TEXT");
342
343         if (tcsetattr(g_kbdfd, TCSAFLUSH, &g_kbd_termios_saved) == -1)
344                 perror(PFX "tcsetattr");
345
346         close(g_kbdfd);
347         g_kbdfd = -1;
348 }
349
350 int xenv_init(const char *window_title)
351 {
352         int ret;
353
354         ret = x11h_init(window_title);
355         if (ret == 0)
356                 return 0;
357
358         ret = tty_init();
359         if (ret == 0)
360                 return 0;
361
362         fprintf(stderr, PFX "error: both x11h_init and tty_init failed\n");
363         return -1;
364 }
365
366 int xenv_update(int *is_down)
367 {
368         if (g_xstuff.display)
369                 return x11h_update(is_down);
370
371         // TODO: read tty?
372         return -1;
373 }
374
375 /* blocking minimize until user maximazes again */
376 int xenv_minimize(void)
377 {
378         int ret, dummy;
379
380         if (g_xstuff.display) {
381                 xenv_update(&dummy);
382                 ret = x11h_minimize();
383                 xenv_update(&dummy);
384                 return ret;
385         }
386
387         return -1;
388 }
389
390 void xenv_finish(void)
391 {
392         // TODO: cleanup X?
393         tty_end();
394 }
395
396 #if 0
397 int main()
398 {
399         int i, r, d;
400
401         xenv_init("just a test");
402
403         for (i = 0; i < 5; i++) {
404                 while ((r = xenv_update(&d)) > 0)
405                         printf("%d %x %d\n", d, r, r);
406                 sleep(1);
407
408                 if (i == 1)
409                         xenv_minimize();
410                 printf("ll %d\n", i);
411         }
412
413         printf("xenv_finish..\n");
414         xenv_finish();
415
416         return 0;
417 }
418 #endif