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