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 	}
278 
279 	if (view->transient_parent > 0) {
280 		XSetTransientForHint(impl->display, impl->win, (Window)view->transient_parent);
281 	}
282 
283 	if (view->parent) {
284 		XMapRaised(impl->display, impl->win);
285 	} else {
286 		Atom wmDelete = XInternAtom(impl->display, "WM_DELETE_WINDOW", True);
287 		XSetWMProtocols(impl->display, impl->win, &wmDelete, 1);
288 	}
289 
290 #ifdef PUGL_VERBOSE
291 #ifdef PUGL_OPENGL
292 	if (glXIsDirect(impl->display, impl->ctx)) {
293 		printf("puGL: DRI enabled (to disable, set LIBGL_ALWAYS_INDIRECT=1\n");
294 	} else {
295 		printf("puGL: No DRI available\n");
296 	}
297 #endif
298 #endif
299 
300 	XFree(vi);
301 	return 0;
302 }
303 
304 void
puglDestroy(PuglView * view)305 puglDestroy(PuglView* view)
306 {
307 	if (!view) {
308 		return;
309 	}
310 
311 	PuglInternals* const impl = view->impl;
312 
313 #ifndef DGL_FILE_BROWSER_DISABLED
314 	x_fib_close(impl->display);
315 #endif
316 
317 #ifdef PUGL_OPENGL
318 	glXDestroyContext(impl->display, impl->ctx);
319 #endif
320 #ifdef PUGL_CAIRO
321 	cairo_destroy(impl->xlib_cr);
322 	cairo_destroy(impl->buffer_cr);
323 	cairo_surface_destroy(impl->xlib_surface);
324 	cairo_surface_destroy(impl->buffer_surface);
325 #endif
326 	XDestroyWindow(impl->display, impl->win);
327 	XCloseDisplay(impl->display);
328 	free(impl);
329 	free(view);
330 }
331 
332 void
puglShowWindow(PuglView * view)333 puglShowWindow(PuglView* view)
334 {
335 	XMapRaised(view->impl->display, view->impl->win);
336 }
337 
338 void
puglHideWindow(PuglView * view)339 puglHideWindow(PuglView* view)
340 {
341 	XUnmapWindow(view->impl->display, view->impl->win);
342 }
343 
344 static void
puglReshape(PuglView * view,int width,int height)345 puglReshape(PuglView* view, int width, int height)
346 {
347 	puglEnterContext(view);
348 
349 	if (view->reshapeFunc) {
350 		view->reshapeFunc(view, width, height);
351 	} else {
352 		puglDefaultReshape(width, height);
353 	}
354 
355 	puglLeaveContext(view, false);
356 
357 	view->width  = width;
358 	view->height = height;
359 }
360 
361 static void
puglDisplay(PuglView * view)362 puglDisplay(PuglView* view)
363 {
364 	PuglInternals* impl = view->impl;
365 
366 	puglEnterContext(view);
367 
368 #ifdef PUGL_CAIRO
369 	cairo_t* bc = impl->buffer_cr;
370 	cairo_surface_t* xs = impl->xlib_surface;
371 	cairo_surface_t* bs = impl->buffer_surface;
372 	int w = cairo_xlib_surface_get_width(xs);
373 	int h = cairo_xlib_surface_get_height(xs);
374 
375 	int bw = bs ? cairo_image_surface_get_width(bs) : -1;
376 	int bh = bs ? cairo_image_surface_get_height(bs) : -1;
377 	if (!bc || bw != w || bh != h) {
378 		cairo_destroy(bc);
379 		cairo_surface_destroy(bs);
380 		bs = cairo_surface_create_similar_image(xs, CAIRO_FORMAT_ARGB32, w, h);
381 		bc = bs ? cairo_create(bs) : NULL;
382 		impl->buffer_cr = bc;
383 		impl->buffer_surface = bs;
384 	}
385 
386 	if (!bc) {
387 		puglLeaveContext(view, false);
388 		return;
389 	}
390 #endif
391 
392 	view->redisplay = false;
393 	if (view->displayFunc) {
394 		view->displayFunc(view);
395 	}
396 
397 #ifdef PUGL_CAIRO
398 	cairo_t* xc = impl->xlib_cr;
399 	cairo_set_source_surface(xc, impl->buffer_surface, 0, 0);
400 	cairo_paint(xc);
401 #endif
402 
403 	puglLeaveContext(view, true);
404 	(void)impl;
405 }
406 
407 static void
puglResize(PuglView * view)408 puglResize(PuglView* view)
409 {
410 	int set_hints = 1;
411 	view->pending_resize = false;
412 	if (!view->resizeFunc) { return; }
413 	/* ask the plugin about the new size */
414 	view->resizeFunc(view, &view->width, &view->height, &set_hints);
415 
416 	if (set_hints) {
417 		XSizeHints sizeHints;
418 		memset(&sizeHints, 0, sizeof(sizeHints));
419 		sizeHints.flags      = PMinSize|PMaxSize;
420 		sizeHints.min_width  = view->width;
421 		sizeHints.min_height = view->height;
422 		sizeHints.max_width  = view->user_resizable ? 4096 : view->width;
423 		sizeHints.max_height = view->user_resizable ? 4096 : view->height;
424 		XSetWMNormalHints(view->impl->display, view->impl->win, &sizeHints);
425 	}
426 	XResizeWindow(view->impl->display, view->impl->win, view->width, view->height);
427 	XFlush(view->impl->display);
428 
429 #ifdef PUGL_VERBOSE
430 	printf("puGL: window resize (%dx%d)\n", view->width, view->height);
431 #endif
432 
433 	/* and call Reshape in glX context */
434 	puglReshape(view, view->width, view->height);
435 }
436 
437 static PuglKey
keySymToSpecial(KeySym sym)438 keySymToSpecial(KeySym sym)
439 {
440 	switch (sym) {
441 	case XK_F1:        return PUGL_KEY_F1;
442 	case XK_F2:        return PUGL_KEY_F2;
443 	case XK_F3:        return PUGL_KEY_F3;
444 	case XK_F4:        return PUGL_KEY_F4;
445 	case XK_F5:        return PUGL_KEY_F5;
446 	case XK_F6:        return PUGL_KEY_F6;
447 	case XK_F7:        return PUGL_KEY_F7;
448 	case XK_F8:        return PUGL_KEY_F8;
449 	case XK_F9:        return PUGL_KEY_F9;
450 	case XK_F10:       return PUGL_KEY_F10;
451 	case XK_F11:       return PUGL_KEY_F11;
452 	case XK_F12:       return PUGL_KEY_F12;
453 	case XK_Left:      return PUGL_KEY_LEFT;
454 	case XK_Up:        return PUGL_KEY_UP;
455 	case XK_Right:     return PUGL_KEY_RIGHT;
456 	case XK_Down:      return PUGL_KEY_DOWN;
457 	case XK_Page_Up:   return PUGL_KEY_PAGE_UP;
458 	case XK_Page_Down: return PUGL_KEY_PAGE_DOWN;
459 	case XK_Home:      return PUGL_KEY_HOME;
460 	case XK_End:       return PUGL_KEY_END;
461 	case XK_Insert:    return PUGL_KEY_INSERT;
462 	case XK_Shift_L:   return PUGL_KEY_SHIFT;
463 	case XK_Shift_R:   return PUGL_KEY_SHIFT;
464 	case XK_Control_L: return PUGL_KEY_CTRL;
465 	case XK_Control_R: return PUGL_KEY_CTRL;
466 	case XK_Alt_L:     return PUGL_KEY_ALT;
467 	case XK_Alt_R:     return PUGL_KEY_ALT;
468 	case XK_Super_L:   return PUGL_KEY_SUPER;
469 	case XK_Super_R:   return PUGL_KEY_SUPER;
470 	}
471 	return (PuglKey)0;
472 }
473 
474 static void
setModifiers(PuglView * view,unsigned xstate,unsigned xtime)475 setModifiers(PuglView* view, unsigned xstate, unsigned xtime)
476 {
477 	view->event_timestamp_ms = xtime;
478 
479 	view->mods = 0;
480 	view->mods |= (xstate & ShiftMask)   ? PUGL_MOD_SHIFT  : 0;
481 	view->mods |= (xstate & ControlMask) ? PUGL_MOD_CTRL   : 0;
482 	view->mods |= (xstate & Mod1Mask)    ? PUGL_MOD_ALT    : 0;
483 	view->mods |= (xstate & Mod4Mask)    ? PUGL_MOD_SUPER  : 0;
484 }
485 
486 static void
dispatchKey(PuglView * view,XEvent * event,bool press)487 dispatchKey(PuglView* view, XEvent* event, bool press)
488 {
489 	KeySym    sym;
490 	char      str[5];
491 	PuglKey   special;
492 	const int n = XLookupString(&event->xkey, str, 4, &sym, NULL);
493 
494 	if (sym == XK_Escape && view->closeFunc && !press && !view->parent) {
495 		view->closeFunc(view);
496 		view->redisplay = false;
497 		return;
498 	}
499 	if (n == 0 && sym == 0) {
500 		goto send_event;
501 		return;
502 	}
503 	if (n > 1) {
504 		fprintf(stderr, "warning: Unsupported multi-byte key %X\n", (int)sym);
505 		goto send_event;
506 		return;
507 	}
508 
509 	special = keySymToSpecial(sym);
510 	if (special && view->specialFunc) {
511 		if (view->specialFunc(view, press, special) == 0) {
512 			return;
513 		}
514 	} else if (!special && view->keyboardFunc) {
515 		if (view->keyboardFunc(view, press, str[0]) == 0) {
516 			return;
517 		}
518 	}
519 
520 send_event:
521 	if (view->parent != 0) {
522 		event->xkey.time   = 0; // purposefully set an invalid time, used for feedback detection on bad hosts
523 		event->xany.window = view->parent;
524 		XSendEvent(view->impl->display, view->parent, False, NoEventMask, event);
525 	}
526 }
527 
528 PuglStatus
puglProcessEvents(PuglView * view)529 puglProcessEvents(PuglView* view)
530 {
531 	int conf_width = -1;
532 	int conf_height = -1;
533 
534 	XEvent event;
535 	while (XPending(view->impl->display) > 0) {
536 		XNextEvent(view->impl->display, &event);
537 
538 #ifndef DGL_FILE_BROWSER_DISABLED
539 		if (x_fib_handle_events(view->impl->display, &event)) {
540 			const int status = x_fib_status();
541 
542 			if (status > 0) {
543 				char* const filename = x_fib_filename();
544 				x_fib_close(view->impl->display);
545 				if (view->fileSelectedFunc) {
546 					view->fileSelectedFunc(view, filename);
547 				}
548 				free(filename);
549 			} else if (status < 0) {
550 				x_fib_close(view->impl->display);
551 				if (view->fileSelectedFunc) {
552 					view->fileSelectedFunc(view, NULL);
553 				}
554 			}
555 			break;
556 		}
557 #endif
558 
559 		if (event.xany.window != view->impl->win &&
560 			(view->parent == 0 || event.xany.window != (Window)view->parent)) {
561 			continue;
562 		}
563 		if ((event.type == KeyPress || event.type == KeyRelease) && event.xkey.time == 0) {
564 			continue;
565 		}
566 
567 		switch (event.type) {
568 		case UnmapNotify:
569 			if (view->motionFunc) {
570 				view->motionFunc(view, -1, -1);
571 			}
572 			break;
573 		case MapNotify:
574 			puglReshape(view, view->width, view->height);
575 			break;
576 		case ConfigureNotify:
577 			if ((event.xconfigure.width != view->width) ||
578 			    (event.xconfigure.height != view->height)) {
579 				conf_width = event.xconfigure.width;
580 				conf_height = event.xconfigure.height;
581 			}
582 			break;
583 		case Expose:
584 			if (event.xexpose.count != 0) {
585 				break;
586 			}
587 			view->redisplay = true;
588 			break;
589 		case MotionNotify:
590 			setModifiers(view, event.xmotion.state, event.xmotion.time);
591 			if (view->motionFunc) {
592 				view->motionFunc(view, event.xmotion.x, event.xmotion.y);
593 			}
594 			break;
595 		case ButtonPress:
596 			setModifiers(view, event.xbutton.state, event.xbutton.time);
597 			if (event.xbutton.button >= 4 && event.xbutton.button <= 7) {
598 				if (view->scrollFunc) {
599 					float dx = 0, dy = 0;
600 					switch (event.xbutton.button) {
601 					case 4: dy =  1.0f; break;
602 					case 5: dy = -1.0f; break;
603 					case 6: dx = -1.0f; break;
604 					case 7: dx =  1.0f; break;
605 					}
606 					view->scrollFunc(view, event.xbutton.x, event.xbutton.y, dx, dy);
607 				}
608 				break;
609 			}
610 			// nobreak
611 		case ButtonRelease:
612 			setModifiers(view, event.xbutton.state, event.xbutton.time);
613 			if (view->mouseFunc &&
614 			    (event.xbutton.button < 4 || event.xbutton.button > 7)) {
615 				view->mouseFunc(view,
616 				                event.xbutton.button, event.type == ButtonPress,
617 				                event.xbutton.x, event.xbutton.y);
618 			}
619 			break;
620 		case KeyPress:
621 			setModifiers(view, event.xkey.state, event.xkey.time);
622 			dispatchKey(view, &event, true);
623 			break;
624 		case KeyRelease: {
625 			setModifiers(view, event.xkey.state, event.xkey.time);
626 			bool repeated = false;
627 			if (view->ignoreKeyRepeat &&
628 			    XEventsQueued(view->impl->display, QueuedAfterReading)) {
629 				XEvent next;
630 				XPeekEvent(view->impl->display, &next);
631 				if (next.type == KeyPress &&
632 				    next.xkey.time == event.xkey.time &&
633 				    next.xkey.keycode == event.xkey.keycode) {
634 					XNextEvent(view->impl->display, &event);
635 					repeated = true;
636 				}
637 			}
638 			if (!repeated) {
639 				dispatchKey(view, &event, false);
640 			}
641 		}	break;
642 		case ClientMessage: {
643 			char* type = XGetAtomName(view->impl->display,
644 			                          event.xclient.message_type);
645 			if (!strcmp(type, "WM_PROTOCOLS")) {
646 				if (view->closeFunc) {
647 					view->closeFunc(view);
648 					view->redisplay = false;
649 				}
650 			}
651 			XFree(type);
652 		}	break;
653 #ifdef PUGL_GRAB_FOCUS
654 		case EnterNotify:
655 			XSetInputFocus(view->impl->display, view->impl->win, RevertToPointerRoot, CurrentTime);
656 			break;
657 #endif
658 		default:
659 			break;
660 		}
661 	}
662 
663 	if (conf_width != -1) {
664 #ifdef PUGL_CAIRO
665 		// Resize surfaces/contexts before dispatching
666 		view->redisplay = true;
667 		cairo_xlib_surface_set_size(view->impl->xlib_surface,
668 		                            conf_width, conf_height);
669 #endif
670 		puglReshape(view, conf_width, conf_height);
671 	}
672 
673 	if (view->pending_resize) {
674 		puglResize(view);
675 	}
676 
677 	if (view->redisplay) {
678 		puglDisplay(view);
679 	}
680 
681 	return PUGL_SUCCESS;
682 }
683 
684 void
puglPostRedisplay(PuglView * view)685 puglPostRedisplay(PuglView* view)
686 {
687 	view->redisplay = true;
688 }
689 
690 void
puglPostResize(PuglView * view)691 puglPostResize(PuglView* view)
692 {
693 	view->pending_resize = true;
694 }
695 
696 PuglNativeWindow
puglGetNativeWindow(PuglView * view)697 puglGetNativeWindow(PuglView* view)
698 {
699 	return view->impl->win;
700 }
701 
702 void*
puglGetContext(PuglView * view)703 puglGetContext(PuglView* view)
704 {
705 #ifdef PUGL_CAIRO
706 	return view->impl->buffer_cr;
707 #endif
708 	return NULL;
709 
710 	// may be unused
711 	(void)view;
712 }
713 
714 int
puglUpdateGeometryConstraints(PuglView * view,int min_width,int min_height,bool aspect)715 puglUpdateGeometryConstraints(PuglView* view, int min_width, int min_height, bool aspect)
716 {
717 	XSizeHints sizeHints;
718 	memset(&sizeHints, 0, sizeof(sizeHints));
719 	sizeHints.flags      = PMinSize|PMaxSize;
720 	sizeHints.min_width  = min_width;
721 	sizeHints.min_height = min_height;
722 	sizeHints.max_width  = view->user_resizable ? 4096 : min_width;
723 	sizeHints.max_height = view->user_resizable ? 4096 : min_height;
724 	if (aspect) {
725 		sizeHints.flags |= PAspect;
726 		sizeHints.min_aspect.x = min_width;
727 		sizeHints.min_aspect.y = min_height;
728 		sizeHints.max_aspect.x = min_width;
729 		sizeHints.max_aspect.y = min_height;
730 	}
731 	XSetWMNormalHints(view->impl->display, view->impl->win, &sizeHints);
732 	return 0;
733 }
734