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