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