1 /*
2 Copyright 2012-2020 David Robillard <d@drobilla.net>
3 Copyright 2013 Robin Gareus <robin@gareus.org>
4 Copyright 2011-2012 Ben Loftis, Harrison Consoles
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19 #define _POSIX_C_SOURCE 199309L
20
21 #include "x11.h"
22
23 #include "implementation.h"
24 #include "types.h"
25
26 #include "pugl/pugl.h"
27
28 #include <X11/X.h>
29 #include <X11/Xatom.h>
30 #include <X11/Xlib.h>
31 #include <X11/Xutil.h>
32 #include <X11/keysym.h>
33
34 #ifdef HAVE_XRANDR
35 # include <X11/extensions/Xrandr.h>
36 #endif
37
38 #ifdef HAVE_XSYNC
39 # include <X11/extensions/sync.h>
40 # include <X11/extensions/syncconst.h>
41 #endif
42
43 #ifdef HAVE_XCURSOR
44 # include <X11/Xcursor/Xcursor.h>
45 # include <X11/cursorfont.h>
46 #endif
47
48 #include <sys/select.h>
49 #include <sys/time.h>
50
51 #include <math.h>
52 #include <stdbool.h>
53 #include <stdint.h>
54 #include <stdlib.h>
55 #include <string.h>
56 #include <time.h>
57
58 #ifndef MIN
59 # define MIN(a, b) (((a) < (b)) ? (a) : (b))
60 #endif
61
62 #ifndef MAX
63 # define MAX(a, b) (((a) > (b)) ? (a) : (b))
64 #endif
65
66 enum WmClientStateMessageAction {
67 WM_STATE_REMOVE,
68 WM_STATE_ADD,
69 WM_STATE_TOGGLE
70 };
71
72 static bool
puglInitXSync(PuglWorldInternals * impl)73 puglInitXSync(PuglWorldInternals* impl)
74 {
75 #ifdef HAVE_XSYNC
76 int syncMajor = 0;
77 int syncMinor = 0;
78 int errorBase = 0;
79 XSyncSystemCounter* counters = NULL;
80 int numCounters = 0;
81
82 if (XSyncQueryExtension(impl->display, &impl->syncEventBase, &errorBase) &&
83 XSyncInitialize(impl->display, &syncMajor, &syncMinor) &&
84 (counters = XSyncListSystemCounters(impl->display, &numCounters))) {
85 for (int n = 0; n < numCounters; ++n) {
86 if (!strcmp(counters[n].name, "SERVERTIME")) {
87 impl->serverTimeCounter = counters[n].counter;
88 impl->syncSupported = true;
89 break;
90 }
91 }
92
93 XSyncFreeSystemCounterList(counters);
94 }
95 #else
96 (void)impl;
97 #endif
98
99 return false;
100 }
101
102 PuglWorldInternals*
puglInitWorldInternals(PuglWorldType type,PuglWorldFlags flags)103 puglInitWorldInternals(PuglWorldType type, PuglWorldFlags flags)
104 {
105 if (type == PUGL_PROGRAM && (flags & PUGL_WORLD_THREADS)) {
106 XInitThreads();
107 }
108
109 Display* display = XOpenDisplay(NULL);
110 if (!display) {
111 return NULL;
112 }
113
114 PuglWorldInternals* impl =
115 (PuglWorldInternals*)calloc(1, sizeof(PuglWorldInternals));
116
117 impl->display = display;
118
119 // Intern the various atoms we will need
120 impl->atoms.CLIPBOARD = XInternAtom(display, "CLIPBOARD", 0);
121 impl->atoms.UTF8_STRING = XInternAtom(display, "UTF8_STRING", 0);
122 impl->atoms.TARGETS = XInternAtom(display, "TARGETS", 0);
123 impl->atoms.WM_PROTOCOLS = XInternAtom(display, "WM_PROTOCOLS", 0);
124 impl->atoms.WM_DELETE_WINDOW = XInternAtom(display, "WM_DELETE_WINDOW", 0);
125 impl->atoms.PUGL_CLIENT_MSG = XInternAtom(display, "_PUGL_CLIENT_MSG", 0);
126 impl->atoms.NET_WM_NAME = XInternAtom(display, "_NET_WM_NAME", 0);
127 impl->atoms.NET_WM_STATE = XInternAtom(display, "_NET_WM_STATE", 0);
128 impl->atoms.NET_WM_STATE_DEMANDS_ATTENTION =
129 XInternAtom(display, "_NET_WM_STATE_DEMANDS_ATTENTION", 0);
130
131 // Open input method
132 XSetLocaleModifiers("");
133 if (!(impl->xim = XOpenIM(display, NULL, NULL, NULL))) {
134 XSetLocaleModifiers("@im=");
135 impl->xim = XOpenIM(display, NULL, NULL, NULL);
136 }
137
138 puglInitXSync(impl);
139 XFlush(display);
140
141 return impl;
142 }
143
144 void*
puglGetNativeWorld(PuglWorld * world)145 puglGetNativeWorld(PuglWorld* world)
146 {
147 return world->impl->display;
148 }
149
150 PuglInternals*
puglInitViewInternals(void)151 puglInitViewInternals(void)
152 {
153 PuglInternals* impl = (PuglInternals*)calloc(1, sizeof(PuglInternals));
154
155 #ifdef HAVE_XCURSOR
156 impl->cursorShape = XC_arrow;
157 #endif
158
159 return impl;
160 }
161
162 static PuglStatus
puglPollX11Socket(PuglWorld * world,const double timeout)163 puglPollX11Socket(PuglWorld* world, const double timeout)
164 {
165 if (XPending(world->impl->display) > 0) {
166 return PUGL_SUCCESS;
167 }
168
169 const int fd = ConnectionNumber(world->impl->display);
170 const int nfds = fd + 1;
171 int ret = 0;
172 fd_set fds;
173 FD_ZERO(&fds); // NOLINT
174 FD_SET(fd, &fds);
175
176 if (timeout < 0.0) {
177 ret = select(nfds, &fds, NULL, NULL, NULL);
178 } else {
179 const long sec = (long)timeout;
180 const long usec = (long)((timeout - (double)sec) * 1e6);
181 struct timeval tv = {sec, usec};
182 ret = select(nfds, &fds, NULL, NULL, &tv);
183 }
184
185 return ret < 0 ? PUGL_UNKNOWN_ERROR : PUGL_SUCCESS;
186 }
187
188 static PuglView*
puglFindView(PuglWorld * world,const Window window)189 puglFindView(PuglWorld* world, const Window window)
190 {
191 for (size_t i = 0; i < world->numViews; ++i) {
192 if (world->views[i]->impl->win == window) {
193 return world->views[i];
194 }
195 }
196
197 return NULL;
198 }
199
200 static PuglStatus
updateSizeHints(const PuglView * view)201 updateSizeHints(const PuglView* view)
202 {
203 if (!view->impl->win) {
204 return PUGL_SUCCESS;
205 }
206
207 Display* display = view->world->impl->display;
208 XSizeHints sizeHints = {0};
209
210 if (!view->hints[PUGL_RESIZABLE]) {
211 sizeHints.flags = PBaseSize | PMinSize | PMaxSize;
212 sizeHints.base_width = (int)view->frame.width;
213 sizeHints.base_height = (int)view->frame.height;
214 sizeHints.min_width = (int)view->frame.width;
215 sizeHints.min_height = (int)view->frame.height;
216 sizeHints.max_width = (int)view->frame.width;
217 sizeHints.max_height = (int)view->frame.height;
218 } else {
219 if (view->defaultWidth || view->defaultHeight) {
220 sizeHints.flags = PBaseSize;
221 sizeHints.base_width = view->defaultWidth;
222 sizeHints.base_height = view->defaultHeight;
223 }
224 if (view->minWidth || view->minHeight) {
225 sizeHints.flags = PMinSize;
226 sizeHints.min_width = view->minWidth;
227 sizeHints.min_height = view->minHeight;
228 }
229 if (view->maxWidth || view->maxHeight) {
230 sizeHints.flags = PMaxSize;
231 sizeHints.max_width = view->maxWidth;
232 sizeHints.max_height = view->maxHeight;
233 }
234 if (view->minAspectX) {
235 sizeHints.flags |= PAspect;
236 sizeHints.min_aspect.x = view->minAspectX;
237 sizeHints.min_aspect.y = view->minAspectY;
238 sizeHints.max_aspect.x = view->maxAspectX;
239 sizeHints.max_aspect.y = view->maxAspectY;
240 }
241 }
242
243 XSetNormalHints(display, view->impl->win, &sizeHints);
244 return PUGL_SUCCESS;
245 }
246
247 #ifdef HAVE_XCURSOR
248 static PuglStatus
puglDefineCursorShape(PuglView * view,unsigned shape)249 puglDefineCursorShape(PuglView* view, unsigned shape)
250 {
251 PuglInternals* const impl = view->impl;
252 PuglWorld* const world = view->world;
253 Display* const display = world->impl->display;
254 const Cursor cur = XcursorShapeLoadCursor(display, shape);
255
256 if (cur) {
257 XDefineCursor(display, impl->win, cur);
258 XFreeCursor(display, cur);
259 return PUGL_SUCCESS;
260 }
261
262 return PUGL_FAILURE;
263 }
264 #endif
265
266 PuglStatus
puglRealize(PuglView * view)267 puglRealize(PuglView* view)
268 {
269 PuglInternals* const impl = view->impl;
270 PuglWorld* const world = view->world;
271 PuglX11Atoms* const atoms = &view->world->impl->atoms;
272 Display* const display = world->impl->display;
273 const int screen = DefaultScreen(display);
274 const Window root = RootWindow(display, screen);
275 const Window parent = view->parent ? (Window)view->parent : root;
276 XSetWindowAttributes attr = {0};
277 PuglStatus st = PUGL_SUCCESS;
278
279 // Ensure that we're unrealized and that a reasonable backend has been set
280 if (impl->win) {
281 return PUGL_FAILURE;
282 }
283
284 if (!view->backend || !view->backend->configure) {
285 return PUGL_BAD_BACKEND;
286 }
287
288 // Set the size to the default if it has not already been set
289 if (view->frame.width == 0.0 && view->frame.height == 0.0) {
290 if (view->defaultWidth == 0.0 || view->defaultHeight == 0.0) {
291 return PUGL_BAD_CONFIGURATION;
292 }
293
294 view->frame.width = view->defaultWidth;
295 view->frame.height = view->defaultHeight;
296 }
297
298 // Center top-level windows if a position has not been set
299 if (!view->parent && view->frame.x == 0.0 && view->frame.y == 0.0) {
300 const int screenWidth = DisplayWidth(display, screen);
301 const int screenHeight = DisplayHeight(display, screen);
302
303 view->frame.x = screenWidth / 2.0 - view->frame.width / 2.0;
304 view->frame.y = screenHeight / 2.0 - view->frame.height / 2.0;
305 }
306
307 // Configure the backend to get the visual info
308 impl->display = display;
309 impl->screen = screen;
310 if ((st = view->backend->configure(view)) || !impl->vi) {
311 view->backend->destroy(view);
312 return st ? st : PUGL_BACKEND_FAILED;
313 }
314
315 // Create a colormap based on the visual info from the backend
316 attr.colormap = XCreateColormap(display, parent, impl->vi->visual, AllocNone);
317
318 // Set the event mask to request all of the event types we react to
319 attr.event_mask |= ButtonPressMask;
320 attr.event_mask |= ButtonReleaseMask;
321 attr.event_mask |= EnterWindowMask;
322 attr.event_mask |= ExposureMask;
323 attr.event_mask |= FocusChangeMask;
324 attr.event_mask |= KeyPressMask;
325 attr.event_mask |= KeyReleaseMask;
326 attr.event_mask |= LeaveWindowMask;
327 attr.event_mask |= PointerMotionMask;
328 attr.event_mask |= StructureNotifyMask;
329 attr.event_mask |= VisibilityChangeMask;
330
331 // Create the window
332 impl->win = XCreateWindow(display,
333 parent,
334 (int)view->frame.x,
335 (int)view->frame.y,
336 (unsigned)view->frame.width,
337 (unsigned)view->frame.height,
338 0,
339 impl->vi->depth,
340 InputOutput,
341 impl->vi->visual,
342 CWColormap | CWEventMask,
343 &attr);
344
345 // Create the backend drawing context/surface
346 if ((st = view->backend->create(view))) {
347 return st;
348 }
349
350 #ifdef HAVE_XRANDR
351 // Set refresh rate hint to the real refresh rate
352 XRRScreenConfiguration* conf = XRRGetScreenInfo(display, parent);
353 short current_rate = XRRConfigCurrentRate(conf);
354
355 view->hints[PUGL_REFRESH_RATE] = current_rate;
356 XRRFreeScreenConfigInfo(conf);
357 #endif
358
359 updateSizeHints(view);
360
361 XClassHint classHint = {world->className, world->className};
362 XSetClassHint(display, impl->win, &classHint);
363
364 if (view->title) {
365 puglSetWindowTitle(view, view->title);
366 }
367
368 if (parent == root) {
369 XSetWMProtocols(display, impl->win, &atoms->WM_DELETE_WINDOW, 1);
370 }
371
372 if (view->transientParent) {
373 XSetTransientForHint(display, impl->win, (Window)view->transientParent);
374 }
375
376 // Create input context
377 impl->xic = XCreateIC(world->impl->xim,
378 XNInputStyle,
379 XIMPreeditNothing | XIMStatusNothing,
380 XNClientWindow,
381 impl->win,
382 XNFocusWindow,
383 impl->win,
384 NULL);
385
386 #ifdef HAVE_XCURSOR
387 puglDefineCursorShape(view, impl->cursorShape);
388 #endif
389
390 puglDispatchSimpleEvent(view, PUGL_CREATE);
391
392 return PUGL_SUCCESS;
393 }
394
395 PuglStatus
puglShow(PuglView * view)396 puglShow(PuglView* view)
397 {
398 PuglStatus st = PUGL_SUCCESS;
399
400 if (!view->impl->win) {
401 if ((st = puglRealize(view))) {
402 return st;
403 }
404 }
405
406 XMapRaised(view->impl->display, view->impl->win);
407 puglPostRedisplay(view);
408
409 return st;
410 }
411
412 PuglStatus
puglHide(PuglView * view)413 puglHide(PuglView* view)
414 {
415 XUnmapWindow(view->impl->display, view->impl->win);
416 return PUGL_SUCCESS;
417 }
418
419 void
puglFreeViewInternals(PuglView * view)420 puglFreeViewInternals(PuglView* view)
421 {
422 if (view && view->impl) {
423 if (view->impl->xic) {
424 XDestroyIC(view->impl->xic);
425 }
426 if (view->backend) {
427 view->backend->destroy(view);
428 }
429 if (view->impl->display) {
430 XDestroyWindow(view->impl->display, view->impl->win);
431 }
432 XFree(view->impl->vi);
433 free(view->impl);
434 }
435 }
436
437 void
puglFreeWorldInternals(PuglWorld * world)438 puglFreeWorldInternals(PuglWorld* world)
439 {
440 if (world->impl->xim) {
441 XCloseIM(world->impl->xim);
442 }
443 XCloseDisplay(world->impl->display);
444 free(world->impl->timers);
445 free(world->impl);
446 }
447
448 static PuglKey
keySymToSpecial(KeySym sym)449 keySymToSpecial(KeySym sym)
450 {
451 switch (sym) {
452 case XK_F1:
453 return PUGL_KEY_F1;
454 case XK_F2:
455 return PUGL_KEY_F2;
456 case XK_F3:
457 return PUGL_KEY_F3;
458 case XK_F4:
459 return PUGL_KEY_F4;
460 case XK_F5:
461 return PUGL_KEY_F5;
462 case XK_F6:
463 return PUGL_KEY_F6;
464 case XK_F7:
465 return PUGL_KEY_F7;
466 case XK_F8:
467 return PUGL_KEY_F8;
468 case XK_F9:
469 return PUGL_KEY_F9;
470 case XK_F10:
471 return PUGL_KEY_F10;
472 case XK_F11:
473 return PUGL_KEY_F11;
474 case XK_F12:
475 return PUGL_KEY_F12;
476 case XK_Left:
477 return PUGL_KEY_LEFT;
478 case XK_Up:
479 return PUGL_KEY_UP;
480 case XK_Right:
481 return PUGL_KEY_RIGHT;
482 case XK_Down:
483 return PUGL_KEY_DOWN;
484 case XK_Page_Up:
485 return PUGL_KEY_PAGE_UP;
486 case XK_Page_Down:
487 return PUGL_KEY_PAGE_DOWN;
488 case XK_Home:
489 return PUGL_KEY_HOME;
490 case XK_End:
491 return PUGL_KEY_END;
492 case XK_Insert:
493 return PUGL_KEY_INSERT;
494 case XK_Shift_L:
495 return PUGL_KEY_SHIFT_L;
496 case XK_Shift_R:
497 return PUGL_KEY_SHIFT_R;
498 case XK_Control_L:
499 return PUGL_KEY_CTRL_L;
500 case XK_Control_R:
501 return PUGL_KEY_CTRL_R;
502 case XK_Alt_L:
503 return PUGL_KEY_ALT_L;
504 case XK_ISO_Level3_Shift:
505 case XK_Alt_R:
506 return PUGL_KEY_ALT_R;
507 case XK_Super_L:
508 return PUGL_KEY_SUPER_L;
509 case XK_Super_R:
510 return PUGL_KEY_SUPER_R;
511 case XK_Menu:
512 return PUGL_KEY_MENU;
513 case XK_Caps_Lock:
514 return PUGL_KEY_CAPS_LOCK;
515 case XK_Scroll_Lock:
516 return PUGL_KEY_SCROLL_LOCK;
517 case XK_Num_Lock:
518 return PUGL_KEY_NUM_LOCK;
519 case XK_Print:
520 return PUGL_KEY_PRINT_SCREEN;
521 case XK_Pause:
522 return PUGL_KEY_PAUSE;
523 default:
524 break;
525 }
526 return (PuglKey)0;
527 }
528
529 static int
lookupString(XIC xic,XEvent * xevent,char * str,KeySym * sym)530 lookupString(XIC xic, XEvent* xevent, char* str, KeySym* sym)
531 {
532 Status status = 0;
533
534 #ifdef X_HAVE_UTF8_STRING
535 const int n = Xutf8LookupString(xic, &xevent->xkey, str, 7, sym, &status);
536 #else
537 const int n = XmbLookupString(xic, &xevent->xkey, str, 7, sym, &status);
538 #endif
539
540 return status == XBufferOverflow ? 0 : n;
541 }
542
543 static void
translateKey(PuglView * view,XEvent * xevent,PuglEvent * event)544 translateKey(PuglView* view, XEvent* xevent, PuglEvent* event)
545 {
546 const unsigned state = xevent->xkey.state;
547 const bool filter = XFilterEvent(xevent, None);
548
549 event->key.keycode = xevent->xkey.keycode;
550 xevent->xkey.state = 0;
551
552 // Lookup unshifted key
553 char ustr[8] = {0};
554 KeySym sym = 0;
555 const int ufound = XLookupString(&xevent->xkey, ustr, 8, &sym, NULL);
556 const PuglKey special = keySymToSpecial(sym);
557
558 event->key.key =
559 ((special || ufound <= 0) ? special : puglDecodeUTF8((const uint8_t*)ustr));
560
561 if (xevent->type == KeyPress && !filter && !special) {
562 // Lookup shifted key for possible text event
563 xevent->xkey.state = state;
564
565 char sstr[8] = {0};
566 const int sfound = lookupString(view->impl->xic, xevent, sstr, &sym);
567 if (sfound > 0) {
568 // Dispatch key event now
569 puglDispatchEvent(view, event);
570
571 // "Return" a text event in its place
572 event->text.type = PUGL_TEXT;
573 event->text.character = puglDecodeUTF8((const uint8_t*)sstr);
574 memcpy(event->text.string, sstr, sizeof(sstr));
575 }
576 }
577 }
578
579 static uint32_t
translateModifiers(const unsigned xstate)580 translateModifiers(const unsigned xstate)
581 {
582 return (((xstate & ShiftMask) ? PUGL_MOD_SHIFT : 0u) |
583 ((xstate & ControlMask) ? PUGL_MOD_CTRL : 0u) |
584 ((xstate & Mod1Mask) ? PUGL_MOD_ALT : 0u) |
585 ((xstate & Mod4Mask) ? PUGL_MOD_SUPER : 0u));
586 }
587
588 static PuglEvent
translateEvent(PuglView * view,XEvent xevent)589 translateEvent(PuglView* view, XEvent xevent)
590 {
591 const PuglX11Atoms* atoms = &view->world->impl->atoms;
592
593 PuglEvent event = {{PUGL_NOTHING, 0}};
594 event.any.flags = xevent.xany.send_event ? PUGL_IS_SEND_EVENT : 0;
595
596 switch (xevent.type) {
597 case ClientMessage:
598 if (xevent.xclient.message_type == atoms->WM_PROTOCOLS) {
599 const Atom protocol = (Atom)xevent.xclient.data.l[0];
600 if (protocol == atoms->WM_DELETE_WINDOW) {
601 event.type = PUGL_CLOSE;
602 }
603 } else if (xevent.xclient.message_type == atoms->PUGL_CLIENT_MSG) {
604 event.type = PUGL_CLIENT;
605 event.client.data1 = (uintptr_t)xevent.xclient.data.l[0];
606 event.client.data2 = (uintptr_t)xevent.xclient.data.l[1];
607 }
608 break;
609 case VisibilityNotify:
610 view->visible = xevent.xvisibility.state != VisibilityFullyObscured;
611 break;
612 case MapNotify:
613 event.type = PUGL_MAP;
614 break;
615 case UnmapNotify:
616 event.type = PUGL_UNMAP;
617 view->visible = false;
618 break;
619 case ConfigureNotify:
620 event.type = PUGL_CONFIGURE;
621 event.configure.x = xevent.xconfigure.x;
622 event.configure.y = xevent.xconfigure.y;
623 event.configure.width = xevent.xconfigure.width;
624 event.configure.height = xevent.xconfigure.height;
625 break;
626 case Expose:
627 event.type = PUGL_EXPOSE;
628 event.expose.x = xevent.xexpose.x;
629 event.expose.y = xevent.xexpose.y;
630 event.expose.width = xevent.xexpose.width;
631 event.expose.height = xevent.xexpose.height;
632 break;
633 case MotionNotify:
634 event.type = PUGL_MOTION;
635 event.motion.time = (double)xevent.xmotion.time / 1e3;
636 event.motion.x = xevent.xmotion.x;
637 event.motion.y = xevent.xmotion.y;
638 event.motion.xRoot = xevent.xmotion.x_root;
639 event.motion.yRoot = xevent.xmotion.y_root;
640 event.motion.state = translateModifiers(xevent.xmotion.state);
641 if (xevent.xmotion.is_hint == NotifyHint) {
642 event.motion.flags |= PUGL_IS_HINT;
643 }
644 break;
645 case ButtonPress:
646 if (xevent.xbutton.button >= 4 && xevent.xbutton.button <= 7) {
647 event.type = PUGL_SCROLL;
648 event.scroll.time = (double)xevent.xbutton.time / 1e3;
649 event.scroll.x = xevent.xbutton.x;
650 event.scroll.y = xevent.xbutton.y;
651 event.scroll.xRoot = xevent.xbutton.x_root;
652 event.scroll.yRoot = xevent.xbutton.y_root;
653 event.scroll.state = translateModifiers(xevent.xbutton.state);
654 event.scroll.dx = 0.0;
655 event.scroll.dy = 0.0;
656 switch (xevent.xbutton.button) {
657 case 4:
658 event.scroll.dy = 1.0;
659 event.scroll.direction = PUGL_SCROLL_UP;
660 break;
661 case 5:
662 event.scroll.dy = -1.0;
663 event.scroll.direction = PUGL_SCROLL_DOWN;
664 break;
665 case 6:
666 event.scroll.dx = -1.0;
667 event.scroll.direction = PUGL_SCROLL_LEFT;
668 break;
669 case 7:
670 event.scroll.dx = 1.0;
671 event.scroll.direction = PUGL_SCROLL_RIGHT;
672 break;
673 }
674 // fallthru
675 }
676 // fallthru
677 case ButtonRelease:
678 if (xevent.xbutton.button < 4 || xevent.xbutton.button > 7) {
679 event.button.type = ((xevent.type == ButtonPress) ? PUGL_BUTTON_PRESS
680 : PUGL_BUTTON_RELEASE);
681 event.button.time = (double)xevent.xbutton.time / 1e3;
682 event.button.x = xevent.xbutton.x;
683 event.button.y = xevent.xbutton.y;
684 event.button.xRoot = xevent.xbutton.x_root;
685 event.button.yRoot = xevent.xbutton.y_root;
686 event.button.state = translateModifiers(xevent.xbutton.state);
687 event.button.button = xevent.xbutton.button;
688 }
689 break;
690 case KeyPress:
691 case KeyRelease:
692 event.type =
693 ((xevent.type == KeyPress) ? PUGL_KEY_PRESS : PUGL_KEY_RELEASE);
694 event.key.time = (double)xevent.xkey.time / 1e3;
695 event.key.x = xevent.xkey.x;
696 event.key.y = xevent.xkey.y;
697 event.key.xRoot = xevent.xkey.x_root;
698 event.key.yRoot = xevent.xkey.y_root;
699 event.key.state = translateModifiers(xevent.xkey.state);
700 translateKey(view, &xevent, &event);
701 break;
702 case EnterNotify:
703 case LeaveNotify:
704 event.type =
705 ((xevent.type == EnterNotify) ? PUGL_POINTER_IN : PUGL_POINTER_OUT);
706 event.crossing.time = (double)xevent.xcrossing.time / 1e3;
707 event.crossing.x = xevent.xcrossing.x;
708 event.crossing.y = xevent.xcrossing.y;
709 event.crossing.xRoot = xevent.xcrossing.x_root;
710 event.crossing.yRoot = xevent.xcrossing.y_root;
711 event.crossing.state = translateModifiers(xevent.xcrossing.state);
712 event.crossing.mode = PUGL_CROSSING_NORMAL;
713 if (xevent.xcrossing.mode == NotifyGrab) {
714 event.crossing.mode = PUGL_CROSSING_GRAB;
715 } else if (xevent.xcrossing.mode == NotifyUngrab) {
716 event.crossing.mode = PUGL_CROSSING_UNGRAB;
717 }
718 break;
719
720 case FocusIn:
721 case FocusOut:
722 event.type = (xevent.type == FocusIn) ? PUGL_FOCUS_IN : PUGL_FOCUS_OUT;
723 event.focus.mode = PUGL_CROSSING_NORMAL;
724 if (xevent.xfocus.mode == NotifyGrab) {
725 event.focus.mode = PUGL_CROSSING_GRAB;
726 } else if (xevent.xfocus.mode == NotifyUngrab) {
727 event.focus.mode = PUGL_CROSSING_UNGRAB;
728 }
729 break;
730
731 default:
732 break;
733 }
734
735 return event;
736 }
737
738 PuglStatus
puglGrabFocus(PuglView * view)739 puglGrabFocus(PuglView* view)
740 {
741 XSetInputFocus(
742 view->impl->display, view->impl->win, RevertToNone, CurrentTime);
743 return PUGL_SUCCESS;
744 }
745
746 bool
puglHasFocus(const PuglView * view)747 puglHasFocus(const PuglView* view)
748 {
749 int revertTo = 0;
750 Window focusedWindow = 0;
751 XGetInputFocus(view->impl->display, &focusedWindow, &revertTo);
752 return focusedWindow == view->impl->win;
753 }
754
755 PuglStatus
puglRequestAttention(PuglView * view)756 puglRequestAttention(PuglView* view)
757 {
758 PuglInternals* const impl = view->impl;
759 const PuglX11Atoms* const atoms = &view->world->impl->atoms;
760 XEvent event = {0};
761
762 event.type = ClientMessage;
763 event.xclient.window = impl->win;
764 event.xclient.format = 32;
765 event.xclient.message_type = atoms->NET_WM_STATE;
766 event.xclient.data.l[0] = WM_STATE_ADD;
767 event.xclient.data.l[1] = (long)atoms->NET_WM_STATE_DEMANDS_ATTENTION;
768 event.xclient.data.l[2] = 0;
769 event.xclient.data.l[3] = 1;
770 event.xclient.data.l[4] = 0;
771
772 const Window root = RootWindow(impl->display, impl->screen);
773 XSendEvent(impl->display,
774 root,
775 False,
776 SubstructureNotifyMask | SubstructureRedirectMask,
777 &event);
778
779 return PUGL_SUCCESS;
780 }
781
782 PuglStatus
puglStartTimer(PuglView * view,uintptr_t id,double timeout)783 puglStartTimer(PuglView* view, uintptr_t id, double timeout)
784 {
785 #ifdef HAVE_XSYNC
786 if (view->world->impl->syncSupported) {
787 XSyncValue value;
788 XSyncIntToValue(&value, (int)floor(timeout * 1000.0));
789
790 PuglWorldInternals* w = view->world->impl;
791 Display* const display = w->display;
792 const XSyncCounter counter = w->serverTimeCounter;
793 const XSyncTestType type = XSyncPositiveTransition;
794 const XSyncTrigger trigger = {counter, XSyncRelative, value, type};
795 XSyncAlarmAttributes attr = {trigger, value, True, XSyncAlarmActive};
796 const XSyncAlarm alarm = XSyncCreateAlarm(display, 0x17, &attr);
797 const PuglTimer timer = {alarm, view, id};
798
799 if (alarm != None) {
800 for (size_t i = 0; i < w->numTimers; ++i) {
801 if (w->timers[i].view == view && w->timers[i].id == id) {
802 // Replace existing timer
803 XSyncDestroyAlarm(w->display, w->timers[i].alarm);
804 w->timers[i] = timer;
805 return PUGL_SUCCESS;
806 }
807 }
808
809 // Add new timer
810 const size_t size = ++w->numTimers * sizeof(timer);
811 w->timers = (PuglTimer*)realloc(w->timers, size);
812 w->timers[w->numTimers - 1] = timer;
813 return PUGL_SUCCESS;
814 }
815 }
816 #else
817 (void)view;
818 (void)id;
819 (void)timeout;
820 #endif
821
822 return PUGL_FAILURE;
823 }
824
825 PuglStatus
puglStopTimer(PuglView * view,uintptr_t id)826 puglStopTimer(PuglView* view, uintptr_t id)
827 {
828 #ifdef HAVE_XSYNC
829 PuglWorldInternals* w = view->world->impl;
830
831 for (size_t i = 0; i < w->numTimers; ++i) {
832 if (w->timers[i].view == view && w->timers[i].id == id) {
833 XSyncDestroyAlarm(w->display, w->timers[i].alarm);
834
835 if (i == w->numTimers - 1) {
836 memset(&w->timers[i], 0, sizeof(PuglTimer));
837 } else {
838 memmove(w->timers + i,
839 w->timers + i + 1,
840 sizeof(PuglTimer) * (w->numTimers - i - 1));
841
842 memset(&w->timers[i], 0, sizeof(PuglTimer));
843 }
844
845 --w->numTimers;
846 return PUGL_SUCCESS;
847 }
848 }
849 #else
850 (void)view;
851 (void)id;
852 #endif
853
854 return PUGL_FAILURE;
855 }
856
857 static XEvent
puglEventToX(PuglView * view,const PuglEvent * event)858 puglEventToX(PuglView* view, const PuglEvent* event)
859 {
860 XEvent xev = {0};
861 xev.xany.send_event = True;
862
863 switch (event->type) {
864 case PUGL_EXPOSE: {
865 const double x = floor(event->expose.x);
866 const double y = floor(event->expose.y);
867 const double w = ceil(event->expose.x + event->expose.width) - x;
868 const double h = ceil(event->expose.y + event->expose.height) - y;
869
870 xev.xexpose.type = Expose;
871 xev.xexpose.serial = 0;
872 xev.xexpose.display = view->impl->display;
873 xev.xexpose.window = view->impl->win;
874 xev.xexpose.x = (int)x;
875 xev.xexpose.y = (int)y;
876 xev.xexpose.width = (int)w;
877 xev.xexpose.height = (int)h;
878 break;
879 }
880
881 case PUGL_CLIENT:
882 xev.xclient.type = ClientMessage;
883 xev.xclient.serial = 0;
884 xev.xclient.send_event = True;
885 xev.xclient.display = view->impl->display;
886 xev.xclient.window = view->impl->win;
887 xev.xclient.message_type = view->world->impl->atoms.PUGL_CLIENT_MSG;
888 xev.xclient.format = 32;
889 xev.xclient.data.l[0] = (long)event->client.data1;
890 xev.xclient.data.l[1] = (long)event->client.data2;
891 break;
892
893 default:
894 break;
895 }
896
897 return xev;
898 }
899
900 PuglStatus
puglSendEvent(PuglView * view,const PuglEvent * event)901 puglSendEvent(PuglView* view, const PuglEvent* event)
902 {
903 XEvent xev = puglEventToX(view, event);
904
905 if (xev.type) {
906 if (XSendEvent(view->impl->display, view->impl->win, False, 0, &xev)) {
907 return PUGL_SUCCESS;
908 }
909
910 return PUGL_UNKNOWN_ERROR;
911 }
912
913 return PUGL_UNSUPPORTED_TYPE;
914 }
915
916 #ifndef PUGL_DISABLE_DEPRECATED
917 PuglStatus
puglWaitForEvent(PuglView * view)918 puglWaitForEvent(PuglView* view)
919 {
920 XEvent xevent;
921 XPeekEvent(view->impl->display, &xevent);
922 return PUGL_SUCCESS;
923 }
924 #endif
925
926 static void
mergeExposeEvents(PuglEventExpose * dst,const PuglEventExpose * src)927 mergeExposeEvents(PuglEventExpose* dst, const PuglEventExpose* src)
928 {
929 if (!dst->type) {
930 *dst = *src;
931 } else {
932 const double max_x = MAX(dst->x + dst->width, src->x + src->width);
933 const double max_y = MAX(dst->y + dst->height, src->y + src->height);
934
935 dst->x = MIN(dst->x, src->x);
936 dst->y = MIN(dst->y, src->y);
937 dst->width = max_x - dst->x;
938 dst->height = max_y - dst->y;
939 }
940 }
941
942 static void
handleSelectionNotify(const PuglWorld * world,PuglView * view)943 handleSelectionNotify(const PuglWorld* world, PuglView* view)
944 {
945 uint8_t* str = NULL;
946 Atom type = 0;
947 int fmt = 0;
948 unsigned long len = 0;
949 unsigned long left = 0;
950
951 XGetWindowProperty(world->impl->display,
952 view->impl->win,
953 XA_PRIMARY,
954 0,
955 0x1FFFFFFF,
956 False,
957 AnyPropertyType,
958 &type,
959 &fmt,
960 &len,
961 &left,
962 &str);
963
964 if (str && fmt == 8 && left == 0) {
965 char *type_name = XGetAtomName(world->impl->display, type);
966 if (type_name) {
967 puglSetBlob(&view->clipboardType, type_name, strlen(type_name) + 1);
968 XFree(type_name);
969 } else {
970 puglSetBlob(&view->clipboardType, NULL, 0);
971 }
972 puglSetBlob(&view->clipboard, str, len);
973 }
974
975 XFree(str);
976 }
977
978 static void
handleSelectionRequest(const PuglWorld * world,PuglView * view,const XSelectionRequestEvent * request)979 handleSelectionRequest(const PuglWorld* world,
980 PuglView* view,
981 const XSelectionRequestEvent* request)
982 {
983 XSelectionEvent note = {SelectionNotify,
984 request->serial,
985 False,
986 world->impl->display,
987 request->requestor,
988 request->selection,
989 request->target,
990 None,
991 request->time};
992
993 const char* type = NULL;
994 size_t len = 0;
995 const void* data = puglGetInternalClipboard(view, &type, &len);
996 if (data && request->selection == world->impl->atoms.CLIPBOARD) {
997 const Atom types [2] = {
998 world->impl->atoms.TARGETS,
999 XInternAtom(world->impl->display, type, 0)
1000 };
1001
1002 if (request->target == world->impl->atoms.TARGETS) {
1003 note.property = request->property;
1004 XChangeProperty(world->impl->display,
1005 note.requestor,
1006 note.property,
1007 XA_ATOM,
1008 32,
1009 PropModeReplace,
1010 (const uint8_t*)types,
1011 (int)(sizeof(types) / sizeof(Atom)));
1012 } else if (request->target == types[1]) {
1013 note.property = request->property;
1014 XChangeProperty(world->impl->display,
1015 note.requestor,
1016 note.property,
1017 note.target,
1018 8,
1019 PropModeReplace,
1020 (const uint8_t*)data,
1021 (int)len);
1022 } else {
1023 note.property = None;
1024 }
1025 } else {
1026 note.property = None;
1027 }
1028
1029 XSendEvent(world->impl->display, note.requestor, True, 0, (XEvent*)¬e);
1030 }
1031
1032 /// Flush pending configure and expose events for all views
1033 static void
flushExposures(PuglWorld * world)1034 flushExposures(PuglWorld* world)
1035 {
1036 for (size_t i = 0; i < world->numViews; ++i) {
1037 PuglView* const view = world->views[i];
1038
1039 if (view->visible) {
1040 puglDispatchSimpleEvent(view, PUGL_UPDATE);
1041 }
1042
1043 const PuglEvent configure = view->impl->pendingConfigure;
1044 const PuglEvent expose = view->impl->pendingExpose;
1045
1046 view->impl->pendingConfigure.type = PUGL_NOTHING;
1047 view->impl->pendingExpose.type = PUGL_NOTHING;
1048
1049 if (configure.type || expose.type) {
1050 view->backend->enter(view, expose.type ? &expose.expose : NULL);
1051 puglDispatchEventInContext(view, &configure);
1052 puglDispatchEventInContext(view, &expose);
1053 view->backend->leave(view, expose.type ? &expose.expose : NULL);
1054 }
1055 }
1056 }
1057
1058 static bool
handleTimerEvent(PuglWorld * world,XEvent xevent)1059 handleTimerEvent(PuglWorld* world, XEvent xevent)
1060 {
1061 #ifdef HAVE_XSYNC
1062 if (xevent.type == world->impl->syncEventBase + XSyncAlarmNotify) {
1063 XSyncAlarmNotifyEvent* notify = ((XSyncAlarmNotifyEvent*)&xevent);
1064
1065 for (size_t i = 0; i < world->impl->numTimers; ++i) {
1066 if (world->impl->timers[i].alarm == notify->alarm) {
1067 PuglEvent event = {{PUGL_TIMER, 0}};
1068 event.timer.id = world->impl->timers[i].id;
1069 puglDispatchEvent(world->impl->timers[i].view,
1070 (const PuglEvent*)&event);
1071 }
1072 }
1073
1074 return true;
1075 }
1076 #else
1077 (void)world;
1078 (void)xevent;
1079 #endif
1080
1081 return false;
1082 }
1083
1084 static PuglStatus
puglDispatchX11Events(PuglWorld * world)1085 puglDispatchX11Events(PuglWorld* world)
1086 {
1087 const PuglX11Atoms* const atoms = &world->impl->atoms;
1088
1089 // Flush output to the server once at the start
1090 Display* display = world->impl->display;
1091 XFlush(display);
1092
1093 // Process all queued events (without further flushing)
1094 while (XEventsQueued(display, QueuedAfterReading) > 0) {
1095 XEvent xevent;
1096 XNextEvent(display, &xevent);
1097
1098 if (handleTimerEvent(world, xevent)) {
1099 continue;
1100 }
1101
1102 PuglView* view = puglFindView(world, xevent.xany.window);
1103 if (!view) {
1104 continue;
1105 }
1106
1107 // Handle special events
1108 PuglInternals* const impl = view->impl;
1109 if (xevent.type == KeyRelease && view->hints[PUGL_IGNORE_KEY_REPEAT]) {
1110 XEvent next;
1111 if (XCheckTypedWindowEvent(display, impl->win, KeyPress, &next) &&
1112 next.type == KeyPress && next.xkey.time == xevent.xkey.time &&
1113 next.xkey.keycode == xevent.xkey.keycode) {
1114 continue;
1115 }
1116 } else if (xevent.type == FocusIn) {
1117 XSetICFocus(impl->xic);
1118 } else if (xevent.type == FocusOut) {
1119 XUnsetICFocus(impl->xic);
1120 } else if (xevent.type == SelectionClear) {
1121 puglSetBlob(&view->clipboardType, NULL, 0);
1122 puglSetBlob(&view->clipboard, NULL, 0);
1123 } else if (xevent.type == SelectionNotify &&
1124 xevent.xselection.selection == atoms->CLIPBOARD &&
1125 xevent.xselection.property == XA_PRIMARY) {
1126 handleSelectionNotify(world, view);
1127 } else if (xevent.type == SelectionRequest) {
1128 handleSelectionRequest(world, view, &xevent.xselectionrequest);
1129 }
1130
1131 // Translate X11 event to Pugl event
1132 const PuglEvent event = translateEvent(view, xevent);
1133
1134 if (event.type == PUGL_EXPOSE) {
1135 // Expand expose event to be dispatched after loop
1136 mergeExposeEvents(&view->impl->pendingExpose.expose, &event.expose);
1137 } else if (event.type == PUGL_CONFIGURE) {
1138 // Expand configure event to be dispatched after loop
1139 view->impl->pendingConfigure = event;
1140 view->frame.x = event.configure.x;
1141 view->frame.y = event.configure.y;
1142 view->frame.width = event.configure.width;
1143 view->frame.height = event.configure.height;
1144 } else if (event.type == PUGL_MAP && view->parent) {
1145 XWindowAttributes attrs;
1146 XGetWindowAttributes(view->impl->display, view->impl->win, &attrs);
1147
1148 const PuglEventConfigure configure = {
1149 PUGL_CONFIGURE, 0, attrs.x, attrs.y, attrs.width, attrs.height};
1150
1151 puglDispatchEvent(view, (const PuglEvent*)&configure);
1152 puglDispatchEvent(view, &event);
1153 } else {
1154 // Dispatch event to application immediately
1155 puglDispatchEvent(view, &event);
1156 }
1157 }
1158
1159 return PUGL_SUCCESS;
1160 }
1161
1162 #ifndef PUGL_DISABLE_DEPRECATED
1163 PuglStatus
puglProcessEvents(PuglView * view)1164 puglProcessEvents(PuglView* view)
1165 {
1166 return puglUpdate(view->world, 0.0);
1167 }
1168 #endif
1169
1170 PuglStatus
puglUpdate(PuglWorld * world,double timeout)1171 puglUpdate(PuglWorld* world, double timeout)
1172 {
1173 const double startTime = puglGetTime(world);
1174 PuglStatus st = PUGL_SUCCESS;
1175
1176 world->impl->dispatchingEvents = true;
1177
1178 if (timeout < 0.0) {
1179 st = puglPollX11Socket(world, timeout);
1180 st = st ? st : puglDispatchX11Events(world);
1181 } else if (timeout <= 0.001) {
1182 st = puglDispatchX11Events(world);
1183 } else {
1184 const double endTime = startTime + timeout - 0.001;
1185 for (double t = startTime; t < endTime; t = puglGetTime(world)) {
1186 if ((st = puglPollX11Socket(world, endTime - t)) ||
1187 (st = puglDispatchX11Events(world))) {
1188 break;
1189 }
1190 }
1191 }
1192
1193 flushExposures(world);
1194
1195 world->impl->dispatchingEvents = false;
1196
1197 return st;
1198 }
1199
1200 double
puglGetTime(const PuglWorld * world)1201 puglGetTime(const PuglWorld* world)
1202 {
1203 struct timespec ts;
1204 clock_gettime(CLOCK_MONOTONIC, &ts);
1205 return ((double)ts.tv_sec + (double)ts.tv_nsec / 1000000000.0) -
1206 world->startTime;
1207 }
1208
1209 PuglStatus
puglPostRedisplay(PuglView * view)1210 puglPostRedisplay(PuglView* view)
1211 {
1212 const PuglRect rect = {0, 0, view->frame.width, view->frame.height};
1213
1214 return puglPostRedisplayRect(view, rect);
1215 }
1216
1217 PuglStatus
puglPostRedisplayRect(PuglView * view,PuglRect rect)1218 puglPostRedisplayRect(PuglView* view, PuglRect rect)
1219 {
1220 const PuglEventExpose event = {
1221 PUGL_EXPOSE, 0, rect.x, rect.y, rect.width, rect.height};
1222
1223 if (view->world->impl->dispatchingEvents) {
1224 // Currently dispatching events, add/expand expose for the loop end
1225 mergeExposeEvents(&view->impl->pendingExpose.expose, &event);
1226 } else if (view->visible) {
1227 // Not dispatching events, send an X expose so we wake up next time
1228 return puglSendEvent(view, (const PuglEvent*)&event);
1229 }
1230
1231 return PUGL_SUCCESS;
1232 }
1233
1234 PuglNativeView
puglGetNativeWindow(PuglView * view)1235 puglGetNativeWindow(PuglView* view)
1236 {
1237 return (PuglNativeView)view->impl->win;
1238 }
1239
1240 PuglStatus
puglSetWindowTitle(PuglView * view,const char * title)1241 puglSetWindowTitle(PuglView* view, const char* title)
1242 {
1243 Display* display = view->world->impl->display;
1244 const PuglX11Atoms* const atoms = &view->world->impl->atoms;
1245
1246 puglSetString(&view->title, title);
1247
1248 if (view->impl->win) {
1249 XStoreName(display, view->impl->win, title);
1250 XChangeProperty(display,
1251 view->impl->win,
1252 atoms->NET_WM_NAME,
1253 atoms->UTF8_STRING,
1254 8,
1255 PropModeReplace,
1256 (const uint8_t*)title,
1257 (int)strlen(title));
1258 }
1259
1260 return PUGL_SUCCESS;
1261 }
1262
1263 PuglStatus
puglSetFrame(PuglView * view,const PuglRect frame)1264 puglSetFrame(PuglView* view, const PuglRect frame)
1265 {
1266 if (view->impl->win) {
1267 if (!XMoveResizeWindow(view->world->impl->display,
1268 view->impl->win,
1269 (int)frame.x,
1270 (int)frame.y,
1271 (unsigned)frame.width,
1272 (unsigned)frame.height)) {
1273 return PUGL_UNKNOWN_ERROR;
1274 }
1275 }
1276
1277 view->frame = frame;
1278 return PUGL_SUCCESS;
1279 }
1280
1281 PuglStatus
puglSetDefaultSize(PuglView * const view,const int width,const int height)1282 puglSetDefaultSize(PuglView* const view, const int width, const int height)
1283 {
1284 view->defaultWidth = width;
1285 view->defaultHeight = height;
1286 return updateSizeHints(view);
1287 }
1288
1289 PuglStatus
puglSetMinSize(PuglView * const view,const int width,const int height)1290 puglSetMinSize(PuglView* const view, const int width, const int height)
1291 {
1292 view->minWidth = width;
1293 view->minHeight = height;
1294 return updateSizeHints(view);
1295 }
1296
1297 PuglStatus
puglSetMaxSize(PuglView * const view,const int width,const int height)1298 puglSetMaxSize(PuglView* const view, const int width, const int height)
1299 {
1300 view->minWidth = width;
1301 view->minHeight = height;
1302 return updateSizeHints(view);
1303 }
1304
1305 PuglStatus
puglSetAspectRatio(PuglView * const view,const int minX,const int minY,const int maxX,const int maxY)1306 puglSetAspectRatio(PuglView* const view,
1307 const int minX,
1308 const int minY,
1309 const int maxX,
1310 const int maxY)
1311 {
1312 view->minAspectX = minX;
1313 view->minAspectY = minY;
1314 view->maxAspectX = maxX;
1315 view->maxAspectY = maxY;
1316
1317 return updateSizeHints(view);
1318 }
1319
1320 PuglStatus
puglSetTransientFor(PuglView * view,PuglNativeView parent)1321 puglSetTransientFor(PuglView* view, PuglNativeView parent)
1322 {
1323 Display* display = view->world->impl->display;
1324
1325 view->transientParent = parent;
1326
1327 if (view->impl->win) {
1328 XSetTransientForHint(
1329 display, view->impl->win, (Window)view->transientParent);
1330 }
1331
1332 return PUGL_SUCCESS;
1333 }
1334
1335 const void*
puglGetClipboard(PuglView * const view,const char ** const type,size_t * const len)1336 puglGetClipboard(PuglView* const view,
1337 const char** const type,
1338 size_t* const len)
1339 {
1340 PuglInternals* const impl = view->impl;
1341 const PuglX11Atoms* const atoms = &view->world->impl->atoms;
1342
1343 const Window owner = XGetSelectionOwner(impl->display, atoms->CLIPBOARD);
1344 if (owner != None && owner != impl->win) {
1345 // Clear internal selection
1346 puglSetBlob(&view->clipboardType, NULL, 0);
1347 puglSetBlob(&view->clipboard, NULL, 0);
1348
1349 const Atom type_atom = type && *type
1350 ? XInternAtom(impl->display, *type, 0)
1351 : atoms->UTF8_STRING;
1352
1353 // Request selection from the owner
1354 XConvertSelection(impl->display,
1355 atoms->CLIPBOARD,
1356 type_atom,
1357 XA_PRIMARY,
1358 impl->win,
1359 CurrentTime);
1360
1361 // Run event loop until data is received
1362 while (!view->clipboard.data) {
1363 puglUpdate(view->world, -1.0);
1364 }
1365 }
1366
1367 return puglGetInternalClipboard(view, type, len);
1368 }
1369
1370 PuglStatus
puglSetClipboard(PuglView * const view,const char * const type,const void * const data,const size_t len)1371 puglSetClipboard(PuglView* const view,
1372 const char* const type,
1373 const void* const data,
1374 const size_t len)
1375 {
1376 PuglInternals* const impl = view->impl;
1377 const PuglX11Atoms* const atoms = &view->world->impl->atoms;
1378
1379 PuglStatus st = puglSetInternalClipboard(view, type, data, len);
1380 if (st) {
1381 return st;
1382 }
1383
1384 XSetSelectionOwner(impl->display, atoms->CLIPBOARD, impl->win, CurrentTime);
1385 return PUGL_SUCCESS;
1386 }
1387
1388 #ifdef HAVE_XCURSOR
1389 static const unsigned cursor_nums[] = {
1390 XC_arrow, // ARROW
1391 XC_xterm, // CARET
1392 XC_crosshair, // CROSSHAIR
1393 XC_hand2, // HAND
1394 XC_pirate, // NO
1395 XC_sb_h_double_arrow, // LEFT_RIGHT
1396 XC_sb_v_double_arrow, // UP_DOWN
1397 };
1398 #endif
1399
1400 PuglStatus
puglSetCursor(PuglView * view,PuglCursor cursor)1401 puglSetCursor(PuglView* view, PuglCursor cursor)
1402 {
1403 #ifdef HAVE_XCURSOR
1404 PuglInternals* const impl = view->impl;
1405 const unsigned index = (unsigned)cursor;
1406 const unsigned count = sizeof(cursor_nums) / sizeof(cursor_nums[0]);
1407 if (index >= count) {
1408 return PUGL_BAD_PARAMETER;
1409 }
1410
1411 const unsigned shape = cursor_nums[index];
1412 if (!impl->win || impl->cursorShape == shape) {
1413 return PUGL_SUCCESS;
1414 }
1415
1416 impl->cursorShape = cursor_nums[index];
1417
1418 return puglDefineCursorShape(view, impl->cursorShape);
1419 #else
1420 (void)view;
1421 (void)cursor;
1422 return PUGL_FAILURE;
1423 #endif
1424 }
1425