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