1 /*
2   Copyright 2012-2019 David Robillard <http://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 /**
20    @file pugl_x11.c X11 Pugl Implementation.
21 */
22 
23 
24 #include "pugl_internal.h"
25 #include "pugl_x11.h"
26 #ifdef PUGL_HAVE_GL
27 #include "pugl_x11_gl.h"
28 #endif
29 #ifdef PUGL_HAVE_CAIRO
30 #include "pugl_x11_cairo.h"
31 #endif
32 
33 #include <X11/Xatom.h>
34 #include <X11/Xlib.h>
35 #include <X11/Xutil.h>
36 #include <X11/keysym.h>
37 
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 
42 #ifndef MIN
43 #    define MIN(a, b) (((a) < (b)) ? (a) : (b))
44 #endif
45 
46 #ifndef MAX
47 #    define MAX(a, b) (((a) > (b)) ? (a) : (b))
48 #endif
49 
50 PuglInternals*
puglInitInternals(void)51 puglInitInternals(void)
52 {
53 	return (PuglInternals*)calloc(1, sizeof(PuglInternals));
54 }
55 
56 void
puglEnterContext(PuglView * view)57 puglEnterContext(PuglView* view)
58 {
59 	view->impl->ctx.enter(view);
60 }
61 
62 void
puglLeaveContext(PuglView * view,bool flush)63 puglLeaveContext(PuglView* view, bool flush)
64 {
65 	view->impl->ctx.leave(view, flush);
66 }
67 
68 int
puglCreateWindow(PuglView * view,const char * title)69 puglCreateWindow(PuglView* view, const char* title)
70 {
71 	PuglInternals* const impl = view->impl;
72 
73 	impl->display = XOpenDisplay(0);
74 	impl->screen  = DefaultScreen(impl->display);
75 
76 	if (view->ctx_type == PUGL_GL) {
77 #ifdef PUGL_HAVE_GL
78 		impl->ctx = puglGetX11GlDrawContext();
79 #endif
80 	}
81 	if (view->ctx_type == PUGL_CAIRO) {
82 #ifdef PUGL_HAVE_CAIRO
83 		impl->ctx = puglGetX11CairoDrawContext();
84 #endif
85 	}
86 
87 	if (!impl->ctx.configure) {
88 		return 1;
89 	}
90 
91 	if (impl->ctx.configure(view) || !impl->vi) {
92 		impl->ctx.destroy(view);
93 		return 2;
94 	}
95 
96 	Window xParent = view->parent
97 		? (Window)view->parent
98 		: RootWindow(impl->display, impl->screen);
99 
100 	Colormap cmap = XCreateColormap(
101 		impl->display, xParent, impl->vi->visual, AllocNone);
102 
103 	XSetWindowAttributes attr;
104 	memset(&attr, 0, sizeof(XSetWindowAttributes));
105 	attr.colormap         = cmap;
106 	attr.event_mask       = (ExposureMask | StructureNotifyMask |
107 	                         EnterWindowMask | LeaveWindowMask |
108 	                         KeyPressMask | KeyReleaseMask |
109 	                         ButtonPressMask | ButtonReleaseMask |
110 	                         PointerMotionMask | FocusChangeMask);
111 
112 	impl->win = XCreateWindow(
113 		impl->display, xParent,
114 		0, 0, view->width, view->height, 0, impl->vi->depth, InputOutput,
115 		impl->vi->visual, CWColormap | CWEventMask, &attr);
116 
117 	if (impl->ctx.create(view)) {
118 		return 3;
119 	}
120 
121 	XSizeHints sizeHints;
122 	memset(&sizeHints, 0, sizeof(sizeHints));
123 	if (!view->hints.resizable) {
124 		sizeHints.flags      = PMinSize|PMaxSize;
125 		sizeHints.min_width  = view->width;
126 		sizeHints.min_height = view->height;
127 		sizeHints.max_width  = view->width;
128 		sizeHints.max_height = view->height;
129 		XSetNormalHints(impl->display, impl->win, &sizeHints);
130 	} else {
131 		if (view->min_width || view->min_height) {
132 			sizeHints.flags      = PMinSize;
133 			sizeHints.min_width  = view->min_width;
134 			sizeHints.min_height = view->min_height;
135 		}
136 		if (view->min_aspect_x) {
137 			sizeHints.flags        |= PAspect;
138 			sizeHints.min_aspect.x  = view->min_aspect_x;
139 			sizeHints.min_aspect.y  = view->min_aspect_y;
140 			sizeHints.max_aspect.x  = view->max_aspect_x;
141 			sizeHints.max_aspect.y  = view->max_aspect_y;
142 		}
143 
144 		XSetNormalHints(impl->display, impl->win, &sizeHints);
145 	}
146 
147 	if (title) {
148 		XStoreName(impl->display, impl->win, title);
149 	}
150 
151 	if (!view->parent) {
152 		Atom wmDelete = XInternAtom(impl->display, "WM_DELETE_WINDOW", True);
153 		XSetWMProtocols(impl->display, impl->win, &wmDelete, 1);
154 	}
155 
156 	if (view->transient_parent) {
157 		XSetTransientForHint(impl->display, impl->win,
158 		                     (Window)(view->transient_parent));
159 	}
160 
161 	XSetLocaleModifiers("");
162 	if (!(impl->xim = XOpenIM(impl->display, NULL, NULL, NULL))) {
163 		XSetLocaleModifiers("@im=");
164 		if (!(impl->xim = XOpenIM(impl->display, NULL, NULL, NULL))) {
165 			fprintf(stderr, "warning: XOpenIM failed\n");
166 		}
167 	}
168 
169 	const XIMStyle im_style = XIMPreeditNothing | XIMStatusNothing;
170 	if (!(impl->xic = XCreateIC(impl->xim,
171 	                            XNInputStyle,   im_style,
172 	                            XNClientWindow, impl->win,
173 	                            XNFocusWindow,  impl->win,
174 	                            NULL))) {
175 		fprintf(stderr, "warning: XCreateIC failed\n");
176 	}
177 
178 	return 0;
179 }
180 
181 void
puglShowWindow(PuglView * view)182 puglShowWindow(PuglView* view)
183 {
184 	XMapRaised(view->impl->display, view->impl->win);
185 	view->visible = true;
186 }
187 
188 void
puglHideWindow(PuglView * view)189 puglHideWindow(PuglView* view)
190 {
191 	XUnmapWindow(view->impl->display, view->impl->win);
192 	view->visible = false;
193 }
194 
195 void
puglDestroy(PuglView * view)196 puglDestroy(PuglView* view)
197 {
198 	if (view) {
199 		view->impl->ctx.destroy(view);
200 		XDestroyWindow(view->impl->display, view->impl->win);
201 		XCloseDisplay(view->impl->display);
202 		XFree(view->impl->vi);
203 		free(view->windowClass);
204 		free(view->impl);
205 		free(view);
206 	}
207 }
208 
209 static PuglKey
keySymToSpecial(KeySym sym)210 keySymToSpecial(KeySym sym)
211 {
212 	switch (sym) {
213 	case XK_F1:        return PUGL_KEY_F1;
214 	case XK_F2:        return PUGL_KEY_F2;
215 	case XK_F3:        return PUGL_KEY_F3;
216 	case XK_F4:        return PUGL_KEY_F4;
217 	case XK_F5:        return PUGL_KEY_F5;
218 	case XK_F6:        return PUGL_KEY_F6;
219 	case XK_F7:        return PUGL_KEY_F7;
220 	case XK_F8:        return PUGL_KEY_F8;
221 	case XK_F9:        return PUGL_KEY_F9;
222 	case XK_F10:       return PUGL_KEY_F10;
223 	case XK_F11:       return PUGL_KEY_F11;
224 	case XK_F12:       return PUGL_KEY_F12;
225 	case XK_Left:      return PUGL_KEY_LEFT;
226 	case XK_Up:        return PUGL_KEY_UP;
227 	case XK_Right:     return PUGL_KEY_RIGHT;
228 	case XK_Down:      return PUGL_KEY_DOWN;
229 	case XK_Page_Up:   return PUGL_KEY_PAGE_UP;
230 	case XK_Page_Down: return PUGL_KEY_PAGE_DOWN;
231 	case XK_Home:      return PUGL_KEY_HOME;
232 	case XK_End:       return PUGL_KEY_END;
233 	case XK_Insert:    return PUGL_KEY_INSERT;
234 	case XK_Shift_L:   return PUGL_KEY_SHIFT;
235 	case XK_Shift_R:   return PUGL_KEY_SHIFT;
236 	case XK_Control_L: return PUGL_KEY_CTRL;
237 	case XK_Control_R: return PUGL_KEY_CTRL;
238 	case XK_Alt_L:     return PUGL_KEY_ALT;
239 	case XK_Alt_R:     return PUGL_KEY_ALT;
240 	case XK_Super_L:   return PUGL_KEY_SUPER;
241 	case XK_Super_R:   return PUGL_KEY_SUPER;
242 	default:           return (PuglKey)0;
243 	}
244 	return (PuglKey)0;
245 }
246 
247 static void
translateKey(PuglView * view,XEvent * xevent,PuglEvent * event)248 translateKey(PuglView* view, XEvent* xevent, PuglEvent* event)
249 {
250 	KeySym sym = 0;
251 	char*  str = (char*)event->key.utf8;
252 	memset(str, 0, 8);
253 	event->key.filter = XFilterEvent(xevent, None);
254 	if (xevent->type == KeyRelease || event->key.filter || !view->impl->xic) {
255 		if (XLookupString(&xevent->xkey, str, 7, &sym, NULL) == 1) {
256 			event->key.character = str[0];
257 		}
258 	} else {
259 		/* TODO: Not sure about this.  On my system, some characters work with
260 		   Xutf8LookupString but not with XmbLookupString, and some are the
261 		   opposite. */
262 		Status status = 0;
263 #ifdef X_HAVE_UTF8_STRING
264 		const int n = Xutf8LookupString(
265 			view->impl->xic, &xevent->xkey, str, 7, &sym, &status);
266 #else
267 		const int n = XmbLookupString(
268 			view->impl->xic, &xevent->xkey, str, 7, &sym, &status);
269 #endif
270 		if (n > 0) {
271 			event->key.character = puglDecodeUTF8((const uint8_t*)str);
272 		}
273 	}
274 	event->key.special = keySymToSpecial(sym);
275 	event->key.keycode = xevent->xkey.keycode;
276 }
277 
278 static unsigned
translateModifiers(unsigned xstate)279 translateModifiers(unsigned xstate)
280 {
281 	unsigned state = 0;
282 	state |= (xstate & ShiftMask)   ? PUGL_MOD_SHIFT  : 0;
283 	state |= (xstate & ControlMask) ? PUGL_MOD_CTRL   : 0;
284 	state |= (xstate & Mod1Mask)    ? PUGL_MOD_ALT    : 0;
285 	state |= (xstate & Mod4Mask)    ? PUGL_MOD_SUPER  : 0;
286 	return state;
287 }
288 
289 static PuglEvent
translateEvent(PuglView * view,XEvent xevent)290 translateEvent(PuglView* view, XEvent xevent)
291 {
292 	PuglEvent event;
293 	memset(&event, 0, sizeof(event));
294 
295 	event.any.view = view;
296 	if (xevent.xany.send_event) {
297 		event.any.flags |= PUGL_IS_SEND_EVENT;
298 	}
299 
300 	switch (xevent.type) {
301 	case ClientMessage: {
302 		char* type = XGetAtomName(view->impl->display,
303 		                          xevent.xclient.message_type);
304 		if (!strcmp(type, "WM_PROTOCOLS")) {
305 			event.type = PUGL_CLOSE;
306 		}
307 		break;
308 	}
309 	case ConfigureNotify:
310 		event.type             = PUGL_CONFIGURE;
311 		event.configure.x      = xevent.xconfigure.x;
312 		event.configure.y      = xevent.xconfigure.y;
313 		event.configure.width  = xevent.xconfigure.width;
314 		event.configure.height = xevent.xconfigure.height;
315 		break;
316 	case Expose:
317 		event.type          = PUGL_EXPOSE;
318 		event.expose.x      = xevent.xexpose.x;
319 		event.expose.y      = xevent.xexpose.y;
320 		event.expose.width  = xevent.xexpose.width;
321 		event.expose.height = xevent.xexpose.height;
322 		event.expose.count  = xevent.xexpose.count;
323 		break;
324 	case MotionNotify:
325 		event.type           = PUGL_MOTION_NOTIFY;
326 		event.motion.time    = xevent.xmotion.time;
327 		event.motion.x       = xevent.xmotion.x;
328 		event.motion.y       = xevent.xmotion.y;
329 		event.motion.x_root  = xevent.xmotion.x_root;
330 		event.motion.y_root  = xevent.xmotion.y_root;
331 		event.motion.state   = translateModifiers(xevent.xmotion.state);
332 		event.motion.is_hint = (xevent.xmotion.is_hint == NotifyHint);
333 		break;
334 	case ButtonPress:
335 		if (xevent.xbutton.button >= 4 && xevent.xbutton.button <= 7) {
336 			event.type           = PUGL_SCROLL;
337 			event.scroll.time    = xevent.xbutton.time;
338 			event.scroll.x       = xevent.xbutton.x;
339 			event.scroll.y       = xevent.xbutton.y;
340 			event.scroll.x_root  = xevent.xbutton.x_root;
341 			event.scroll.y_root  = xevent.xbutton.y_root;
342 			event.scroll.state   = translateModifiers(xevent.xbutton.state);
343 			event.scroll.dx      = 0.0;
344 			event.scroll.dy      = 0.0;
345 			switch (xevent.xbutton.button) {
346 			case 4: event.scroll.dy =  1.0f; break;
347 			case 5: event.scroll.dy = -1.0f; break;
348 			case 6: event.scroll.dx = -1.0f; break;
349 			case 7: event.scroll.dx =  1.0f; break;
350 			}
351 			// fallthru
352 		}
353 		// fallthru
354 	case ButtonRelease:
355 		if (xevent.xbutton.button < 4 || xevent.xbutton.button > 7) {
356 			event.button.type   = ((xevent.type == ButtonPress)
357 			                       ? PUGL_BUTTON_PRESS
358 			                       : PUGL_BUTTON_RELEASE);
359 			event.button.time   = xevent.xbutton.time;
360 			event.button.x      = xevent.xbutton.x;
361 			event.button.y      = xevent.xbutton.y;
362 			event.button.x_root = xevent.xbutton.x_root;
363 			event.button.y_root = xevent.xbutton.y_root;
364 			event.button.state  = translateModifiers(xevent.xbutton.state);
365 			event.button.button = xevent.xbutton.button;
366 		}
367 		break;
368 	case KeyPress:
369 	case KeyRelease:
370 		event.type       = ((xevent.type == KeyPress)
371 		                    ? PUGL_KEY_PRESS
372 		                    : PUGL_KEY_RELEASE);
373 		event.key.time   = xevent.xkey.time;
374 		event.key.x      = xevent.xkey.x;
375 		event.key.y      = xevent.xkey.y;
376 		event.key.x_root = xevent.xkey.x_root;
377 		event.key.y_root = xevent.xkey.y_root;
378 		event.key.state  = translateModifiers(xevent.xkey.state);
379 		translateKey(view, &xevent, &event);
380 		break;
381 	case EnterNotify:
382 	case LeaveNotify:
383 		event.type            = ((xevent.type == EnterNotify)
384 		                         ? PUGL_ENTER_NOTIFY
385 		                         : PUGL_LEAVE_NOTIFY);
386 		event.crossing.time   = xevent.xcrossing.time;
387 		event.crossing.x      = xevent.xcrossing.x;
388 		event.crossing.y      = xevent.xcrossing.y;
389 		event.crossing.x_root = xevent.xcrossing.x_root;
390 		event.crossing.y_root = xevent.xcrossing.y_root;
391 		event.crossing.state  = translateModifiers(xevent.xcrossing.state);
392 		event.crossing.mode   = PUGL_CROSSING_NORMAL;
393 		if (xevent.xcrossing.mode == NotifyGrab) {
394 			event.crossing.mode = PUGL_CROSSING_GRAB;
395 		} else if (xevent.xcrossing.mode == NotifyUngrab) {
396 			event.crossing.mode = PUGL_CROSSING_UNGRAB;
397 		}
398 		break;
399 
400 	case FocusIn:
401 	case FocusOut:
402 		event.type = ((xevent.type == FocusIn)
403 		              ? PUGL_FOCUS_IN
404 		              : PUGL_FOCUS_OUT);
405 		event.focus.grab = (xevent.xfocus.mode != NotifyNormal);
406 		break;
407 
408 	default:
409 		break;
410 	}
411 
412 	return event;
413 }
414 
415 void
puglGrabFocus(PuglView * view)416 puglGrabFocus(PuglView* view)
417 {
418 	XSetInputFocus(
419 		view->impl->display, view->impl->win, RevertToPointerRoot, CurrentTime);
420 }
421 
422 PuglStatus
puglWaitForEvent(PuglView * view)423 puglWaitForEvent(PuglView* view)
424 {
425 	XEvent xevent;
426 	XPeekEvent(view->impl->display, &xevent);
427 	return PUGL_SUCCESS;
428 }
429 
430 static void
merge_expose_events(PuglEvent * dst,const PuglEvent * src)431 merge_expose_events(PuglEvent* dst, const PuglEvent* src)
432 {
433 	if (!dst->type) {
434 		*dst = *src;
435 	} else {
436 		const double max_x = MAX(dst->expose.x + dst->expose.width,
437 		                         src->expose.x + src->expose.width);
438 		const double max_y = MAX(dst->expose.y + dst->expose.height,
439 		                         src->expose.y + src->expose.height);
440 
441 		dst->expose.x      = MIN(dst->expose.x, src->expose.x);
442 		dst->expose.y      = MIN(dst->expose.y, src->expose.y);
443 		dst->expose.width  = max_x - dst->expose.x;
444 		dst->expose.height = max_y - dst->expose.y;
445 		dst->expose.count  = MIN(dst->expose.count, src->expose.count);
446 	}
447 }
448 
449 PuglStatus
puglProcessEvents(PuglView * view)450 puglProcessEvents(PuglView* view)
451 {
452 	/* Maintain a single expose/configure event to execute after all pending
453 	   events.  This avoids redundant drawing/configuration which prevents a
454 	   series of window resizes in the same loop from being laggy. */
455 	PuglEvent expose_event = {};
456 	PuglEvent config_event = {};
457 	XEvent    xevent;
458 	while (XPending(view->impl->display) > 0) {
459 		XNextEvent(view->impl->display, &xevent);
460 		if (xevent.type == KeyRelease) {
461 			// Ignore key repeat if necessary
462 			if (view->ignoreKeyRepeat &&
463 			    XEventsQueued(view->impl->display, QueuedAfterReading)) {
464 				XEvent next;
465 				XPeekEvent(view->impl->display, &next);
466 				if (next.type == KeyPress &&
467 				    next.xkey.time == xevent.xkey.time &&
468 				    next.xkey.keycode == xevent.xkey.keycode) {
469 					XNextEvent(view->impl->display, &xevent);
470 					continue;
471 				}
472 			}
473 		} else if (xevent.type == FocusIn) {
474 			XSetICFocus(view->impl->xic);
475 		} else if (xevent.type == FocusOut) {
476 			XUnsetICFocus(view->impl->xic);
477 		}
478 
479 		// Translate X11 event to Pugl event
480 		const PuglEvent event = translateEvent(view, xevent);
481 
482 		if (event.type == PUGL_EXPOSE) {
483 			// Expand expose event to be dispatched after loop
484 			merge_expose_events(&expose_event, &event);
485 		} else if (event.type == PUGL_CONFIGURE) {
486 			// Expand configure event to be dispatched after loop
487 			config_event = event;
488 		} else {
489 			// Dispatch event to application immediately
490 			puglDispatchEvent(view, &event);
491 		}
492 	}
493 
494 	if (config_event.type) {
495 		// Resize drawing context before dispatching
496 		view->impl->ctx.resize(view,
497 		                       (int)config_event.configure.width,
498 		                       (int)config_event.configure.height);
499 		puglDispatchEvent(view, (const PuglEvent*)&config_event);
500 	}
501 
502 	if (view->redisplay) {
503 		expose_event.expose.type       = PUGL_EXPOSE;
504 		expose_event.expose.view       = view;
505 		expose_event.expose.x          = 0;
506 		expose_event.expose.y          = 0;
507 		expose_event.expose.width      = view->width;
508 		expose_event.expose.height     = view->height;
509 		view->redisplay                = false;
510 	}
511 
512 	if (expose_event.type) {
513 		puglDispatchEvent(view, (const PuglEvent*)&expose_event);
514 	}
515 
516 	return PUGL_SUCCESS;
517 }
518 
519 void
puglPostRedisplay(PuglView * view)520 puglPostRedisplay(PuglView* view)
521 {
522 	view->redisplay = true;
523 }
524 
525 PuglNativeWindow
puglGetNativeWindow(PuglView * view)526 puglGetNativeWindow(PuglView* view)
527 {
528 	return view->impl->win;
529 }
530 
531 void*
puglGetContext(PuglView * view)532 puglGetContext(PuglView* view)
533 {
534 	return view->impl->ctx.getHandle(view);
535 }
536