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