1 /*
2 Copyright 2012-2014 David Robillard <http://drobilla.net>
3 Copyright 2014 Harry van Haaren <harryhaaren@gmail.com>
4 Copyright 2013 Robin Gareus <robin@gareus.org>
5 Copyright 2011-2012 Ben Loftis, Harrison Consoles
6
7 Permission to use, copy, modify, and/or distribute this software for any
8 purpose with or without fee is hereby granted, provided that the above
9 copyright notice and this permission notice appear in all copies.
10
11 THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 */
19
20 /**
21 @file pugl_x11.c X11 Pugl Implementation.
22 */
23
24 #include <stdio.h>
25 #include <stdint.h>
26 #include <stdlib.h>
27 #include <string.h>
28
29 #include <X11/Xatom.h>
30 #include <X11/Xlib.h>
31 #include <X11/Xutil.h>
32 #include <X11/keysym.h>
33
34 #ifdef PUGL_HAVE_GL
35 #include <GL/gl.h>
36 #include <GL/glx.h>
37 #endif
38
39 #ifdef PUGL_HAVE_CAIRO
40 #include <cairo/cairo.h>
41 #include <cairo/cairo-xlib.h>
42 #endif
43
44 #include "pugl/event.h"
45 #include "pugl/pugl_internal.h"
46
47 struct PuglInternalsImpl {
48 Display* display;
49 int screen;
50 Window win;
51 #ifdef PUGL_HAVE_CAIRO
52 cairo_t* cr;
53 cairo_t* crBackBuffer;
54 cairo_surface_t* surface;
55 cairo_surface_t* surfaceBackBuffer;
56 #endif
57 #ifdef PUGL_HAVE_GL
58 GLXContext ctx;
59 Bool doubleBuffered;
60 #endif
61 };
62
63 PuglInternals*
puglInitInternals()64 puglInitInternals()
65 {
66 return (PuglInternals*)calloc(1, sizeof(PuglInternals));
67 }
68
69 static XVisualInfo*
getVisual(PuglView * view)70 getVisual(PuglView* view)
71 {
72 PuglInternals* const impl = view->impl;
73 XVisualInfo* vi = NULL;
74
75 #ifdef PUGL_HAVE_GL
76 if (view->ctx_type == PUGL_GL) {
77 // Try to create double-buffered visual
78 int double_attrs[] = { 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 None
84 };
85 vi = glXChooseVisual(impl->display, impl->screen, double_attrs);
86 if (!vi) {
87 // Failed, create single-buffered visual
88 int single_attrs[] = { GLX_RGBA,
89 GLX_RED_SIZE, 4,
90 GLX_GREEN_SIZE, 4,
91 GLX_BLUE_SIZE, 4,
92 GLX_DEPTH_SIZE, 16,
93 None
94 };
95 vi = glXChooseVisual(impl->display, impl->screen, single_attrs);
96 impl->doubleBuffered = False;
97 } else {
98 impl->doubleBuffered = True;
99 }
100 }
101 #endif
102 #ifdef PUGL_HAVE_CAIRO
103 if (view->ctx_type == PUGL_CAIRO) {
104 XVisualInfo pat;
105 int n;
106 pat.screen = impl->screen;
107 vi = XGetVisualInfo(impl->display, VisualScreenMask, &pat, &n);
108 }
109 #endif
110
111 return vi;
112 }
113
114 static void
createContext(PuglView * view,XVisualInfo * vi)115 createContext(PuglView* view, XVisualInfo* vi)
116 {
117 PuglInternals* const impl = view->impl;
118
119 #ifdef PUGL_HAVE_GL
120 if (view->ctx_type == PUGL_GL) {
121 impl->ctx = glXCreateContext(impl->display, vi, 0, GL_TRUE);
122 }
123 #endif
124
125 #ifdef PUGL_HAVE_CAIRO
126 if (view->ctx_type == PUGL_CAIRO) {
127 impl->surface = cairo_xlib_surface_create(
128 impl->display, impl->win, vi->visual, view->width, view->height);
129 if( !(impl->cr = cairo_create( impl->surface )) ) {
130 fprintf(stderr, "failed to create cairo context\n");
131 }
132 impl->surfaceBackBuffer = cairo_surface_create_similar(
133 impl->surface, CAIRO_CONTENT_COLOR_ALPHA, view->width, view->height );
134 if (!impl->surfaceBackBuffer) {
135 fprintf(stderr, "failed to create cairo back buffer surface\n");
136 }
137 if (!(impl->crBackBuffer = cairo_create(impl->surfaceBackBuffer))) {
138 fprintf(stderr, "failed to create cairo back buffer context\n");
139 }
140
141 // request a redisplay to draw backbuffer to shown buffer
142 puglPostRedisplay( view );
143 }
144 #endif
145 }
146
147 static void
destroyContext(PuglView * view)148 destroyContext(PuglView* view)
149 {
150 #ifdef PUGL_HAVE_GL
151 if (view->ctx_type == PUGL_GL) {
152 glXDestroyContext(view->impl->display, view->impl->ctx);
153 }
154 #endif
155 #ifdef PUGL_HAVE_CAIRO
156 if (view->ctx_type == PUGL_CAIRO) {
157 cairo_destroy( view->impl->cr );
158 cairo_destroy( view->impl->crBackBuffer );
159 cairo_surface_destroy( view->impl->surface );
160 cairo_surface_destroy( view->impl->surfaceBackBuffer );
161 }
162 #endif
163 }
164
165 void
puglEnterContext(PuglView * view)166 puglEnterContext(PuglView* view)
167 {
168 #ifdef PUGL_HAVE_GL
169 if (view->ctx_type == PUGL_GL) {
170 glXMakeCurrent(view->impl->display, view->impl->win, view->impl->ctx);
171 }
172 #endif
173 }
174
175 void
puglLeaveContext(PuglView * view,bool flush)176 puglLeaveContext(PuglView* view, bool flush)
177 {
178 #ifdef PUGL_HAVE_GL
179 if (view->ctx_type == PUGL_GL && flush) {
180 glFlush();
181 if (view->impl->doubleBuffered) {
182 glXSwapBuffers(view->impl->display, view->impl->win);
183 }
184 }
185 #endif
186 }
187
188 int
puglCreateWindow(PuglView * view,const char * title)189 puglCreateWindow(PuglView* view, const char* title)
190 {
191 PuglInternals* const impl = view->impl;
192
193 impl->display = XOpenDisplay(0);
194 impl->screen = DefaultScreen(impl->display);
195
196 XVisualInfo* const vi = getVisual(view);
197 if (!vi) {
198 return 1;
199 }
200
201 Window xParent = view->parent
202 ? (Window)view->parent
203 : RootWindow(impl->display, impl->screen);
204
205 Colormap cmap = XCreateColormap(
206 impl->display, xParent, vi->visual, AllocNone);
207
208 XSetWindowAttributes attr;
209 memset(&attr, 0, sizeof(XSetWindowAttributes));
210 attr.background_pixel = BlackPixel(impl->display, impl->screen);
211 attr.border_pixel = BlackPixel(impl->display, impl->screen);
212 attr.colormap = cmap;
213 attr.event_mask = (ExposureMask | StructureNotifyMask |
214 EnterWindowMask | LeaveWindowMask |
215 KeyPressMask | KeyReleaseMask |
216 ButtonPressMask | ButtonReleaseMask |
217 PointerMotionMask);
218
219 impl->win = XCreateWindow(
220 impl->display, xParent,
221 0, 0, view->width, view->height, 0, vi->depth, InputOutput, vi->visual,
222 CWBackPixel | CWBorderPixel | CWColormap | CWEventMask, &attr);
223
224 createContext(view, vi);
225
226 XSizeHints sizeHints;
227 memset(&sizeHints, 0, sizeof(sizeHints));
228 if (!view->resizable) {
229 sizeHints.flags = PMinSize|PMaxSize;
230 sizeHints.min_width = view->width;
231 sizeHints.min_height = view->height;
232 sizeHints.max_width = view->width;
233 sizeHints.max_height = view->height;
234 XSetNormalHints(impl->display, impl->win, &sizeHints);
235 }
236
237 if (title) {
238 XStoreName(impl->display, impl->win, title);
239 }
240
241 if (!view->parent) {
242 Atom wmDelete = XInternAtom(impl->display, "WM_DELETE_WINDOW", True);
243 XSetWMProtocols(impl->display, impl->win, &wmDelete, 1);
244 }
245
246 XFree(vi);
247
248 return 0;
249 }
250
251 void
puglShowWindow(PuglView * view)252 puglShowWindow(PuglView* view)
253 {
254 XMapRaised(view->impl->display, view->impl->win);
255 }
256
257 void
puglHideWindow(PuglView * view)258 puglHideWindow(PuglView* view)
259 {
260 XUnmapWindow(view->impl->display, view->impl->win);
261 }
262
263 void
puglDestroy(PuglView * view)264 puglDestroy(PuglView* view)
265 {
266 if (!view) {
267 return;
268 }
269
270 destroyContext(view);
271 XDestroyWindow(view->impl->display, view->impl->win);
272 XCloseDisplay(view->impl->display);
273 free(view->impl);
274 free(view);
275 }
276
277 static PuglKey
keySymToSpecial(KeySym sym)278 keySymToSpecial(KeySym sym)
279 {
280 switch (sym) {
281 case XK_F1:
282 return PUGL_KEY_F1;
283 case XK_F2:
284 return PUGL_KEY_F2;
285 case XK_F3:
286 return PUGL_KEY_F3;
287 case XK_F4:
288 return PUGL_KEY_F4;
289 case XK_F5:
290 return PUGL_KEY_F5;
291 case XK_F6:
292 return PUGL_KEY_F6;
293 case XK_F7:
294 return PUGL_KEY_F7;
295 case XK_F8:
296 return PUGL_KEY_F8;
297 case XK_F9:
298 return PUGL_KEY_F9;
299 case XK_F10:
300 return PUGL_KEY_F10;
301 case XK_F11:
302 return PUGL_KEY_F11;
303 case XK_F12:
304 return PUGL_KEY_F12;
305 case XK_Left:
306 return PUGL_KEY_LEFT;
307 case XK_Up:
308 return PUGL_KEY_UP;
309 case XK_Right:
310 return PUGL_KEY_RIGHT;
311 case XK_Down:
312 return PUGL_KEY_DOWN;
313 case XK_Page_Up:
314 return PUGL_KEY_PAGE_UP;
315 case XK_Page_Down:
316 return PUGL_KEY_PAGE_DOWN;
317 case XK_Home:
318 return PUGL_KEY_HOME;
319 case XK_End:
320 return PUGL_KEY_END;
321 case XK_Insert:
322 return PUGL_KEY_INSERT;
323 case XK_Shift_L:
324 return PUGL_KEY_SHIFT;
325 case XK_Shift_R:
326 return PUGL_KEY_SHIFT;
327 case XK_Control_L:
328 return PUGL_KEY_CTRL;
329 case XK_Control_R:
330 return PUGL_KEY_CTRL;
331 case XK_Alt_L:
332 return PUGL_KEY_ALT;
333 case XK_Alt_R:
334 return PUGL_KEY_ALT;
335 case XK_Super_L:
336 return PUGL_KEY_SUPER;
337 case XK_Super_R:
338 return PUGL_KEY_SUPER;
339 }
340 return (PuglKey)0;
341 }
342
343 static void
translateKey(PuglView * view,XEvent * xevent,PuglEvent * event)344 translateKey(PuglView* view, XEvent* xevent, PuglEvent* event)
345 {
346 KeySym sym;
347 char str[5];
348 const int n = XLookupString(&xevent->xkey, str, 4, &sym, NULL);
349 if (n == 1) {
350 event->key.character = str[0]; // TODO: multi-byte support
351 }
352 event->key.special = keySymToSpecial(sym);
353 }
354
355 static unsigned
translateModifiers(unsigned xstate)356 translateModifiers(unsigned xstate)
357 {
358 unsigned state = 0;
359 state |= (xstate & ShiftMask) ? PUGL_MOD_SHIFT : 0;
360 state |= (xstate & ControlMask) ? PUGL_MOD_CTRL : 0;
361 state |= (xstate & Mod1Mask) ? PUGL_MOD_ALT : 0;
362 state |= (xstate & Mod4Mask) ? PUGL_MOD_SUPER : 0;
363 return state;
364 }
365
366 static PuglEvent
translateEvent(PuglView * view,XEvent xevent)367 translateEvent(PuglView* view, XEvent xevent)
368 {
369 PuglEvent event;
370 memset(&event, 0, sizeof(event));
371
372 event.any.view = view;
373 event.any.send_event = xevent.xany.send_event;
374
375 switch (xevent.type) {
376 case ConfigureNotify:
377 event.type = PUGL_CONFIGURE;
378 event.configure.x = xevent.xconfigure.x;
379 event.configure.y = xevent.xconfigure.y;
380 event.configure.width = xevent.xconfigure.width;
381 event.configure.height = xevent.xconfigure.height;
382 break;
383 case Expose:
384 event.type = PUGL_EXPOSE;
385 event.expose.x = xevent.xexpose.x;
386 event.expose.y = xevent.xexpose.y;
387 event.expose.width = xevent.xexpose.width;
388 event.expose.height = xevent.xexpose.height;
389 event.expose.count = xevent.xexpose.count;
390 /* re-blit the backbuffer to screen */
391 cairo_save( view->impl->cr );
392 cairo_surface_flush( view->impl->surfaceBackBuffer );
393 cairo_set_source_surface( view->impl->cr, view->impl->surfaceBackBuffer, 0, 0 );
394 cairo_paint( view->impl->cr );
395 cairo_restore( view->impl->cr );
396 break;
397 case MotionNotify:
398 event.type = PUGL_MOTION_NOTIFY;
399 event.motion.time = xevent.xmotion.time;
400 event.motion.x = xevent.xmotion.x;
401 event.motion.y = xevent.xmotion.y;
402 event.motion.x_root = xevent.xmotion.x_root;
403 event.motion.y_root = xevent.xmotion.y_root;
404 event.motion.state = translateModifiers(xevent.xmotion.state);
405 event.motion.is_hint = (xevent.xmotion.is_hint == NotifyHint);
406 break;
407 case ButtonPress:
408 if (xevent.xbutton.button >= 4 && xevent.xbutton.button <= 7) {
409 event.type = PUGL_SCROLL;
410 event.scroll.time = xevent.xbutton.time;
411 event.scroll.x = xevent.xbutton.x;
412 event.scroll.y = xevent.xbutton.y;
413 event.scroll.x_root = xevent.xbutton.x_root;
414 event.scroll.y_root = xevent.xbutton.y_root;
415 event.scroll.state = translateModifiers(xevent.xbutton.state);
416 event.scroll.dx = 0.0;
417 event.scroll.dy = 0.0;
418 switch (xevent.xbutton.button) {
419 case 4:
420 event.scroll.dy = 1.0f;
421 break;
422 case 5:
423 event.scroll.dy = -1.0f;
424 break;
425 case 6:
426 event.scroll.dx = -1.0f;
427 break;
428 case 7:
429 event.scroll.dx = 1.0f;
430 break;
431 }
432 }
433 // nobreak
434 case ButtonRelease:
435 if (xevent.xbutton.button < 4 || xevent.xbutton.button > 7) {
436 event.button.type = ((xevent.type == ButtonPress)
437 ? PUGL_BUTTON_PRESS
438 : PUGL_BUTTON_RELEASE);
439 event.button.time = xevent.xbutton.time;
440 event.button.x = xevent.xbutton.x;
441 event.button.y = xevent.xbutton.y;
442 event.button.x_root = xevent.xbutton.x_root;
443 event.button.y_root = xevent.xbutton.y_root;
444 event.button.state = translateModifiers(xevent.xbutton.state);
445 event.button.button = xevent.xbutton.button;
446 }
447 break;
448 case KeyPress:
449 case KeyRelease:
450 event.type = ((xevent.type == KeyPress)
451 ? PUGL_KEY_PRESS
452 : PUGL_KEY_RELEASE);
453 event.key.time = xevent.xbutton.time;
454 event.key.x = xevent.xbutton.x;
455 event.key.y = xevent.xbutton.y;
456 event.key.x_root = xevent.xbutton.x_root;
457 event.key.y_root = xevent.xbutton.y_root;
458 event.key.state = translateModifiers(xevent.xbutton.state);
459 translateKey(view, &xevent, &event);
460 break;
461 case EnterNotify:
462 case LeaveNotify:
463 event.type = ((xevent.type == EnterNotify)
464 ? PUGL_ENTER_NOTIFY
465 : PUGL_LEAVE_NOTIFY);
466 event.crossing.time = xevent.xcrossing.time;
467 event.crossing.x = xevent.xcrossing.x;
468 event.crossing.y = xevent.xcrossing.y;
469 event.crossing.x_root = xevent.xcrossing.x_root;
470 event.crossing.y_root = xevent.xcrossing.y_root;
471 event.crossing.state = translateModifiers(xevent.xcrossing.state);
472 event.crossing.mode = PUGL_CROSSING_NORMAL;
473 if (xevent.xcrossing.mode == NotifyGrab) {
474 event.crossing.mode = PUGL_CROSSING_GRAB;
475 } else if (xevent.xcrossing.mode == NotifyUngrab) {
476 event.crossing.mode = PUGL_CROSSING_UNGRAB;
477 }
478 break;
479 default:
480 break;
481 }
482
483 return event;
484 }
485
486 void
puglGrabFocus(PuglView * view)487 puglGrabFocus(PuglView* view)
488 {
489 XSetInputFocus(
490 view->impl->display, view->impl->win, RevertToPointerRoot, CurrentTime);
491 }
492
493 PuglStatus
puglProcessEvents(PuglView * view)494 puglProcessEvents(PuglView* view)
495 {
496 XEvent xevent;
497 while (XPending(view->impl->display) > 0) {
498 XNextEvent(view->impl->display, &xevent);
499 bool ignore = false;
500 if (xevent.type == ClientMessage) {
501 // Handle close message
502 char* type = XGetAtomName(view->impl->display,
503 xevent.xclient.message_type);
504 if (!strcmp(type, "WM_PROTOCOLS") && view->closeFunc) {
505 view->closeFunc(view);
506 }
507 XFree(type);
508 continue;
509 } else if (xevent.type == KeyRelease) {
510 // Ignore key repeat if necessary
511 if (view->ignoreKeyRepeat &&
512 XEventsQueued(view->impl->display, QueuedAfterReading)) {
513 XEvent next;
514 XPeekEvent(view->impl->display, &next);
515 if (next.type == KeyPress &&
516 next.xkey.time == xevent.xkey.time &&
517 next.xkey.keycode == xevent.xkey.keycode) {
518 XNextEvent(view->impl->display, &xevent);
519 ignore = true;
520 }
521 }
522 }
523
524 if (!ignore) {
525 // Translate and dispatch event
526 const PuglEvent event = translateEvent(view, xevent);
527 puglDispatchEvent(view, &event);
528 }
529 }
530
531 if (view->redisplay) {
532 const PuglEventExpose expose = {
533 PUGL_EXPOSE, view, true, 0, 0, view->width, view->height, 0
534 };
535 puglDispatchEvent(view, (const PuglEvent*)&expose);
536
537 // copy the backbuffer to the frontbuffer cairo context
538 cairo_save( view->impl->cr );
539 cairo_surface_flush( view->impl->surfaceBackBuffer );
540 cairo_set_source_surface( view->impl->cr, view->impl->surfaceBackBuffer, 0, 0 );
541 cairo_paint( view->impl->cr );
542 cairo_restore( view->impl->cr );
543 }
544
545 return PUGL_SUCCESS;
546 }
547
548 void
puglPostRedisplay(PuglView * view)549 puglPostRedisplay(PuglView* view)
550 {
551 view->redisplay = true;
552 }
553
554 PuglNativeWindow
puglGetNativeWindow(PuglView * view)555 puglGetNativeWindow(PuglView* view)
556 {
557 return view->impl->win;
558 }
559
560 void*
puglGetContext(PuglView * view)561 puglGetContext(PuglView* view)
562 {
563 #ifdef PUGL_HAVE_CAIRO
564 if (view->ctx_type == PUGL_CAIRO) {
565 return view->impl->crBackBuffer;
566 }
567 #endif
568 return NULL;
569 }
570