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