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 	Window xParent = parent
165 		? (Window)parent
166 		: RootWindow(impl->display, impl->screen);
167 
168 	Colormap cmap = XCreateColormap(
169 		impl->display, xParent, vi->visual, AllocNone);
170 
171 	XSetWindowAttributes attr;
172 	memset(&attr, 0, sizeof(XSetWindowAttributes));
173 	attr.colormap     = cmap;
174 	attr.border_pixel = 0;
175 
176 	attr.event_mask = ExposureMask | KeyPressMask | KeyReleaseMask
177 		| ButtonPressMask | ButtonReleaseMask
178 #ifdef XKEYFOCUSGRAB
179 		| EnterWindowMask
180 #endif
181 		| PointerMotionMask | StructureNotifyMask;
182 
183 	impl->win = XCreateWindow(
184 		impl->display, xParent,
185 		0, 0, view->width, view->height, 0, vi->depth, InputOutput, vi->visual,
186 		CWBorderPixel | CWColormap | CWEventMask, &attr);
187 
188 	XSizeHints sizeHints;
189 	memset(&sizeHints, 0, sizeof(sizeHints));
190 	if (view->set_window_hints) {
191 		sizeHints.flags      = PMinSize|PMaxSize;
192 		sizeHints.min_width  = min_width;
193 		sizeHints.min_height = min_height;
194 		sizeHints.max_width  = resizable ? 2048 : width;
195 		sizeHints.max_height = resizable ? 2048 : height;
196 		if (min_width != width) {
197 			sizeHints.flags |= PAspect;
198 			sizeHints.min_aspect.x=min_width;
199 			sizeHints.min_aspect.y=min_height;
200 			sizeHints.max_aspect.x=min_width;
201 			sizeHints.max_aspect.y=min_height;
202 		}
203 		XSetNormalHints(impl->display, impl->win, &sizeHints);
204 	}
205 	XResizeWindow(view->impl->display, view->impl->win, width, height);
206 
207 	if (title) {
208 		XStoreName(impl->display, impl->win, title);
209 	}
210 
211 	if (!parent) {
212 		Atom wmDelete = XInternAtom(impl->display, "WM_DELETE_WINDOW", True);
213 		XSetWMProtocols(impl->display, impl->win, &wmDelete, 1);
214 	}
215 
216 	if (!parent && view->ontop) { /* TODO stay on top  */
217 		Atom type = XInternAtom(impl->display, "_NET_WM_STATE_ABOVE", False);
218 		XChangeProperty(impl->display, impl->win,
219 				XInternAtom(impl->display, "_NET_WM_STATE", False),
220 				XInternAtom(impl->display, "ATOM", False),
221 				32, PropModeReplace, (unsigned char *)&type, 1);
222 	}
223 
224 	if (transientId > 0) {
225 		XSetTransientForHint(impl->display, impl->win, (Window)(transientId));
226 	}
227 
228 	if (parent) {
229 		XMapRaised(impl->display, impl->win);
230 	}
231 
232 	if (glXIsDirect(impl->display, impl->ctx)) {
233 #ifdef VERBOSE_PUGL
234 		printf("puGL: DRI enabled\n");
235 #endif
236 	} else {
237 #ifdef VERBOSE_PUGL
238 		printf("puGL: No DRI available\n");
239 #endif
240 	}
241 
242 	XFree(vi);
243 	return view;
244 }
245 
246 void
puglDestroy(PuglView * view)247 puglDestroy(PuglView* view)
248 {
249 	if (!view) {
250 		return;
251 	}
252 #ifdef WITH_SOFD
253 	x_fib_close(view->impl->display);
254 #endif
255 
256 	glXDestroyContext(view->impl->display, view->impl->ctx);
257 	XDestroyWindow(view->impl->display, view->impl->win);
258 	XCloseDisplay(view->impl->display);
259 	free(view->impl);
260 	free(view);
261 	view = NULL;
262 }
263 
264 PUGL_API void
puglShowWindow(PuglView * view)265 puglShowWindow(PuglView* view) {
266 	XMapRaised(view->impl->display, view->impl->win);
267 }
268 
269 PUGL_API void
puglHideWindow(PuglView * view)270 puglHideWindow(PuglView* view) {
271 	XUnmapWindow(view->impl->display, view->impl->win);
272 }
273 
274 static void
puglReshape(PuglView * view,int width,int height)275 puglReshape(PuglView* view, int width, int height)
276 {
277 	glXMakeCurrent(view->impl->display, view->impl->win, view->impl->ctx);
278 
279 	if (view->reshapeFunc) {
280 		view->reshapeFunc(view, width, height);
281 	} else {
282 		puglDefaultReshape(view, width, height);
283 	}
284 
285 	view->width  = width;
286 	view->height = height;
287 }
288 
289 static void
puglDisplay(PuglView * view)290 puglDisplay(PuglView* view)
291 {
292 	glXMakeCurrent(view->impl->display, view->impl->win, view->impl->ctx);
293 #if 0
294 	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
295 	glLoadIdentity();
296 #endif
297 
298 	view->redisplay = false;
299 	if (view->displayFunc) {
300 		view->displayFunc(view);
301 	}
302 
303 	glFlush();
304 	if (view->impl->doubleBuffered) {
305 		glXSwapBuffers(view->impl->display, view->impl->win);
306 	}
307 }
308 
309 static void
puglResize(PuglView * view)310 puglResize(PuglView* view)
311 {
312 	int set_hints = 1;
313 	view->resize = false;
314 	if (!view->resizeFunc) { return; }
315 	/* ask the plugin about the new size */
316 	view->resizeFunc(view, &view->width, &view->height, &set_hints);
317 
318 	XSizeHints *hints = XAllocSizeHints();
319 	hints->min_width = view->width;
320 	hints->min_height = view->height;
321 	hints->max_width = view->user_resizable ? 2048 : view->width;
322 	hints->max_height = view->user_resizable ? 2048 : view->height;
323 	hints->flags = PMaxSize | PMinSize;
324 
325 	if (set_hints) {
326 		XSetWMNormalHints(view->impl->display, view->impl->win, hints);
327 	}
328 	XResizeWindow(view->impl->display, view->impl->win, view->width, view->height);
329 	XFlush(view->impl->display);
330 	XFree(hints);
331 
332 #ifdef VERBOSE_PUGL
333 	printf("puGL: window resize (%dx%d)\n", view->width, view->height);
334 #endif
335 
336 	/* and call Reshape in glX context */
337 	puglReshape(view, view->width, view->height);
338 }
339 
340 static PuglKey
keySymToSpecial(KeySym sym)341 keySymToSpecial(KeySym sym)
342 {
343 	switch (sym) {
344 	case XK_F1:        return PUGL_KEY_F1;
345 	case XK_F2:        return PUGL_KEY_F2;
346 	case XK_F3:        return PUGL_KEY_F3;
347 	case XK_F4:        return PUGL_KEY_F4;
348 	case XK_F5:        return PUGL_KEY_F5;
349 	case XK_F6:        return PUGL_KEY_F6;
350 	case XK_F7:        return PUGL_KEY_F7;
351 	case XK_F8:        return PUGL_KEY_F8;
352 	case XK_F9:        return PUGL_KEY_F9;
353 	case XK_F10:       return PUGL_KEY_F10;
354 	case XK_F11:       return PUGL_KEY_F11;
355 	case XK_F12:       return PUGL_KEY_F12;
356 	case XK_Left:      return PUGL_KEY_LEFT;
357 	case XK_Up:        return PUGL_KEY_UP;
358 	case XK_Right:     return PUGL_KEY_RIGHT;
359 	case XK_Down:      return PUGL_KEY_DOWN;
360 	case XK_Page_Up:   return PUGL_KEY_PAGE_UP;
361 	case XK_Page_Down: return PUGL_KEY_PAGE_DOWN;
362 	case XK_Home:      return PUGL_KEY_HOME;
363 	case XK_End:       return PUGL_KEY_END;
364 	case XK_Insert:    return PUGL_KEY_INSERT;
365 	case XK_Shift_L:   return PUGL_KEY_SHIFT;
366 	case XK_Shift_R:   return PUGL_KEY_SHIFT;
367 	case XK_Control_L: return PUGL_KEY_CTRL;
368 	case XK_Control_R: return PUGL_KEY_CTRL;
369 	case XK_Alt_L:     return PUGL_KEY_ALT;
370 	case XK_Alt_R:     return PUGL_KEY_ALT;
371 	case XK_Super_L:   return PUGL_KEY_SUPER;
372 	case XK_Super_R:   return PUGL_KEY_SUPER;
373 	}
374 	return (PuglKey)0;
375 }
376 
377 static void
setModifiers(PuglView * view,unsigned xstate,unsigned xtime)378 setModifiers(PuglView* view, unsigned xstate, unsigned xtime)
379 {
380 	view->event_timestamp_ms = xtime;
381 
382 	view->mods = 0;
383 	view->mods |= (xstate & ShiftMask)   ? PUGL_MOD_SHIFT  : 0;
384 	view->mods |= (xstate & ControlMask) ? PUGL_MOD_CTRL   : 0;
385 	view->mods |= (xstate & Mod1Mask)    ? PUGL_MOD_ALT    : 0;
386 	view->mods |= (xstate & Mod4Mask)    ? PUGL_MOD_SUPER  : 0;
387 }
388 
389 Window x_fib_window();
390 
391 PuglStatus
puglProcessEvents(PuglView * view)392 puglProcessEvents(PuglView* view)
393 {
394 	XEvent event;
395 	while (XPending(view->impl->display) > 0) {
396 		XNextEvent(view->impl->display, &event);
397 
398 #ifdef WITH_SOFD
399 		if (x_fib_handle_events(view->impl->display, &event)) {
400 			const int status = x_fib_status();
401 
402 			if (status > 0) {
403 				char* const filename = x_fib_filename();
404 				x_fib_close(view->impl->display);
405 				x_fib_add_recent (filename, time(NULL));
406 				//x_fib_save_recent ("~/.robtk.recent");
407 				if (view->fileSelectedFunc) {
408 					view->fileSelectedFunc(view, filename);
409 				}
410 				free(filename);
411 				x_fib_free_recent ();
412 			} else if (status < 0) {
413 				x_fib_close(view->impl->display);
414 				if (view->fileSelectedFunc) {
415 					view->fileSelectedFunc(view, NULL);
416 				}
417 			}
418 		}
419 #endif
420 
421 		if (event.xany.window != view->impl->win) {
422 			continue;
423 		}
424 
425 		switch (event.type) {
426 		case MapNotify:
427 			puglReshape(view, view->width, view->height);
428 			break;
429 		case ConfigureNotify:
430 			if ((event.xconfigure.width != view->width) ||
431 			    (event.xconfigure.height != view->height)) {
432 				puglReshape(view,
433 				            event.xconfigure.width,
434 				            event.xconfigure.height);
435 			}
436 			break;
437 		case Expose:
438 			if (event.xexpose.count != 0) {
439 				break;
440 			}
441 			puglDisplay(view);
442 			break;
443 		case MotionNotify:
444 			setModifiers(view, event.xmotion.state, event.xmotion.time);
445 			if (view->motionFunc) {
446 				view->motionFunc(view, event.xmotion.x, event.xmotion.y);
447 			}
448 			break;
449 		case ButtonPress:
450 			setModifiers(view, event.xbutton.state, event.xbutton.time);
451 			if (event.xbutton.button >= 4 && event.xbutton.button <= 7) {
452 				if (view->scrollFunc) {
453 					float dx = 0, dy = 0;
454 					switch (event.xbutton.button) {
455 					case 4: dy =  1.0f; break;
456 					case 5: dy = -1.0f; break;
457 					case 6: dx = -1.0f; break;
458 					case 7: dx =  1.0f; break;
459 					}
460 					view->scrollFunc(view, event.xbutton.x, event.xbutton.y, dx, dy);
461 				}
462 				break;
463 			}
464 			// nobreak
465 		case ButtonRelease:
466 			setModifiers(view, event.xbutton.state, event.xbutton.time);
467 			if (view->mouseFunc &&
468 			    (event.xbutton.button < 4 || event.xbutton.button > 7)) {
469 				view->mouseFunc(view,
470 				                event.xbutton.button, event.type == ButtonPress,
471 				                event.xbutton.x, event.xbutton.y);
472 			}
473 			break;
474 		case KeyPress: {
475 			setModifiers(view, event.xkey.state, event.xkey.time);
476 			KeySym  sym;
477 			char    str[5];
478 			int     n   = XLookupString(&event.xkey, str, 4, &sym, NULL);
479 			PuglKey key = keySymToSpecial(sym);
480 			if (!key && view->keyboardFunc) {
481 				if (n == 1) {
482 					view->keyboardFunc(view, true, str[0]);
483 				} else {
484 					fprintf(stderr, "warning: Unknown key %X\n", (int)sym);
485 				}
486 			} else if (view->specialFunc) {
487 				view->specialFunc(view, true, key);
488 			}
489 		} break;
490 		case KeyRelease: {
491 			setModifiers(view, event.xkey.state, event.xkey.time);
492 			bool repeated = false;
493 			if (view->ignoreKeyRepeat &&
494 			    XEventsQueued(view->impl->display, QueuedAfterReading)) {
495 				XEvent next;
496 				XPeekEvent(view->impl->display, &next);
497 				if (next.type == KeyPress &&
498 				    next.xkey.time == event.xkey.time &&
499 				    next.xkey.keycode == event.xkey.keycode) {
500 					XNextEvent(view->impl->display, &event);
501 					repeated = true;
502 				}
503 			}
504 
505 			if (!repeated) {
506 				KeySym sym = XLookupKeysym(&event.xkey, 0);
507 				PuglKey special = keySymToSpecial(sym);
508 #if 1 // close on 'Esc'
509 				if (sym == XK_Escape && view->closeFunc) {
510 					view->closeFunc(view);
511 					view->redisplay = false;
512 				} else
513 #endif
514 				if (view->keyboardFunc) {
515 					if (!special) {
516 						view->keyboardFunc(view, false, sym);
517 					} else if (view->specialFunc) {
518 						view->specialFunc(view, false, special);
519 					}
520 				}
521 			}
522 		} break;
523 		case ClientMessage: {
524 			char* type = XGetAtomName(view->impl->display,
525 			                          event.xclient.message_type);
526 			if (!strcmp(type, "WM_PROTOCOLS")) {
527 				if (view->closeFunc) {
528 					view->closeFunc(view);
529 					view->redisplay = false;
530 				}
531 			}
532 			XFree(type);
533 		} break;
534 #ifdef XKEYFOCUSGRAB
535 		case EnterNotify:
536 			XSetInputFocus(view->impl->display, view->impl->win, RevertToPointerRoot, CurrentTime);
537 			break;
538 #endif
539 		default:
540 			break;
541 		}
542 	}
543 
544 	if (view->resize) {
545 		puglResize(view);
546 	}
547 
548 	if (view->redisplay) {
549 		puglDisplay(view);
550 	}
551 
552 	return PUGL_SUCCESS;
553 }
554 
555 void
puglPostRedisplay(PuglView * view)556 puglPostRedisplay(PuglView* view)
557 {
558 	view->redisplay = true;
559 }
560 
561 void
puglPostResize(PuglView * view)562 puglPostResize(PuglView* view)
563 {
564 	view->resize = true;
565 }
566 
567 PuglNativeWindow
puglGetNativeWindow(PuglView * view)568 puglGetNativeWindow(PuglView* view)
569 {
570 	return view->impl->win;
571 }
572 
573 int
puglOpenFileDialog(PuglView * view,const char * title)574 puglOpenFileDialog(PuglView* view, const char *title)
575 {
576 #ifdef WITH_SOFD
577 	//x_fib_cfg_filter_callback (fib_filter_movie_filename);
578 	if (x_fib_configure (1, title)) {
579 		return -1;
580 	}
581 	//x_fib_load_recent ("~/.robtk.recent");
582 	if (x_fib_show (view->impl->display, view->impl->win, 300, 300)) {
583 		return -1;
584 	}
585 	return 0;
586 #else
587 	return -1;
588 #endif
589 }
590