1 /*
2   Copyright 2012-2014 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 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 
27 #include <X11/Xatom.h>
28 #include <X11/Xlib.h>
29 #include <X11/Xutil.h>
30 #include <X11/keysym.h>
31 
32 #ifdef PUGL_HAVE_GL
33 #include <GL/gl.h>
34 #include <GL/glx.h>
35 #endif
36 
37 #ifdef PUGL_HAVE_CAIRO
38 #include <cairo/cairo.h>
39 #include <cairo/cairo-xlib.h>
40 #endif
41 
42 #include "pugl/pugl_internal.h"
43 
44 #ifndef DGL_FILE_BROWSER_DISABLED
45 #define SOFD_HAVE_X11
46 #include "../sofd/libsofd.h"
47 #include "../sofd/libsofd.c"
48 #endif
49 
50 struct PuglInternalsImpl {
51 	Display*   display;
52 	int        screen;
53 	Window     win;
54 	XIM        xim;
55 	XIC        xic;
56 #ifdef PUGL_HAVE_CAIRO
57 	cairo_t*         cr;
58 	cairo_surface_t* surface;
59 #endif
60 #ifdef PUGL_HAVE_GL
61 	GLXContext ctx;
62 	Bool       doubleBuffered;
63 #endif
64 };
65 
66 PuglInternals*
puglInitInternals(void)67 puglInitInternals(void)
68 {
69 	return (PuglInternals*)calloc(1, sizeof(PuglInternals));
70 }
71 
72 static XVisualInfo*
getVisual(PuglView * view)73 getVisual(PuglView* view)
74 {
75 	PuglInternals* const impl = view->impl;
76 	XVisualInfo*         vi   = NULL;
77 
78 #ifdef PUGL_HAVE_GL
79 	if (view->ctx_type == PUGL_GL) {
80 		/**
81 		  Attributes for single-buffered RGBA with at least
82 		  4 bits per color and a 16 bit depth buffer.
83 		*/
84 		int attrListSgl[] = {
85 			GLX_RGBA,
86 			GLX_RED_SIZE,        4,
87 			GLX_GREEN_SIZE,      4,
88 			GLX_BLUE_SIZE,       4,
89 			GLX_DEPTH_SIZE,      16,
90 			GLX_ARB_multisample, 1,
91 			None
92 		};
93 
94 		/**
95 		  Attributes for double-buffered RGBA with at least
96 		  4 bits per color and a 16 bit depth buffer.
97 		*/
98 		int attrListDbl[] = {
99 			GLX_RGBA,
100 			GLX_DOUBLEBUFFER,
101 			GLX_RED_SIZE,        4,
102 			GLX_GREEN_SIZE,      4,
103 			GLX_BLUE_SIZE,       4,
104 			GLX_DEPTH_SIZE,      16,
105 			GLX_ARB_multisample, 1,
106 			None
107 		};
108 
109 		/**
110 		  Attributes for double-buffered RGBA with multi-sampling
111 		  (antialiasing)
112 		*/
113 		int attrListDblMS[] = {
114 			GLX_RGBA,
115 			GLX_DOUBLEBUFFER,
116 			GLX_RED_SIZE,       4,
117 			GLX_GREEN_SIZE,     4,
118 			GLX_BLUE_SIZE,      4,
119 			GLX_ALPHA_SIZE,     4,
120 			GLX_DEPTH_SIZE,     16,
121 			GLX_SAMPLE_BUFFERS, 1,
122 			GLX_SAMPLES,        4,
123 			None
124 		};
125 
126 		impl->doubleBuffered = True;
127 
128 		vi = glXChooseVisual(impl->display, impl->screen, attrListDblMS);
129 
130 		if (vi == NULL) {
131 			vi = glXChooseVisual(impl->display, impl->screen, attrListDbl);
132 			PUGL_LOG("multisampling (antialiasing) is not available\n");
133 		}
134 
135 		if (vi == NULL) {
136 			vi = glXChooseVisual(impl->display, impl->screen, attrListSgl);
137 			impl->doubleBuffered = False;
138 			PUGL_LOG("singlebuffered rendering will be used, no doublebuffering available\n");
139 		}
140 	}
141 #endif
142 #ifdef PUGL_HAVE_CAIRO
143 	if (view->ctx_type == PUGL_CAIRO) {
144 		XVisualInfo pat;
145 		int         n;
146 		pat.screen = impl->screen;
147 		vi         = XGetVisualInfo(impl->display, VisualScreenMask, &pat, &n);
148 	}
149 #endif
150 
151 	return vi;
152 }
153 
154 static bool
createContext(PuglView * view,XVisualInfo * vi)155 createContext(PuglView* view, XVisualInfo* vi)
156 {
157 	PuglInternals* const impl = view->impl;
158 
159 #ifdef PUGL_HAVE_GL
160 	if (view->ctx_type == PUGL_GL) {
161 		impl->ctx = glXCreateContext(impl->display, vi, 0, GL_TRUE);
162 		return (impl->ctx != NULL);
163 	}
164 #endif
165 #ifdef PUGL_HAVE_CAIRO
166 	if (view->ctx_type == PUGL_CAIRO) {
167 		impl->surface = cairo_xlib_surface_create(
168 			impl->display, impl->win, vi->visual, view->width, view->height);
169 		if (impl->surface == NULL) {
170 			PUGL_LOG("failed to create cairo surface\n");
171 			return false;
172 		}
173 		impl->cr = cairo_create(impl->surface);
174 		if (impl->cr == NULL) {
175 			cairo_surface_destroy(impl->surface);
176 			impl->surface = NULL;
177 			PUGL_LOG("failed to create cairo context\n");
178 			return false;
179 		}
180 		return true;
181 	}
182 #endif
183 
184 	return false;
185 }
186 
187 static void
destroyContext(PuglView * view)188 destroyContext(PuglView* view)
189 {
190 	PuglInternals* const impl = view->impl;
191 
192 #ifdef PUGL_HAVE_GL
193 	if (view->ctx_type == PUGL_GL) {
194 		glXDestroyContext(impl->display, impl->ctx);
195 		impl->ctx = NULL;
196 	}
197 #endif
198 #ifdef PUGL_HAVE_CAIRO
199 	if (view->ctx_type == PUGL_CAIRO) {
200 		cairo_destroy(impl->cr);
201 		impl->cr = NULL;
202 
203 		cairo_surface_destroy(impl->surface);
204 		impl->surface = NULL;
205 	}
206 #endif
207 }
208 
209 void
puglEnterContext(PuglView * view)210 puglEnterContext(PuglView* view)
211 {
212 #ifdef PUGL_HAVE_GL
213 	if (view->ctx_type == PUGL_GL) {
214 		glXMakeCurrent(view->impl->display, view->impl->win, view->impl->ctx);
215 	}
216 #endif
217 }
218 
219 void
puglLeaveContext(PuglView * view,bool flush)220 puglLeaveContext(PuglView* view, bool flush)
221 {
222 #ifdef PUGL_HAVE_GL
223 	if (view->ctx_type == PUGL_GL) {
224 		if (flush) {
225 			glFlush();
226 			if (view->impl->doubleBuffered) {
227 				glXSwapBuffers(view->impl->display, view->impl->win);
228 			}
229 		}
230 		glXMakeCurrent(view->impl->display, None, NULL);
231 	}
232 #endif
233 }
234 
235 int
puglCreateWindow(PuglView * view,const char * title)236 puglCreateWindow(PuglView* view, const char* title)
237 {
238 	PuglInternals* const impl = view->impl;
239 
240 	impl->display = XOpenDisplay(NULL);
241 	impl->screen  = DefaultScreen(impl->display);
242 
243 	XVisualInfo* const vi = getVisual(view);
244 	if (!vi) {
245 		XCloseDisplay(impl->display);
246 		impl->display = NULL;
247 		return 1;
248 	}
249 
250 #ifdef PUGL_HAVE_GL
251 	int glxMajor, glxMinor;
252 	glXQueryVersion(impl->display, &glxMajor, &glxMinor);
253 	PUGL_LOGF("GLX Version %d.%d\n", glxMajor, glxMinor);
254 #endif
255 
256 	Window xParent = view->parent
257 		? (Window)view->parent
258 		: RootWindow(impl->display, impl->screen);
259 
260 	Colormap cmap = XCreateColormap(
261 		impl->display, xParent, vi->visual, AllocNone);
262 
263 	XSetWindowAttributes attr;
264 	memset(&attr, 0, sizeof(XSetWindowAttributes));
265 	attr.background_pixel = BlackPixel(impl->display, impl->screen);
266 	attr.border_pixel     = BlackPixel(impl->display, impl->screen);
267 	attr.colormap         = cmap;
268 	attr.event_mask       = (ExposureMask | StructureNotifyMask |
269 	                         EnterWindowMask | LeaveWindowMask |
270 	                         KeyPressMask | KeyReleaseMask |
271 	                         ButtonPressMask | ButtonReleaseMask |
272 	                         PointerMotionMask | FocusChangeMask);
273 
274 	impl->win = XCreateWindow(
275 		impl->display, xParent,
276 		0, 0, view->width, view->height, 0, vi->depth, InputOutput, vi->visual,
277 		CWBackPixel | CWBorderPixel | CWColormap | CWEventMask, &attr);
278 
279 	if (!createContext(view, vi)) {
280 		XDestroyWindow(impl->display, impl->win);
281 		impl->win = 0;
282 
283 		XCloseDisplay(impl->display);
284 		impl->display = NULL;
285 
286 		return 1;
287 	}
288 
289 	XSizeHints sizeHints;
290 	memset(&sizeHints, 0, sizeof(sizeHints));
291 	if (!view->resizable) {
292 		sizeHints.flags      = PMinSize|PMaxSize;
293 		sizeHints.min_width  = view->width;
294 		sizeHints.min_height = view->height;
295 		sizeHints.max_width  = view->width;
296 		sizeHints.max_height = view->height;
297 		XSetNormalHints(impl->display, impl->win, &sizeHints);
298 	} else if (view->min_width > 0 && view->min_height > 0) {
299 		sizeHints.flags      = PMinSize;
300 		sizeHints.min_width  = view->min_width;
301 		sizeHints.min_height = view->min_height;
302 		XSetNormalHints(impl->display, impl->win, &sizeHints);
303 	}
304 
305 	if (title) {
306 		XStoreName(impl->display, impl->win, title);
307 	}
308 
309 	if (!view->parent) {
310 		Atom wmDelete = XInternAtom(impl->display, "WM_DELETE_WINDOW", True);
311 		XSetWMProtocols(impl->display, impl->win, &wmDelete, 1);
312 	}
313 
314 	if (glXIsDirect(impl->display, impl->ctx)) {
315 		PUGL_LOG("DRI enabled (to disable, set LIBGL_ALWAYS_INDIRECT=1\n");
316 	} else {
317 		PUGL_LOG("No DRI available\n");
318 	}
319 
320 	XFree(vi);
321 
322 	return PUGL_SUCCESS;
323 }
324 
325 void
puglShowWindow(PuglView * view)326 puglShowWindow(PuglView* view)
327 {
328 	XMapRaised(view->impl->display, view->impl->win);
329 }
330 
331 void
puglHideWindow(PuglView * view)332 puglHideWindow(PuglView* view)
333 {
334 	XUnmapWindow(view->impl->display, view->impl->win);
335 }
336 
337 void
puglDestroy(PuglView * view)338 puglDestroy(PuglView* view)
339 {
340 	if (!view) {
341 		return;
342 	}
343 
344 #ifndef DGL_FILE_BROWSER_DISABLED
345 	x_fib_close(view->impl->display);
346 #endif
347 
348 	destroyContext(view);
349 	XDestroyWindow(view->impl->display, view->impl->win);
350 	XCloseDisplay(view->impl->display);
351 	free(view->impl);
352 	free(view);
353 }
354 
355 static void
puglReshape(PuglView * view,int width,int height)356 puglReshape(PuglView* view, int width, int height)
357 {
358 	puglEnterContext(view);
359 
360 	if (view->reshapeFunc) {
361 		view->reshapeFunc(view, width, height);
362 	} else {
363 		puglDefaultReshape(view, width, height);
364 	}
365 
366 	puglLeaveContext(view, false);
367 
368 	view->width  = width;
369 	view->height = height;
370 }
371 
372 static void
puglDisplay(PuglView * view)373 puglDisplay(PuglView* view)
374 {
375 	puglEnterContext(view);
376 
377 	view->redisplay = false;
378 
379 	if (view->displayFunc) {
380 		view->displayFunc(view);
381 	}
382 
383 	puglLeaveContext(view, true);
384 }
385 
386 static PuglKey
keySymToSpecial(KeySym sym)387 keySymToSpecial(KeySym sym)
388 {
389 	switch (sym) {
390 	case XK_F1:        return PUGL_KEY_F1;
391 	case XK_F2:        return PUGL_KEY_F2;
392 	case XK_F3:        return PUGL_KEY_F3;
393 	case XK_F4:        return PUGL_KEY_F4;
394 	case XK_F5:        return PUGL_KEY_F5;
395 	case XK_F6:        return PUGL_KEY_F6;
396 	case XK_F7:        return PUGL_KEY_F7;
397 	case XK_F8:        return PUGL_KEY_F8;
398 	case XK_F9:        return PUGL_KEY_F9;
399 	case XK_F10:       return PUGL_KEY_F10;
400 	case XK_F11:       return PUGL_KEY_F11;
401 	case XK_F12:       return PUGL_KEY_F12;
402 	case XK_Left:      return PUGL_KEY_LEFT;
403 	case XK_Up:        return PUGL_KEY_UP;
404 	case XK_Right:     return PUGL_KEY_RIGHT;
405 	case XK_Down:      return PUGL_KEY_DOWN;
406 	case XK_Page_Up:   return PUGL_KEY_PAGE_UP;
407 	case XK_Page_Down: return PUGL_KEY_PAGE_DOWN;
408 	case XK_Home:      return PUGL_KEY_HOME;
409 	case XK_End:       return PUGL_KEY_END;
410 	case XK_Insert:    return PUGL_KEY_INSERT;
411 	case XK_Shift_L:   return PUGL_KEY_SHIFT;
412 	case XK_Shift_R:   return PUGL_KEY_SHIFT;
413 	case XK_Control_L: return PUGL_KEY_CTRL;
414 	case XK_Control_R: return PUGL_KEY_CTRL;
415 	case XK_Alt_L:     return PUGL_KEY_ALT;
416 	case XK_Alt_R:     return PUGL_KEY_ALT;
417 	case XK_Super_L:   return PUGL_KEY_SUPER;
418 	case XK_Super_R:   return PUGL_KEY_SUPER;
419 	}
420 	return (PuglKey)0;
421 }
422 
423 static void
setModifiers(PuglView * view,unsigned xstate,unsigned xtime)424 setModifiers(PuglView* view, unsigned xstate, unsigned xtime)
425 {
426 	view->event_timestamp_ms = xtime;
427 
428 	view->mods = 0;
429 	view->mods |= (xstate & ShiftMask)   ? PUGL_MOD_SHIFT  : 0;
430 	view->mods |= (xstate & ControlMask) ? PUGL_MOD_CTRL   : 0;
431 	view->mods |= (xstate & Mod1Mask)    ? PUGL_MOD_ALT    : 0;
432 	view->mods |= (xstate & Mod4Mask)    ? PUGL_MOD_SUPER  : 0;
433 }
434 
435 static void
dispatchKey(PuglView * view,XEvent * event,bool press)436 dispatchKey(PuglView* view, XEvent* event, bool press)
437 {
438 	KeySym    sym;
439 	char      str[5];
440 	PuglKey   special;
441 	const int n = XLookupString(&event->xkey, str, 4, &sym, NULL);
442 
443 	if (sym == XK_Escape && view->closeFunc && !press && !view->parent) {
444 		view->closeFunc(view);
445 		view->redisplay = false;
446 		return;
447 	}
448 	if (n == 0 && sym == 0) {
449 		goto send_event;
450 		return;
451 	}
452 	if (n > 1) {
453 		fprintf(stderr, "warning: Unsupported multi-byte key %X\n", (int)sym);
454 		goto send_event;
455 		return;
456 	}
457 
458 	special = keySymToSpecial(sym);
459 	if (special && view->specialFunc) {
460 		if (view->specialFunc(view, press, special) == 0) {
461 			return;
462 		}
463 	} else if (!special && view->keyboardFunc) {
464 		if (view->keyboardFunc(view, press, str[0]) == 0) {
465 			return;
466 		}
467 	}
468 
469 send_event:
470 	if (view->parent != 0) {
471 		event->xkey.time   = 0; // purposefully set an invalid time, used for feedback detection on bad hosts
472 		event->xany.window = view->parent;
473 		XSendEvent(view->impl->display, view->parent, False, NoEventMask, event);
474 	}
475 }
476 
477 PuglStatus
puglProcessEvents(PuglView * view)478 puglProcessEvents(PuglView* view)
479 {
480 	XEvent event;
481 	while (XPending(view->impl->display) > 0) {
482 		XNextEvent(view->impl->display, &event);
483 
484 #ifndef DGL_FILE_BROWSER_DISABLED
485 		if (x_fib_handle_events(view->impl->display, &event)) {
486 			const int status = x_fib_status();
487 
488 			if (status > 0) {
489 				char* const filename = x_fib_filename();
490 				x_fib_close(view->impl->display);
491 				if (view->fileSelectedFunc) {
492 					view->fileSelectedFunc(view, filename);
493 				}
494 				free(filename);
495 			} else if (status < 0) {
496 				x_fib_close(view->impl->display);
497 				if (view->fileSelectedFunc) {
498 					view->fileSelectedFunc(view, NULL);
499 				}
500 			}
501 			break;
502 		}
503 #endif
504 
505 		if (event.xany.window != view->impl->win &&
506 			(view->parent == 0 || event.xany.window != (Window)view->parent)) {
507 			continue;
508 		}
509 		if ((event.type == KeyPress || event.type == KeyRelease) && event.xkey.time == 0) {
510 			continue;
511 		}
512 
513 		switch (event.type) {
514 		case MapNotify:
515 			puglReshape(view, view->width, view->height);
516 			break;
517 		case ConfigureNotify:
518 			if ((event.xconfigure.width != view->width) ||
519 			    (event.xconfigure.height != view->height)) {
520 				puglReshape(view,
521 				            event.xconfigure.width,
522 				            event.xconfigure.height);
523 			}
524 			break;
525 		case Expose:
526 			if (event.xexpose.count != 0) {
527 				break;
528 			}
529 			puglDisplay(view);
530 			break;
531 		case MotionNotify:
532 			setModifiers(view, event.xmotion.state, event.xmotion.time);
533 			if (view->motionFunc) {
534 				view->motionFunc(view, event.xmotion.x, event.xmotion.y);
535 			}
536 			break;
537 		case ButtonPress:
538 			setModifiers(view, event.xbutton.state, event.xbutton.time);
539 			if (event.xbutton.button >= 4 && event.xbutton.button <= 7) {
540 				if (view->scrollFunc) {
541 					float dx = 0, dy = 0;
542 					switch (event.xbutton.button) {
543 					case 4: dy =  1.0f; break;
544 					case 5: dy = -1.0f; break;
545 					case 6: dx = -1.0f; break;
546 					case 7: dx =  1.0f; break;
547 					}
548 					view->scrollFunc(view, event.xbutton.x, event.xbutton.y, dx, dy);
549 				}
550 				break;
551 			}
552 			// nobreak
553 		case ButtonRelease:
554 			setModifiers(view, event.xbutton.state, event.xbutton.time);
555 			if (view->mouseFunc &&
556 			    (event.xbutton.button < 4 || event.xbutton.button > 7)) {
557 				view->mouseFunc(view,
558 				                event.xbutton.button, event.type == ButtonPress,
559 				                event.xbutton.x, event.xbutton.y);
560 			}
561 			break;
562 		case KeyPress:
563 			setModifiers(view, event.xkey.state, event.xkey.time);
564 			dispatchKey(view, &event, true);
565 			break;
566 		case KeyRelease: {
567 			setModifiers(view, event.xkey.state, event.xkey.time);
568 			bool repeated = false;
569 			if (view->ignoreKeyRepeat &&
570 			    XEventsQueued(view->impl->display, QueuedAfterReading)) {
571 				XEvent next;
572 				XPeekEvent(view->impl->display, &next);
573 				if (next.type == KeyPress &&
574 				    next.xkey.time == event.xkey.time &&
575 				    next.xkey.keycode == event.xkey.keycode) {
576 					XNextEvent(view->impl->display, &event);
577 					repeated = true;
578 				}
579 			}
580 			if (!repeated) {
581 				dispatchKey(view, &event, false);
582 			}
583 		}	break;
584 		case ClientMessage: {
585 			char* type = XGetAtomName(view->impl->display,
586 			                          event.xclient.message_type);
587 			if (!strcmp(type, "WM_PROTOCOLS")) {
588 				if (view->closeFunc) {
589 					view->closeFunc(view);
590 					view->redisplay = false;
591 				}
592 			}
593 			XFree(type);
594 		}	break;
595 #ifdef PUGL_GRAB_FOCUS
596 		case EnterNotify:
597 			XSetInputFocus(view->impl->display, view->impl->win, RevertToPointerRoot, CurrentTime);
598 			break;
599 #endif
600 		default:
601 			break;
602 		}
603 	}
604 
605 	if (view->redisplay) {
606 		puglDisplay(view);
607 	}
608 
609 	return PUGL_SUCCESS;
610 }
611 
612 void
puglPostRedisplay(PuglView * view)613 puglPostRedisplay(PuglView* view)
614 {
615 	view->redisplay = true;
616 }
617 
618 PuglNativeWindow
puglGetNativeWindow(PuglView * view)619 puglGetNativeWindow(PuglView* view)
620 {
621 	return view->impl->win;
622 }
623 
624 void*
puglGetContext(PuglView * view)625 puglGetContext(PuglView* view)
626 {
627 #ifdef PUGL_HAVE_CAIRO
628 	if (view->ctx_type == PUGL_CAIRO) {
629 		return view->impl->cr;
630 	}
631 #endif
632 	return NULL;
633 
634 	// may be unused
635 	(void)view;
636 }
637