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