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