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