master uint/maus / source / maus_x11.c
  1#include <limits.h>
  2#include <stdlib.h>
  3#include <string.h>
  4#include <sys/shm.h>
  5#include <sys/ipc.h>
  6
  7#include <X11/Xlib.h>
  8#include <X11/Xutil.h>
  9#include <X11/keysym.h>
 10#include <X11/cursorfont.h>
 11#include <X11/extensions/XShm.h>
 12#include <X11/extensions/XInput2.h>
 13
 14#include "maus.h"
 15#include "maus_input.h"
 16#include "maus_x11.h"
 17#include "utils.h"
 18
 19typedef struct {
 20	KeySym  x11;
 21	MausKey maus;
 22} KeyMapEntry;
 23
 24static ule_i8 fb_create(Maus* mw);
 25static ule_i8 fb_create_shm(Maus* mw);
 26/* TODO static ule_i8 fb_create_ximage(Maus* mw); */
 27static void fb_destroy(Maus* mw);
 28static ule_i8 handle_event(XEvent* xev, MausEvent* ev, Maus* mw);
 29static MausKey keysym_to_mauskey(KeySym sym);
 30static MausMouseButton mouse_button_to_maus(int btn);
 31static int xerr(Display* dpy, XErrorEvent* ev);
 32static ule_i8 xi2_handle_event(Maus* mw, XGenericEventCookie* cookie, MausEvent* ev);
 33static void xi2_init(Maus* mw);
 34
 35static const KeyMapEntry keymap[] = {
 36	{ XK_BackSpace,    MAUS_KEY_BACKSPACE }, { XK_Tab,              MAUS_KEY_TAB },
 37	{ XK_Return,       MAUS_KEY_ENTER },     { XK_Escape,           MAUS_KEY_ESCAPE },
 38	{ XK_apostrophe,   MAUS_KEY_APOSTROPHE },{ XK_comma,            MAUS_KEY_COMMA },
 39	{ XK_space,        MAUS_KEY_SPACE },     { XK_minus,            MAUS_KEY_MINUS },
 40	{ XK_period,       MAUS_KEY_PERIOD },    { XK_slash,            MAUS_KEY_SLASH },
 41	{ XK_semicolon,    MAUS_KEY_SEMICOLON }, { XK_equal,            MAUS_KEY_EQUAL },
 42	{ XK_Delete,       MAUS_KEY_DELETE },    { XK_Left,             MAUS_KEY_LEFT },
 43	{ XK_Right,        MAUS_KEY_RIGHT },     { XK_Up,               MAUS_KEY_UP },
 44	{ XK_Down,         MAUS_KEY_DOWN },      { XK_Home,             MAUS_KEY_HOME },
 45	{ XK_End,          MAUS_KEY_END },       { XK_Page_Up,          MAUS_KEY_PAGE_UP },
 46	{ XK_Page_Down,    MAUS_KEY_PAGE_DOWN }, { XK_Insert,           MAUS_KEY_INSERT },
 47	{ XK_Shift_L,      MAUS_KEY_SHIFT_L },   { XK_Shift_R,          MAUS_KEY_SHIFT_R },
 48	{ XK_Control_L,    MAUS_KEY_CONTROL_L }, { XK_Control_R,        MAUS_KEY_CONTROL_R },
 49	{ XK_Alt_L,        MAUS_KEY_ALT_L },     { XK_Alt_R,            MAUS_KEY_ALT_R },
 50	{ XK_Super_L,      MAUS_KEY_SUPER_L },   { XK_Super_R,          MAUS_KEY_SUPER_R },
 51	{ XK_Caps_Lock,    MAUS_KEY_CAPS_LOCK }, { XK_Num_Lock,         MAUS_KEY_NUM_LOCK },
 52	{ XK_Pause,        MAUS_KEY_PAUSE },     { XK_Menu,             MAUS_KEY_MENU },
 53	{ XK_KP_Add,       MAUS_KEY_KP_ADD },    { XK_KP_Enter,         MAUS_KEY_KP_ENTER },
 54	{ XK_bracketright, MAUS_KEY_RIGHT_BRACKET }, { XK_grave,        MAUS_KEY_GRAVE },
 55	{ XK_Scroll_Lock,  MAUS_KEY_SCROLL_LOCK },   { XK_Print,        MAUS_KEY_PRINT_SCREEN },
 56	{ XK_KP_Decimal,   MAUS_KEY_KP_DECIMAL },    { XK_KP_Divide,    MAUS_KEY_KP_DIVIDE },
 57	{ XK_KP_Multiply,  MAUS_KEY_KP_MULTIPLY },   { XK_KP_Subtract,  MAUS_KEY_KP_SUBTRACT },
 58	{ XK_bracketleft,  MAUS_KEY_LEFT_BRACKET },  { XK_backslash,    MAUS_KEY_BACKSLASH },
 59	{ XK_KP_Equal,     MAUS_KEY_KP_EQUAL },
 60};
 61
 62static int xerrored;
 63
 64static ule_i8 fb_create(Maus* mw)
 65{
 66	MausBackend* be = &mw->backend;
 67
 68	be->image = NULL;
 69	be->shmat = 0;
 70	be->shm.shmid = -1;
 71	be->shm.shmaddr = NULL;
 72	mw->fb = NULL;
 73	mw->stride = 0;
 74
 75	return fb_create_shm(mw);
 76}
 77
 78/* allocate and store a new shm for the framebuffer */
 79static ule_i8 fb_create_shm(Maus* mw)
 80{
 81	MausBackend* be = &mw->backend;
 82	int scr = DefaultScreen(be->display);
 83	int depth = DefaultDepth(be->display, scr);
 84	Visual* vis = DefaultVisual(be->display, scr);
 85
 86	size_t size;
 87	int (*old_xerr)(Display*, XErrorEvent*);
 88
 89	if (!XShmQueryExtension(be->display))
 90		return 0;
 91
 92	if (!vis) {
 93		maus_log(stderr, "could not get default visual");
 94		return 0;
 95	}
 96
 97	be->image = XShmCreateImage(
 98		be->display, vis, depth, ZPixmap, NULL,
 99		&be->shm, mw->width, mw->height
100	);
101
102	if (!be->image) {
103		maus_log(stderr, "could not create XImage (shm)");
104		return 0;
105	}
106
107	if (be->image->bits_per_pixel != 32) {
108		XDestroyImage(be->image);
109		be->image = NULL;
110		maus_log(stderr, "XImage not 32bpp");
111		return 0;
112	}
113
114	size = be->image->bytes_per_line * be->image->height;
115#ifdef __OpenBSD__
116	be->shm.shmid = shmget(IPC_PRIVATE, size, IPC_CREAT | 0666);
117#else
118	be->shm.shmid = shmget(IPC_PRIVATE, size, IPC_CREAT | 0600);
119#endif
120
121	if (be->shm.shmid < 0) {
122		XDestroyImage(be->image);
123		be->image = NULL;
124		maus_log(stderr, "shmget() failed");
125		return 0;
126	}
127
128	be->shm.shmaddr = shmat(be->shm.shmid, NULL, 0);
129	if (be->shm.shmaddr == (char*)-1) {
130		shmctl(be->shm.shmid, IPC_RMID, NULL);
131		XDestroyImage(be->image);
132		be->image = NULL;
133		maus_log(stderr, "shmat() failed");
134		return 0;
135	}
136
137	be->shm.readOnly = False;
138	be->image->data = be->shm.shmaddr;
139
140	xerrored = 0;
141	old_xerr = XSetErrorHandler(xerr);
142
143	if (!XShmAttach(be->display, &be->shm))
144		xerrored = 1;
145
146	XSync(be->display, False);
147	XSetErrorHandler(old_xerr);
148	if (xerrored) {
149		shmdt(be->shm.shmaddr);
150		shmctl(be->shm.shmid, IPC_RMID, NULL);
151		XDestroyImage(be->image);
152
153		be->image = NULL;
154		mw->fb = NULL;
155		be->shm.shmaddr = NULL;
156		be->shm.shmid = -1;
157		maus_log(stderr, "XShmAttach failed");
158		return 0;
159	}
160
161	shmctl(be->shm.shmid, IPC_RMID, NULL);
162
163	be->shmat = 1;
164	mw->fb = (ule_u32*) be->image->data;
165	mw->stride = be->image->bytes_per_line / sizeof(ule_u32);
166
167	return 1;
168}
169
170static void fb_destroy(Maus* mw)
171{
172	MausBackend* be = &mw->backend;
173	Display* dpy = be->display;
174
175	if (be->shmat) {
176		XShmDetach(dpy, &be->shm);
177		XSync(dpy, False);
178		be->shmat = 0;
179	}
180
181	if (be->image) {
182		XDestroyImage(be->image);
183		be->image = NULL;
184	}
185
186	if (be->shm.shmaddr && be->shm.shmaddr != (char*) -1) {
187		shmdt(be->shm.shmaddr);
188		be->shm.shmaddr = NULL;
189	}
190
191	/* normally already removed after attach but
192	   can check anyways */
193	if (be->shm.shmid >= 0) {
194		shmctl(be->shm.shmid, IPC_RMID, NULL);
195		be->shm.shmid = -1;
196	}
197
198	mw->fb = NULL;
199	mw->stride = 0;
200}
201
202static ule_i8 handle_event(XEvent* xev, MausEvent* ev, Maus* mw)
203{
204	MausBackend* be = &mw->backend;
205	ule_u32 code = 0;
206	unsigned int mb;
207	MausMouseButton mbtype;
208
209	char buf[8] = {0};
210	int len;
211
212	KeySym sym;
213
214	XSelectionRequestEvent* req;
215	XEvent rsp; /* response */
216	Atom utf8_string;
217	Atom clipboard;
218
219	int cx;
220	int cy;
221	ule_i8 handled;
222
223	if (xev->type == GenericEvent && XGetEventData(be->display, &xev->xcookie)) {
224		handled = xi2_handle_event(mw, &xev->xcookie, ev);
225		XFreeEventData(be->display, &xev->xcookie);
226		return handled;
227	}
228
229	switch (xev->type) {
230		case Expose:
231			if (xev->xexpose.count != 0)
232				return 0;
233			ev->type = MAUS_EV_REDRAW;
234			return 1;
235
236		case ClientMessage:
237			if ((Atom)xev->xclient.data.l[0] == be->atoms[MAUS_ATOM_WM_DELETE_WINDOW]) {
238				ev->type = MAUS_EV_CLOSE;
239				return 1;
240			}
241			break;
242
243		case MappingNotify:
244			XRefreshKeyboardMapping(&xev->xmapping);
245			return 1;
246			break;
247
248		case KeyPress: {
249			ev->type = MAUS_EV_KEY;
250			code = xev->xkey.keycode;
251			ev->key.code = code;
252			ev->key.pressed = 1;
253			if (code < MAUS_KEYCODE_LAST)
254			        mw->key_codes[code] = 1;
255
256			/* grab the raw, base sym */
257			sym = XLookupKeysym(&xev->xkey, 0);
258			ev->key.key = keysym_to_mauskey(sym);
259			if (ev->key.key != MAUS_KEY_NONE)
260			        mw->key_syms[ev->key.key] = 1;
261
262			len = XLookupString(&xev->xkey, buf, sizeof(buf), NULL, NULL);
263			if (len > 0)
264				ev->key.text = (buf[0] == '\r') ? '\n' : buf[0];
265			else
266				ev->key.text = 0;
267
268			return 1;
269		}
270
271		case KeyRelease: {
272			ev->type = MAUS_EV_KEY;
273			code = xev->xkey.keycode;
274			ev->key.code = code;
275			ev->key.pressed = 0;
276			ev->key.text = 0;
277
278			if (code < MAUS_KEYCODE_LAST)
279			        mw->key_codes[code] = 0;
280
281			/* unset base sym set on `KeyPress` */
282			sym = XLookupKeysym(&xev->xkey, 0);
283			ev->key.key = keysym_to_mauskey(sym);
284			if (ev->key.key != MAUS_KEY_NONE)
285			        mw->key_syms[ev->key.key] = 0;
286
287			return 1;
288		}
289
290		case ButtonPress:
291			ev->type = MAUS_EV_MOUSE_BUTTON;
292			mb = xev->xbutton.button;
293			mbtype = mouse_button_to_maus(mb);
294			ev->mouse.button.button = mbtype;
295			ev->mouse.button.pressed = 1;
296			mw->mouse_buttons[mbtype] = 1;
297			return 1;
298
299		case ButtonRelease:
300			ev->type = MAUS_EV_MOUSE_BUTTON;
301			mb = xev->xbutton.button;
302			mbtype = mouse_button_to_maus(mb);
303			ev->mouse.button.button = mbtype;
304			ev->mouse.button.pressed = 0;
305			mw->mouse_buttons[mbtype] = 0;
306			return 1;
307
308		case MotionNotify:
309			if (be->cur_rel) {
310				if (be->xi2)
311					return 0;
312
313				cx = mw->width / 2;
314				cy = mw->height / 2;
315
316				if (be->ignore_warp && xev->xmotion.x == cx && xev->xmotion.y == cy) {
317					be->ignore_warp = 0;
318					return 0;
319				}
320
321				ev->type = MAUS_EV_MOUSE_MOTION;
322				ev->mouse.motion.x = xev->xmotion.x - cx;
323				ev->mouse.motion.y = xev->xmotion.y - cy;
324				ev->mouse.motion.dx = ev->mouse.motion.x;
325				ev->mouse.motion.dy = ev->mouse.motion.y;
326
327				if (ev->mouse.motion.x != 0 || ev->mouse.motion.y != 0) {
328					be->ignore_warp = 1;
329					XWarpPointer(
330						be->display, None, be->win,
331						0, 0, 0, 0, cx, cy
332					);
333					XFlush(be->display);
334				}
335
336				return ev->mouse.motion.x != 0 || ev->mouse.motion.y != 0;
337			}
338			ev->type = MAUS_EV_MOUSE_MOTION;
339			ev->mouse.motion.x = xev->xmotion.x;
340			ev->mouse.motion.y = xev->xmotion.y;
341			ev->mouse.motion.dx = be->mouse_pos_set ? xev->xmotion.x - be->mouse_x : 0;
342			ev->mouse.motion.dy = be->mouse_pos_set ? xev->xmotion.y - be->mouse_y : 0;
343			be->mouse_x = xev->xmotion.x;
344			be->mouse_y = xev->xmotion.y;
345			be->mouse_pos_set = 1;
346			return 1;
347
348		case ConfigureNotify:
349			ev->type = MAUS_EV_RESIZE;
350			ev->resize.width = xev->xconfigure.width;
351			ev->resize.height = xev->xconfigure.height;
352			return 1;
353
354		case SelectionRequest: {
355			req = &xev->xselectionrequest;
356
357			utf8_string = XInternAtom(be->display, "UTF8_STRING", False);
358			clipboard = XInternAtom(be->display, "CLIPBOARD", False);
359
360			rsp.type = SelectionNotify;
361			rsp.xselection.display = req->display;
362			rsp.xselection.requestor = req->requestor;
363			rsp.xselection.selection = req->selection;
364			rsp.xselection.target = req->target;
365			rsp.xselection.property = None; 
366			rsp.xselection.time = req->time;
367
368			/* assign clipboard text to requesting programs
369			   window property */
370			if (req->selection == clipboard &&
371			    req->target == utf8_string &&
372			    mw->clipboard) {
373			        XChangeProperty(
374					req->display, req->requestor,
375					req->property, utf8_string, 8,
376					PropModeReplace, (unsigned char*)mw->clipboard, 
377					strlen(mw->clipboard)
378			 	);
379				rsp.xselection.property = req->property; 
380			}
381
382			/* send reply back to the prog requesting paste */
383			XSendEvent(req->display, req->requestor, False, 0, &rsp);
384			XFlush(be->display);
385
386			return 0; /* skip maus polling it */
387		}
388	}
389
390	return 0;
391}
392
393static MausKey keysym_to_mauskey(KeySym sym)
394{
395	size_t i;
396
397	if (sym >= 0x20 && sym <= 0x7E)
398		return (MausKey)sym;
399
400	if (sym >= XK_F1 && sym <= XK_F12)
401		return MAUS_KEY_F1 + (sym - XK_F1);
402	if (sym >= XK_KP_0 && sym <= XK_KP_9)
403		return MAUS_KEY_KP_0 + (sym - XK_KP_0);
404
405	for (i = 0; i < sizeof(keymap)/ sizeof(keymap[0]); i++) {
406		if (keymap[i].x11 == sym)
407			return keymap[i].maus;
408	}
409
410	return MAUS_KEY_NONE;
411}
412
413static MausMouseButton mouse_button_to_maus(int btn)
414{
415	switch (btn) {
416		case Button1: return MAUS_MOUSE_BUTTON_LEFT;
417		case Button2: return MAUS_MOUSE_BUTTON_MIDDLE;
418		case Button3: return MAUS_MOUSE_BUTTON_RIGHT;
419		case Button4: return MAUS_MOUSE_BUTTON_SCROLL_UP;
420		case Button5: return MAUS_MOUSE_BUTTON_SCROLL_DOWN;
421		default:      return MAUS_MOUSE_BUTTON_NONE;
422	}
423}
424
425static int xerr(Display* dpy, XErrorEvent* ev)
426{
427	char buf[256];
428
429	XGetErrorText(dpy, ev->error_code, buf, sizeof(buf));
430	maus_log(stderr, "X11 error: %s (request=%u, minor=%u, resource=%lu)", buf, ev->request_code, ev->minor_code, ev->resourceid);
431	xerrored = 1;
432	return 0;
433}
434
435static ule_i8 xi2_handle_event(Maus* mw, XGenericEventCookie* cookie, MausEvent* ev)
436{
437	MausBackend* be = &mw->backend;
438	XIRawEvent* raw;
439	double* values;
440	double dx;
441	double dy;
442	int valuator;
443	int value;
444
445	if (!be->cur_rel || !be->xi2 || cookie->extension != be->xi2_opcode)
446		return 0;
447	if (cookie->evtype != XI_RawMotion)
448		return 0;
449
450	raw = cookie->data;
451	values = raw->raw_values;
452	dx = 0.0;
453	dy = 0.0;
454	value = 0;
455
456	for (valuator = 0; valuator < raw->valuators.mask_len * 8; valuator++) {
457		if (!XIMaskIsSet(raw->valuators.mask, valuator))
458			continue;
459		if (valuator == 0)
460			dx = values[value];
461		else if (valuator == 1)
462			dy = values[value];
463		value++;
464	}
465
466	ev->type = MAUS_EV_MOUSE_MOTION;
467	ev->mouse.motion.x = (ule_i32)dx;
468	ev->mouse.motion.y = (ule_i32)dy;
469	ev->mouse.motion.dx = ev->mouse.motion.x;
470	ev->mouse.motion.dy = ev->mouse.motion.y;
471	return ev->mouse.motion.dx != 0 || ev->mouse.motion.dy != 0;
472}
473
474static void xi2_init(Maus* mw)
475{
476	MausBackend* be = &mw->backend;
477	int event;
478	int error;
479	int major;
480	int minor;
481	unsigned char mask[(XI_LASTEVENT + 7) / 8];
482	XIEventMask evmask;
483
484	be->xi2 = 0;
485	be->xi2_opcode = 0;
486
487	if (!XQueryExtension(be->display, "XInputExtension", &be->xi2_opcode, &event, &error))
488		return;
489
490	major = 2;
491	minor = 0;
492	if (XIQueryVersion(be->display, &major, &minor) != Success)
493		return;
494
495	memset(mask, 0, sizeof(mask));
496	XISetMask(mask, XI_RawMotion);
497
498	evmask.deviceid = XIAllMasterDevices;
499	evmask.mask_len = sizeof(mask);
500	evmask.mask = mask;
501	XISelectEvents(be->display, be->root, &evmask, 1);
502	XFlush(be->display);
503
504	be->xi2 = 1;
505}
506
507void maus_clipboard_set_text(Maus* mw, const char* text)
508{
509	MausBackend* be = &mw->backend;
510	Atom clipboard;
511
512	if (!be->display || be->win == None)
513		return;
514
515	/* clear old entry */
516	if (mw->clipboard) {
517		free(mw->clipboard);
518		mw->clipboard = NULL;
519	}
520
521	if (text)
522		mw->clipboard = maus_strdup(text);
523
524	clipboard = XInternAtom(be->display, "CLIPBOARD", False);
525	XSetSelectionOwner(be->display, clipboard, be->win, CurrentTime);
526	XFlush(be->display);
527}
528
529char* maus_clipboard_get_text(Maus* mw)
530{
531	MausBackend* be = &mw->backend;
532	Atom clipboard = XInternAtom(be->display, "CLIPBOARD", False);
533	Atom utf8_string = XInternAtom(be->display, "UTF8_STRING", False);
534	Atom maus_clipboard_prop = XInternAtom(be->display, "MAUS_CLIPBOARD_PROP", False);
535
536	XEvent xev;
537	ule_i8 ok = 0;
538
539	int timeout;
540
541	Atom actual_type;
542	int actual_fmt;
543	unsigned long nitems;
544	unsigned long bytes_after;
545	unsigned char* prop_data = NULL;
546
547	char* res = NULL;
548
549	/* fetch clipboard data to `maus_clipboard_prop`
550	   window property */
551	XConvertSelection(
552		be->display, clipboard, utf8_string,
553		maus_clipboard_prop, be->win, CurrentTime
554	);
555	XFlush(be->display);
556
557	/* check for selection response */
558	for (timeout = 0; timeout < 10000; timeout++) { 
559		if (XCheckTypedWindowEvent(be->display, be->win, SelectionNotify, &xev)) {
560			if (xev.xselection.property != None)
561				ok = 1;
562			break;
563		}
564	}
565	if (!ok)
566		return NULL;
567
568	/* read string off property */
569	XGetWindowProperty(
570		be->display, be->win, maus_clipboard_prop, 0,LONG_MAX,
571		True, utf8_string, &actual_type, &actual_fmt, &nitems,
572		&bytes_after, &prop_data
573	);
574
575	if (prop_data) {
576		res = maus_strdup((char*)prop_data);
577		XFree(prop_data);
578	}
579
580	return res;
581}
582
583void maus_close(Maus* mw)
584{
585	MausBackend* be = &mw->backend;
586
587	fb_destroy(mw);
588	if (mw->bfb) {
589		free(mw->bfb);
590		mw->bfb = NULL;
591	}
592	if (mw->clipboard) {
593		free(mw->clipboard);
594		mw->clipboard = NULL;
595	}
596	if (be->win != None) {
597		(void) maus_close_window(mw);
598	}
599	if (be->display)
600		XCloseDisplay(be->display);
601}
602
603ule_i8 maus_close_window(Maus* mw)
604{
605	MausBackend* be = &mw->backend;
606
607	if (be->gc) {
608		XFreeGC(be->display, be->gc);
609		be->gc = 0;
610	}
611
612	if (be->win != None) {
613		XUnmapWindow(be->display, be->win);
614		XDestroyWindow(be->display, be->win);
615	}
616
617	return 1;
618}
619
620ule_i8 maus_create_window(Maus* mw)
621{
622	MausBackend* be = &mw->backend;
623	Display* dpy = be->display;
624	Window* win = &be->win;
625
626	*win = XCreateSimpleWindow(
627		dpy, be->root,
628		mw->x, mw->y,
629		mw->width, mw->height,
630		0u, 0u, 0u
631	);
632
633	if (*win == None)
634		return 0;
635
636	XSelectInput(dpy, *win,
637		ExposureMask | KeyPressMask | KeyReleaseMask | ButtonPressMask |
638		ButtonReleaseMask | PointerMotionMask | StructureNotifyMask
639	);
640
641	/* no black flashing when resizing */
642	XSetWindowBackgroundPixmap(dpy, *win, None);
643
644	/* atoms */
645	be->atoms[MAUS_ATOM_WM_DELETE_WINDOW] = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
646
647	XSetWMProtocols(dpy, *win, &be->atoms[MAUS_ATOM_WM_DELETE_WINDOW], 1);
648	XStoreName(dpy, *win, mw->title);
649
650	XMapWindow(dpy, *win);
651	XFlush(dpy);
652
653	be->gc = XCreateGC(dpy, *win, 0, NULL);
654	if (!be->gc) {
655		maus_log(stderr, "failed to create window GC");
656		maus_close_window(mw);
657		return 0;
658	}
659
660	return 1;
661}
662
663void maus_clear(Maus* mw, MausColor col)
664{
665	ule_u32 col_up = MAUS_UNPACK_COL(col);
666	ule_u32 pxs = mw->height * mw->width;
667	ule_u32 i;
668	for (i = 0; i < pxs; i++)
669		mw->bfb[i] = col_up;
670}
671
672Maus* maus_init(const char* title, int x, int y, int width, int height)
673{
674	Display* d = XOpenDisplay(NULL);
675	Maus* mw = calloc(1, sizeof(Maus));
676	MausBackend* be = &mw->backend;
677
678	if (MAUS_WARN_BACKEND_AUTO_SEL)
679		maus_log(stderr, "backend auto-selected: X11");
680
681	if (!d) {
682		maus_die("failed to open X11 display");
683		return NULL;
684	}
685
686	be->display = d;
687	be->root = DefaultRootWindow(d);
688	be->win = None;
689	be->cur_rel = 0;
690	be->ignore_warp = 0;
691	be->mouse_x = 0;
692	be->mouse_y = 0;
693	be->mouse_pos_set = 0;
694	xi2_init(mw);
695
696	mw->frame_time_last = maus_get_time_ns();
697	mw->title = title;
698	mw->width = width;
699	mw->height = height;
700	mw->x = x;
701	mw->y = y;
702	mw->fb = NULL;
703
704	be->gc = 0;
705	be->image = NULL;
706	be->shmat = 0;
707	be->shm.shmid = -1;
708	be->shm.shmaddr = NULL;
709	if (!fb_create(mw)) {
710		maus_log(stderr, "failed to create framebuffer");
711		maus_close(mw);
712		free(mw);
713		return NULL;
714	}
715	mw->bfb = calloc(mw->stride * mw->height, sizeof(ule_u32));
716	if (!mw->bfb) {
717		maus_log(stderr, "failed to allocate back buffer");
718		maus_close(mw);
719		free(mw);
720		return NULL;
721	}
722
723	return mw;
724}
725
726ule_i8 maus_event_poll(Maus* mw, MausEvent* ev)
727{
728	MausBackend* be = &mw->backend;
729	XEvent xev;
730
731	while (XPending(be->display)) {
732		XNextEvent(be->display, &xev);
733		ev->type = MAUS_EV_NONE;
734		if (handle_event(&xev, ev, mw))
735			return 1;
736
737	}
738
739	return 0;
740}
741
742void maus_event_wait(Maus* mw, MausEvent* ev)
743{
744	MausBackend* be = &mw->backend;
745	XEvent xev;
746
747	for (;;) {
748		XNextEvent(be->display, &xev);
749		ev->type = MAUS_EV_NONE;
750		if (handle_event(&xev, ev, mw))
751			return;
752	}
753}
754
755void maus_present(Maus* mw)
756{
757	MausBackend* be = &mw->backend;
758	ule_u32 bytes;
759
760	if (!be->image || be->win == None)
761		return;
762
763	bytes = mw->stride * mw->height * sizeof(ule_u32);
764	memcpy(mw->fb, mw->bfb, bytes);
765
766	XShmPutImage(
767		be->display, be->win, be->gc, be->image,
768		0, 0, 0, 0, mw->width, mw->height, False
769	);
770
771	XFlush(be->display);
772}
773
774ule_i8 maus_resize(Maus* mw, ule_u32 width, ule_u32 height)
775{
776	MausBackend* be = &mw->backend;
777
778	if (width == 0 || height == 0 ||
779	    (mw->width == width && mw->height == height))
780		return 0;
781
782	fb_destroy(mw);
783	if (mw->bfb) {
784		free(mw->bfb);
785		mw->bfb = NULL;
786	}
787
788	mw->width = width;
789	mw->height = height;
790
791	XSync(be->display, False);
792	XFlush(be->display);
793
794	if (!fb_create(mw))
795		return 0;
796
797	mw->bfb = calloc(mw->stride * mw->height, sizeof(ule_u32));
798	if (!mw->bfb)
799		return 0;
800
801	return 1;
802}
803
804void maus_cur_set_mode(Maus* mw, MausCursorState state)
805{
806	MausBackend* be = &mw->backend;
807	Display* dpy = be->display;
808	Window win = be->win;
809
810	Mask mask;
811	int grab;
812
813	if (!dpy) {
814		maus_log(stderr, "display is NULL");
815		return;
816	}
817	if (win == None) {
818		maus_log(stderr, "win is None");
819		return;
820	}
821
822	/* show cursor */
823	if (state == MAUS_CURSOR_STATE_VISIBLE) {
824		XUndefineCursor(dpy, win);
825		XFlush(dpy);
826		return;
827	}
828
829	/* hide cursor */
830	if (state == MAUS_CURSOR_STATE_HIDDEN) {
831		Pixmap blank_pm = XCreatePixmap(dpy, win, 1, 1, 1);
832		XColor black = {0, 0, 0, 0, 0, 0};
833		Cursor blank_cur = XCreatePixmapCursor(
834			dpy, blank_pm, blank_pm, &black, &black, 0, 0
835		);
836		XFreePixmap(dpy, blank_pm);
837
838		XDefineCursor(dpy, win, blank_cur);
839		XFreeCursor(dpy, blank_cur);
840		XFlush(dpy);
841		return;
842	}
843
844	/* lock cursor */
845	if (state == MAUS_CURSOR_STATE_LOCKED) {
846		mask = ButtonPressMask | ButtonReleaseMask | PointerMotionMask;
847		grab = XGrabPointer(
848			dpy, win, True, mask, GrabModeAsync,
849			GrabModeAsync, win, None, CurrentTime
850		);
851
852		if (grab != GrabSuccess) {
853			maus_log(stderr, "failed to grab mouse pointer");
854			return;
855		}
856
857		/* TODO keep position just snap to window dimensions */
858		XWarpPointer(dpy, None, win, 0, 0, 0, 0, mw->width/2, mw->height/2);
859		XFlush(dpy);
860		return;
861	}
862
863	/* unlock cursor */
864	if (state == MAUS_CURSOR_STATE_FREE) {
865		XUngrabPointer(dpy, CurrentTime);
866		XFlush(dpy);
867		return;
868	}
869
870	/* relative cursor */
871	if (state == MAUS_CURSOR_STATE_RELATIVE) {
872		mask = ButtonPressMask | ButtonReleaseMask | PointerMotionMask;
873		grab = XGrabPointer(
874			dpy, win, True, mask, GrabModeAsync,
875			GrabModeAsync, win, None, CurrentTime
876		);
877
878		if (grab != GrabSuccess) {
879			maus_log(stderr, "failed to grab mouse pointer");
880			return;
881		}
882
883		be->cur_rel = 1;
884		if (!be->xi2) { /* fallback */
885			be->ignore_warp = 1;
886			XWarpPointer(dpy, None, win, 0, 0, 0, 0, mw->width/2, mw->height/2);
887		}
888		else
889			be->ignore_warp = 0;
890		XFlush(dpy);
891		return;
892	}
893
894	/* absolute cursor */
895	if (state == MAUS_CURSOR_STATE_ABSOLUTE) {
896		be->cur_rel = 0;
897		be->ignore_warp = 0;
898		XUngrabPointer(dpy, CurrentTime);
899		XFlush(dpy);
900		return;
901	}
902}
903