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