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