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