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