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