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