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