1 /*
2 Copyright 2012-2014 David Robillard <http://drobilla.net>
3 Copyright 2011-2012 Ben Loftis, Harrison Consoles
4 Copyright 2013,2015 Robin Gareus <robin@gareus.org>
5 Copyright 2012-2019 Filipe Coelho <falktx@falktx.com>
6
7 Permission to use, copy, modify, and/or distribute this software for any
8 purpose with or without fee is hereby granted, provided that the above
9 copyright notice and this permission notice appear in all copies.
10
11 THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 */
19
20 /**
21 @file pugl_x11.c X11 Pugl Implementation.
22 */
23
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27
28 #ifdef PUGL_CAIRO
29 #include <cairo/cairo.h>
30 #include <cairo/cairo-xlib.h>
31 #endif
32 #ifdef PUGL_OPENGL
33 #include <GL/gl.h>
34 #include <GL/glx.h>
35 #endif
36 #include <X11/Xatom.h>
37 #include <X11/Xlib.h>
38 #include <X11/Xutil.h>
39 #include <X11/keysym.h>
40
41 #include "pugl_internal.h"
42
43 #ifndef DGL_FILE_BROWSER_DISABLED
44 #define SOFD_HAVE_X11
45 #include "../sofd/libsofd.h"
46 #include "../sofd/libsofd.c"
47 #endif
48
49 /* work around buggy re-parent & focus issues on some systems
50 * where no keyboard events are passed through even if the
51 * app has mouse-focus and all other events are working.
52 */
53 //#define PUGL_GRAB_FOCUS
54
55 /* show messages during initalization
56 */
57 //#define PUGL_VERBOSE
58
59 struct PuglInternalsImpl {
60 Display* display;
61 int screen;
62 Window win;
63 #ifdef PUGL_CAIRO
64 cairo_t* xlib_cr;
65 cairo_t* buffer_cr;
66 cairo_surface_t* xlib_surface;
67 cairo_surface_t* buffer_surface;
68 #endif
69 #ifdef PUGL_OPENGL
70 GLXContext ctx;
71 Bool doubleBuffered;
72 #endif
73 };
74
75 #ifdef PUGL_OPENGL
76 /**
77 Attributes for single-buffered RGBA with at least
78 4 bits per color and a 16 bit depth buffer.
79 */
80 static int attrListSgl[] = {
81 GLX_RGBA,
82 GLX_RED_SIZE, 4,
83 GLX_GREEN_SIZE, 4,
84 GLX_BLUE_SIZE, 4,
85 GLX_DEPTH_SIZE, 16,
86 GLX_ARB_multisample, 1,
87 None
88 };
89
90 /**
91 Attributes for double-buffered RGBA with at least
92 4 bits per color and a 16 bit depth buffer.
93 */
94 static int attrListDbl[] = {
95 GLX_RGBA,
96 GLX_DOUBLEBUFFER, True,
97 GLX_RED_SIZE, 4,
98 GLX_GREEN_SIZE, 4,
99 GLX_BLUE_SIZE, 4,
100 GLX_DEPTH_SIZE, 16,
101 GLX_ARB_multisample, 1,
102 None
103 };
104
105 /**
106 Attributes for double-buffered RGBA with multi-sampling
107 (antialiasing)
108 */
109 static int attrListDblMS[] = {
110 GLX_RGBA,
111 GLX_DOUBLEBUFFER, True,
112 GLX_RED_SIZE, 4,
113 GLX_GREEN_SIZE, 4,
114 GLX_BLUE_SIZE, 4,
115 GLX_ALPHA_SIZE, 4,
116 GLX_DEPTH_SIZE, 16,
117 GLX_SAMPLE_BUFFERS, 1,
118 GLX_SAMPLES, 4,
119 None
120 };
121 #endif
122
123 PuglInternals*
puglInitInternals(void)124 puglInitInternals(void)
125 {
126 return (PuglInternals*)calloc(1, sizeof(PuglInternals));
127 }
128
129 void
puglEnterContext(PuglView * view)130 puglEnterContext(PuglView* view)
131 {
132 #ifdef PUGL_OPENGL
133 glXMakeCurrent(view->impl->display, view->impl->win, view->impl->ctx);
134 #endif
135 }
136
137 void
puglLeaveContext(PuglView * view,bool flush)138 puglLeaveContext(PuglView* view, bool flush)
139 {
140 #ifdef PUGL_OPENGL
141 if (flush) {
142 glFlush();
143 if (view->impl->doubleBuffered) {
144 glXSwapBuffers(view->impl->display, view->impl->win);
145 }
146 }
147 glXMakeCurrent(view->impl->display, None, NULL);
148 #endif
149 }
150
151 int
puglCreateWindow(PuglView * view,const char * title)152 puglCreateWindow(PuglView* view, const char* title)
153 {
154 PuglInternals* impl = view->impl;
155 if (!impl) {
156 return 1;
157 }
158
159 view->impl = impl;
160 impl->display = XOpenDisplay(NULL);
161 if (!impl->display) {
162 free(impl);
163 return 1;
164 }
165 impl->screen = DefaultScreen(impl->display);
166
167 XVisualInfo* vi = NULL;
168
169 #ifdef PUGL_OPENGL
170 impl->doubleBuffered = True;
171 vi = glXChooseVisual(impl->display, impl->screen, attrListDblMS);
172
173 if (!vi) {
174 vi = glXChooseVisual(impl->display, impl->screen, attrListDbl);
175 #ifdef PUGL_VERBOSE
176 printf("puGL: multisampling (antialiasing) is not available\n");
177 #endif
178 }
179
180 if (!vi) {
181 vi = glXChooseVisual(impl->display, impl->screen, attrListSgl);
182 impl->doubleBuffered = False;
183 }
184 #endif
185 #ifdef PUGL_CAIRO
186 XVisualInfo pat;
187 int n;
188 pat.screen = impl->screen;
189 vi = XGetVisualInfo(impl->display, VisualScreenMask, &pat, &n);
190 #endif
191
192 if (!vi) {
193 XCloseDisplay(impl->display);
194 free(impl);
195 return 1;
196 }
197
198 #ifdef PUGL_VERBOSE
199 #ifdef PUGL_OPENGL
200 int glxMajor, glxMinor;
201 glXQueryVersion(impl->display, &glxMajor, &glxMinor);
202 printf("puGL: GLX-Version : %d.%d\n", glxMajor, glxMinor);
203 #endif
204 #endif
205
206 #ifdef PUGL_OPENGL
207 impl->ctx = glXCreateContext(impl->display, vi, 0, GL_TRUE);
208
209 if (!impl->ctx) {
210 XFree(vi);
211 XCloseDisplay(impl->display);
212 free(impl);
213 return 1;
214 }
215 #endif
216
217 Window xParent = view->parent
218 ? (Window)view->parent
219 : RootWindow(impl->display, impl->screen);
220
221 Colormap cmap = XCreateColormap(
222 impl->display, xParent, vi->visual, AllocNone);
223
224 XSetWindowAttributes attr;
225 memset(&attr, 0, sizeof(XSetWindowAttributes));
226 attr.border_pixel = BlackPixel(impl->display, impl->screen);
227 attr.colormap = cmap;
228 attr.event_mask = (ExposureMask | StructureNotifyMask |
229 EnterWindowMask | LeaveWindowMask |
230 KeyPressMask | KeyReleaseMask |
231 ButtonPressMask | ButtonReleaseMask |
232 PointerMotionMask | FocusChangeMask);
233
234 impl->win = XCreateWindow(
235 impl->display, xParent,
236 0, 0, view->width, view->height, 0, vi->depth, InputOutput, vi->visual,
237 CWBorderPixel | CWColormap | CWEventMask, &attr);
238
239 if (!impl->win) {
240 #ifdef PUGL_OPENGL
241 glXDestroyContext(impl->display, impl->ctx);
242 #endif
243 XFree(vi);
244 XCloseDisplay(impl->display);
245 free(impl);
246 return 1;
247 }
248
249 #ifdef PUGL_CAIRO
250 impl->xlib_surface = cairo_xlib_surface_create(
251 impl->display, impl->win, vi->visual, view->width, view->height);
252 if (impl->xlib_surface == NULL || cairo_surface_status(impl->xlib_surface) != CAIRO_STATUS_SUCCESS) {
253 printf("puGL: failed to create cairo surface\n");
254 }
255 else {
256 impl->xlib_cr = cairo_create(impl->xlib_surface);
257 }
258 if (impl->xlib_cr == NULL || cairo_status(impl->xlib_cr) != CAIRO_STATUS_SUCCESS) {
259 cairo_destroy(impl->xlib_cr);
260 cairo_surface_destroy(impl->xlib_surface);
261 XDestroyWindow(impl->display, impl->win);
262 XFree(vi);
263 XCloseDisplay(impl->display);
264 free(impl);
265 printf("puGL: failed to create cairo context\n");
266 return 1;
267 }
268 #endif
269
270 if (view->width > 1 || view->height > 1) {
271 puglUpdateGeometryConstraints(view, view->min_width, view->min_height, view->min_width != view->width);
272 XResizeWindow(view->impl->display, view->impl->win, view->width, view->height);
273 }
274
275 if (title) {
276 XStoreName(impl->display, impl->win, title);
277 Atom netWmName = XInternAtom(impl->display, "_NET_WM_NAME", False);
278 Atom utf8String = XInternAtom(impl->display, "UTF8_STRING", False);
279 XChangeProperty(impl->display, impl->win, netWmName, utf8String, 8, PropModeReplace, (unsigned char *)title, strlen(title));
280 }
281
282 if (view->transient_parent > 0) {
283 XSetTransientForHint(impl->display, impl->win, (Window)view->transient_parent);
284 }
285
286 if (view->parent) {
287 XMapRaised(impl->display, impl->win);
288 } else {
289 Atom wmDelete = XInternAtom(impl->display, "WM_DELETE_WINDOW", True);
290 XSetWMProtocols(impl->display, impl->win, &wmDelete, 1);
291 }
292
293 #ifdef PUGL_VERBOSE
294 #ifdef PUGL_OPENGL
295 if (glXIsDirect(impl->display, impl->ctx)) {
296 printf("puGL: DRI enabled (to disable, set LIBGL_ALWAYS_INDIRECT=1\n");
297 } else {
298 printf("puGL: No DRI available\n");
299 }
300 #endif
301 #endif
302
303 XFree(vi);
304 return 0;
305 }
306
307 void
puglDestroy(PuglView * view)308 puglDestroy(PuglView* view)
309 {
310 if (!view) {
311 return;
312 }
313
314 PuglInternals* const impl = view->impl;
315
316 #ifndef DGL_FILE_BROWSER_DISABLED
317 x_fib_close(impl->display);
318 #endif
319
320 #ifdef PUGL_OPENGL
321 glXDestroyContext(impl->display, impl->ctx);
322 #endif
323 #ifdef PUGL_CAIRO
324 cairo_destroy(impl->xlib_cr);
325 cairo_destroy(impl->buffer_cr);
326 cairo_surface_destroy(impl->xlib_surface);
327 cairo_surface_destroy(impl->buffer_surface);
328 #endif
329 XDestroyWindow(impl->display, impl->win);
330 XCloseDisplay(impl->display);
331 free(impl);
332 free(view);
333 }
334
335 void
puglShowWindow(PuglView * view)336 puglShowWindow(PuglView* view)
337 {
338 XMapRaised(view->impl->display, view->impl->win);
339 }
340
341 void
puglHideWindow(PuglView * view)342 puglHideWindow(PuglView* view)
343 {
344 XUnmapWindow(view->impl->display, view->impl->win);
345 }
346
347 static void
puglReshape(PuglView * view,int width,int height)348 puglReshape(PuglView* view, int width, int height)
349 {
350 puglEnterContext(view);
351
352 if (view->reshapeFunc) {
353 view->reshapeFunc(view, width, height);
354 } else {
355 puglDefaultReshape(width, height);
356 }
357
358 puglLeaveContext(view, false);
359
360 view->width = width;
361 view->height = height;
362 }
363
364 static void
puglDisplay(PuglView * view)365 puglDisplay(PuglView* view)
366 {
367 PuglInternals* impl = view->impl;
368
369 puglEnterContext(view);
370
371 #ifdef PUGL_CAIRO
372 cairo_t* bc = impl->buffer_cr;
373 cairo_surface_t* xs = impl->xlib_surface;
374 cairo_surface_t* bs = impl->buffer_surface;
375 int w = cairo_xlib_surface_get_width(xs);
376 int h = cairo_xlib_surface_get_height(xs);
377
378 int bw = bs ? cairo_image_surface_get_width(bs) : -1;
379 int bh = bs ? cairo_image_surface_get_height(bs) : -1;
380 if (!bc || bw != w || bh != h) {
381 cairo_destroy(bc);
382 cairo_surface_destroy(bs);
383 bs = cairo_surface_create_similar_image(xs, CAIRO_FORMAT_ARGB32, w, h);
384 bc = bs ? cairo_create(bs) : NULL;
385 impl->buffer_cr = bc;
386 impl->buffer_surface = bs;
387 }
388
389 if (!bc) {
390 puglLeaveContext(view, false);
391 return;
392 }
393 #endif
394
395 view->redisplay = false;
396 if (view->displayFunc) {
397 view->displayFunc(view);
398 }
399
400 #ifdef PUGL_CAIRO
401 cairo_t* xc = impl->xlib_cr;
402 cairo_set_source_surface(xc, impl->buffer_surface, 0, 0);
403 cairo_paint(xc);
404 #endif
405
406 puglLeaveContext(view, true);
407 (void)impl;
408 }
409
410 static void
puglResize(PuglView * view)411 puglResize(PuglView* view)
412 {
413 int set_hints = 1;
414 view->pending_resize = false;
415 if (!view->resizeFunc) { return; }
416 /* ask the plugin about the new size */
417 view->resizeFunc(view, &view->width, &view->height, &set_hints);
418
419 if (set_hints) {
420 XSizeHints sizeHints;
421 memset(&sizeHints, 0, sizeof(sizeHints));
422 sizeHints.flags = PMinSize|PMaxSize;
423 sizeHints.min_width = view->width;
424 sizeHints.min_height = view->height;
425 sizeHints.max_width = view->user_resizable ? 4096 : view->width;
426 sizeHints.max_height = view->user_resizable ? 4096 : view->height;
427 XSetWMNormalHints(view->impl->display, view->impl->win, &sizeHints);
428 }
429 XResizeWindow(view->impl->display, view->impl->win, view->width, view->height);
430 XFlush(view->impl->display);
431
432 #ifdef PUGL_VERBOSE
433 printf("puGL: window resize (%dx%d)\n", view->width, view->height);
434 #endif
435
436 /* and call Reshape in glX context */
437 puglReshape(view, view->width, view->height);
438 }
439
440 static PuglKey
keySymToSpecial(KeySym sym)441 keySymToSpecial(KeySym sym)
442 {
443 switch (sym) {
444 case XK_F1: return PUGL_KEY_F1;
445 case XK_F2: return PUGL_KEY_F2;
446 case XK_F3: return PUGL_KEY_F3;
447 case XK_F4: return PUGL_KEY_F4;
448 case XK_F5: return PUGL_KEY_F5;
449 case XK_F6: return PUGL_KEY_F6;
450 case XK_F7: return PUGL_KEY_F7;
451 case XK_F8: return PUGL_KEY_F8;
452 case XK_F9: return PUGL_KEY_F9;
453 case XK_F10: return PUGL_KEY_F10;
454 case XK_F11: return PUGL_KEY_F11;
455 case XK_F12: return PUGL_KEY_F12;
456 case XK_Left: return PUGL_KEY_LEFT;
457 case XK_Up: return PUGL_KEY_UP;
458 case XK_Right: return PUGL_KEY_RIGHT;
459 case XK_Down: return PUGL_KEY_DOWN;
460 case XK_Page_Up: return PUGL_KEY_PAGE_UP;
461 case XK_Page_Down: return PUGL_KEY_PAGE_DOWN;
462 case XK_Home: return PUGL_KEY_HOME;
463 case XK_End: return PUGL_KEY_END;
464 case XK_Insert: return PUGL_KEY_INSERT;
465 case XK_Shift_L: return PUGL_KEY_SHIFT;
466 case XK_Shift_R: return PUGL_KEY_SHIFT;
467 case XK_Control_L: return PUGL_KEY_CTRL;
468 case XK_Control_R: return PUGL_KEY_CTRL;
469 case XK_Alt_L: return PUGL_KEY_ALT;
470 case XK_Alt_R: return PUGL_KEY_ALT;
471 case XK_Super_L: return PUGL_KEY_SUPER;
472 case XK_Super_R: return PUGL_KEY_SUPER;
473 }
474 return (PuglKey)0;
475 }
476
477 static void
setModifiers(PuglView * view,unsigned xstate,unsigned xtime)478 setModifiers(PuglView* view, unsigned xstate, unsigned xtime)
479 {
480 view->event_timestamp_ms = xtime;
481
482 view->mods = 0;
483 view->mods |= (xstate & ShiftMask) ? PUGL_MOD_SHIFT : 0;
484 view->mods |= (xstate & ControlMask) ? PUGL_MOD_CTRL : 0;
485 view->mods |= (xstate & Mod1Mask) ? PUGL_MOD_ALT : 0;
486 view->mods |= (xstate & Mod4Mask) ? PUGL_MOD_SUPER : 0;
487 }
488
489 static void
dispatchKey(PuglView * view,XEvent * event,bool press)490 dispatchKey(PuglView* view, XEvent* event, bool press)
491 {
492 KeySym sym;
493 char str[5];
494 PuglKey special;
495 const int n = XLookupString(&event->xkey, str, 4, &sym, NULL);
496
497 if (sym == XK_Escape && view->closeFunc && !press && !view->parent) {
498 view->closeFunc(view);
499 view->redisplay = false;
500 return;
501 }
502 if (n == 0 && sym == 0) {
503 goto send_event;
504 return;
505 }
506 if (n > 1) {
507 fprintf(stderr, "warning: Unsupported multi-byte key %X\n", (int)sym);
508 goto send_event;
509 return;
510 }
511
512 special = keySymToSpecial(sym);
513 if (special && view->specialFunc) {
514 if (view->specialFunc(view, press, special) == 0) {
515 return;
516 }
517 } else if (!special && view->keyboardFunc) {
518 if (view->keyboardFunc(view, press, str[0]) == 0) {
519 return;
520 }
521 }
522
523 send_event:
524 if (view->parent != 0) {
525 event->xkey.time = 0; // purposefully set an invalid time, used for feedback detection on bad hosts
526 event->xany.window = view->parent;
527 XSendEvent(view->impl->display, view->parent, False, NoEventMask, event);
528 }
529 }
530
531 PuglStatus
puglProcessEvents(PuglView * view)532 puglProcessEvents(PuglView* view)
533 {
534 int conf_width = -1;
535 int conf_height = -1;
536
537 XEvent event;
538 while (XPending(view->impl->display) > 0) {
539 XNextEvent(view->impl->display, &event);
540
541 #ifndef DGL_FILE_BROWSER_DISABLED
542 if (x_fib_handle_events(view->impl->display, &event)) {
543 const int status = x_fib_status();
544
545 if (status > 0) {
546 char* const filename = x_fib_filename();
547 x_fib_close(view->impl->display);
548 if (view->fileSelectedFunc) {
549 view->fileSelectedFunc(view, filename);
550 }
551 free(filename);
552 } else if (status < 0) {
553 x_fib_close(view->impl->display);
554 if (view->fileSelectedFunc) {
555 view->fileSelectedFunc(view, NULL);
556 }
557 }
558 break;
559 }
560 #endif
561
562 if (event.xany.window != view->impl->win &&
563 (view->parent == 0 || event.xany.window != (Window)view->parent)) {
564 continue;
565 }
566 if ((event.type == KeyPress || event.type == KeyRelease) && event.xkey.time == 0) {
567 continue;
568 }
569
570 switch (event.type) {
571 case UnmapNotify:
572 if (view->motionFunc) {
573 view->motionFunc(view, -1, -1);
574 }
575 break;
576 case MapNotify:
577 puglReshape(view, view->width, view->height);
578 break;
579 case ConfigureNotify:
580 if ((event.xconfigure.width != view->width) ||
581 (event.xconfigure.height != view->height)) {
582 conf_width = event.xconfigure.width;
583 conf_height = event.xconfigure.height;
584 }
585 break;
586 case Expose:
587 if (event.xexpose.count != 0) {
588 break;
589 }
590 view->redisplay = true;
591 break;
592 case MotionNotify:
593 setModifiers(view, event.xmotion.state, event.xmotion.time);
594 if (view->motionFunc) {
595 view->motionFunc(view, event.xmotion.x, event.xmotion.y);
596 }
597 break;
598 case ButtonPress:
599 setModifiers(view, event.xbutton.state, event.xbutton.time);
600 if (event.xbutton.button >= 4 && event.xbutton.button <= 7) {
601 if (view->scrollFunc) {
602 float dx = 0, dy = 0;
603 switch (event.xbutton.button) {
604 case 4: dy = 1.0f; break;
605 case 5: dy = -1.0f; break;
606 case 6: dx = -1.0f; break;
607 case 7: dx = 1.0f; break;
608 }
609 view->scrollFunc(view, event.xbutton.x, event.xbutton.y, dx, dy);
610 }
611 break;
612 }
613 // nobreak
614 case ButtonRelease:
615 setModifiers(view, event.xbutton.state, event.xbutton.time);
616 if (view->mouseFunc &&
617 (event.xbutton.button < 4 || event.xbutton.button > 7)) {
618 view->mouseFunc(view,
619 event.xbutton.button, event.type == ButtonPress,
620 event.xbutton.x, event.xbutton.y);
621 }
622 break;
623 case KeyPress:
624 setModifiers(view, event.xkey.state, event.xkey.time);
625 dispatchKey(view, &event, true);
626 break;
627 case KeyRelease: {
628 setModifiers(view, event.xkey.state, event.xkey.time);
629 bool repeated = false;
630 if (view->ignoreKeyRepeat &&
631 XEventsQueued(view->impl->display, QueuedAfterReading)) {
632 XEvent next;
633 XPeekEvent(view->impl->display, &next);
634 if (next.type == KeyPress &&
635 next.xkey.time == event.xkey.time &&
636 next.xkey.keycode == event.xkey.keycode) {
637 XNextEvent(view->impl->display, &event);
638 repeated = true;
639 }
640 }
641 if (!repeated) {
642 dispatchKey(view, &event, false);
643 }
644 } break;
645 case ClientMessage: {
646 char* type = XGetAtomName(view->impl->display,
647 event.xclient.message_type);
648 if (!strcmp(type, "WM_PROTOCOLS")) {
649 if (view->closeFunc) {
650 view->closeFunc(view);
651 view->redisplay = false;
652 }
653 }
654 XFree(type);
655 } break;
656 #ifdef PUGL_GRAB_FOCUS
657 case EnterNotify:
658 XSetInputFocus(view->impl->display, view->impl->win, RevertToPointerRoot, CurrentTime);
659 break;
660 #endif
661 default:
662 break;
663 }
664 }
665
666 if (conf_width != -1) {
667 #ifdef PUGL_CAIRO
668 // Resize surfaces/contexts before dispatching
669 view->redisplay = true;
670 cairo_xlib_surface_set_size(view->impl->xlib_surface,
671 conf_width, conf_height);
672 #endif
673 puglReshape(view, conf_width, conf_height);
674 }
675
676 if (view->pending_resize) {
677 puglResize(view);
678 }
679
680 if (view->redisplay) {
681 puglDisplay(view);
682 }
683
684 return PUGL_SUCCESS;
685 }
686
687 void
puglPostRedisplay(PuglView * view)688 puglPostRedisplay(PuglView* view)
689 {
690 view->redisplay = true;
691 }
692
693 void
puglPostResize(PuglView * view)694 puglPostResize(PuglView* view)
695 {
696 view->pending_resize = true;
697 }
698
699 PuglNativeWindow
puglGetNativeWindow(PuglView * view)700 puglGetNativeWindow(PuglView* view)
701 {
702 return view->impl->win;
703 }
704
705 void*
puglGetContext(PuglView * view)706 puglGetContext(PuglView* view)
707 {
708 #ifdef PUGL_CAIRO
709 return view->impl->buffer_cr;
710 #endif
711 return NULL;
712
713 // may be unused
714 (void)view;
715 }
716
717 int
puglUpdateGeometryConstraints(PuglView * view,int min_width,int min_height,bool aspect)718 puglUpdateGeometryConstraints(PuglView* view, int min_width, int min_height, bool aspect)
719 {
720 XSizeHints sizeHints;
721 memset(&sizeHints, 0, sizeof(sizeHints));
722 sizeHints.flags = PMinSize|PMaxSize;
723 sizeHints.min_width = min_width;
724 sizeHints.min_height = min_height;
725 sizeHints.max_width = view->user_resizable ? 4096 : min_width;
726 sizeHints.max_height = view->user_resizable ? 4096 : min_height;
727 if (aspect) {
728 sizeHints.flags |= PAspect;
729 sizeHints.min_aspect.x = min_width;
730 sizeHints.min_aspect.y = min_height;
731 sizeHints.max_aspect.x = min_width;
732 sizeHints.max_aspect.y = min_height;
733 }
734 XSetWMNormalHints(view->impl->display, view->impl->win, &sizeHints);
735 return 0;
736 }
737