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*)¬e);
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