1 /*
2 Copyright 2012-2019 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
24 #include "pugl_internal.h"
25 #include "pugl_x11.h"
26 #ifdef PUGL_HAVE_GL
27 #include "pugl_x11_gl.h"
28 #endif
29 #ifdef PUGL_HAVE_CAIRO
30 #include "pugl_x11_cairo.h"
31 #endif
32
33 #include <X11/Xatom.h>
34 #include <X11/Xlib.h>
35 #include <X11/Xutil.h>
36 #include <X11/keysym.h>
37
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41
42 #ifndef MIN
43 # define MIN(a, b) (((a) < (b)) ? (a) : (b))
44 #endif
45
46 #ifndef MAX
47 # define MAX(a, b) (((a) > (b)) ? (a) : (b))
48 #endif
49
50 PuglInternals*
puglInitInternals(void)51 puglInitInternals(void)
52 {
53 return (PuglInternals*)calloc(1, sizeof(PuglInternals));
54 }
55
56 void
puglEnterContext(PuglView * view)57 puglEnterContext(PuglView* view)
58 {
59 view->impl->ctx.enter(view);
60 }
61
62 void
puglLeaveContext(PuglView * view,bool flush)63 puglLeaveContext(PuglView* view, bool flush)
64 {
65 view->impl->ctx.leave(view, flush);
66 }
67
68 int
puglCreateWindow(PuglView * view,const char * title)69 puglCreateWindow(PuglView* view, const char* title)
70 {
71 PuglInternals* const impl = view->impl;
72
73 impl->display = XOpenDisplay(0);
74 impl->screen = DefaultScreen(impl->display);
75
76 if (view->ctx_type == PUGL_GL) {
77 #ifdef PUGL_HAVE_GL
78 impl->ctx = puglGetX11GlDrawContext();
79 #endif
80 }
81 if (view->ctx_type == PUGL_CAIRO) {
82 #ifdef PUGL_HAVE_CAIRO
83 impl->ctx = puglGetX11CairoDrawContext();
84 #endif
85 }
86
87 if (!impl->ctx.configure) {
88 return 1;
89 }
90
91 if (impl->ctx.configure(view) || !impl->vi) {
92 impl->ctx.destroy(view);
93 return 2;
94 }
95
96 Window xParent = view->parent
97 ? (Window)view->parent
98 : RootWindow(impl->display, impl->screen);
99
100 Colormap cmap = XCreateColormap(
101 impl->display, xParent, impl->vi->visual, AllocNone);
102
103 XSetWindowAttributes attr;
104 memset(&attr, 0, sizeof(XSetWindowAttributes));
105 attr.colormap = cmap;
106 attr.event_mask = (ExposureMask | StructureNotifyMask |
107 EnterWindowMask | LeaveWindowMask |
108 KeyPressMask | KeyReleaseMask |
109 ButtonPressMask | ButtonReleaseMask |
110 PointerMotionMask | FocusChangeMask);
111
112 impl->win = XCreateWindow(
113 impl->display, xParent,
114 0, 0, view->width, view->height, 0, impl->vi->depth, InputOutput,
115 impl->vi->visual, CWColormap | CWEventMask, &attr);
116
117 if (impl->ctx.create(view)) {
118 return 3;
119 }
120
121 XSizeHints sizeHints;
122 memset(&sizeHints, 0, sizeof(sizeHints));
123 if (!view->hints.resizable) {
124 sizeHints.flags = PMinSize|PMaxSize;
125 sizeHints.min_width = view->width;
126 sizeHints.min_height = view->height;
127 sizeHints.max_width = view->width;
128 sizeHints.max_height = view->height;
129 XSetNormalHints(impl->display, impl->win, &sizeHints);
130 } else {
131 if (view->min_width || view->min_height) {
132 sizeHints.flags = PMinSize;
133 sizeHints.min_width = view->min_width;
134 sizeHints.min_height = view->min_height;
135 }
136 if (view->min_aspect_x) {
137 sizeHints.flags |= PAspect;
138 sizeHints.min_aspect.x = view->min_aspect_x;
139 sizeHints.min_aspect.y = view->min_aspect_y;
140 sizeHints.max_aspect.x = view->max_aspect_x;
141 sizeHints.max_aspect.y = view->max_aspect_y;
142 }
143
144 XSetNormalHints(impl->display, impl->win, &sizeHints);
145 }
146
147 if (title) {
148 XStoreName(impl->display, impl->win, title);
149 }
150
151 if (!view->parent) {
152 Atom wmDelete = XInternAtom(impl->display, "WM_DELETE_WINDOW", True);
153 XSetWMProtocols(impl->display, impl->win, &wmDelete, 1);
154 }
155
156 if (view->transient_parent) {
157 XSetTransientForHint(impl->display, impl->win,
158 (Window)(view->transient_parent));
159 }
160
161 XSetLocaleModifiers("");
162 if (!(impl->xim = XOpenIM(impl->display, NULL, NULL, NULL))) {
163 XSetLocaleModifiers("@im=");
164 if (!(impl->xim = XOpenIM(impl->display, NULL, NULL, NULL))) {
165 fprintf(stderr, "warning: XOpenIM failed\n");
166 }
167 }
168
169 const XIMStyle im_style = XIMPreeditNothing | XIMStatusNothing;
170 if (!(impl->xic = XCreateIC(impl->xim,
171 XNInputStyle, im_style,
172 XNClientWindow, impl->win,
173 XNFocusWindow, impl->win,
174 NULL))) {
175 fprintf(stderr, "warning: XCreateIC failed\n");
176 }
177
178 return 0;
179 }
180
181 void
puglShowWindow(PuglView * view)182 puglShowWindow(PuglView* view)
183 {
184 XMapRaised(view->impl->display, view->impl->win);
185 view->visible = true;
186 }
187
188 void
puglHideWindow(PuglView * view)189 puglHideWindow(PuglView* view)
190 {
191 XUnmapWindow(view->impl->display, view->impl->win);
192 view->visible = false;
193 }
194
195 void
puglDestroy(PuglView * view)196 puglDestroy(PuglView* view)
197 {
198 if (view) {
199 view->impl->ctx.destroy(view);
200 XDestroyWindow(view->impl->display, view->impl->win);
201 XCloseDisplay(view->impl->display);
202 XFree(view->impl->vi);
203 free(view->windowClass);
204 free(view->impl);
205 free(view);
206 }
207 }
208
209 static PuglKey
keySymToSpecial(KeySym sym)210 keySymToSpecial(KeySym sym)
211 {
212 switch (sym) {
213 case XK_F1: return PUGL_KEY_F1;
214 case XK_F2: return PUGL_KEY_F2;
215 case XK_F3: return PUGL_KEY_F3;
216 case XK_F4: return PUGL_KEY_F4;
217 case XK_F5: return PUGL_KEY_F5;
218 case XK_F6: return PUGL_KEY_F6;
219 case XK_F7: return PUGL_KEY_F7;
220 case XK_F8: return PUGL_KEY_F8;
221 case XK_F9: return PUGL_KEY_F9;
222 case XK_F10: return PUGL_KEY_F10;
223 case XK_F11: return PUGL_KEY_F11;
224 case XK_F12: return PUGL_KEY_F12;
225 case XK_Left: return PUGL_KEY_LEFT;
226 case XK_Up: return PUGL_KEY_UP;
227 case XK_Right: return PUGL_KEY_RIGHT;
228 case XK_Down: return PUGL_KEY_DOWN;
229 case XK_Page_Up: return PUGL_KEY_PAGE_UP;
230 case XK_Page_Down: return PUGL_KEY_PAGE_DOWN;
231 case XK_Home: return PUGL_KEY_HOME;
232 case XK_End: return PUGL_KEY_END;
233 case XK_Insert: return PUGL_KEY_INSERT;
234 case XK_Shift_L: return PUGL_KEY_SHIFT;
235 case XK_Shift_R: return PUGL_KEY_SHIFT;
236 case XK_Control_L: return PUGL_KEY_CTRL;
237 case XK_Control_R: return PUGL_KEY_CTRL;
238 case XK_Alt_L: return PUGL_KEY_ALT;
239 case XK_Alt_R: return PUGL_KEY_ALT;
240 case XK_Super_L: return PUGL_KEY_SUPER;
241 case XK_Super_R: return PUGL_KEY_SUPER;
242 default: return (PuglKey)0;
243 }
244 return (PuglKey)0;
245 }
246
247 static void
translateKey(PuglView * view,XEvent * xevent,PuglEvent * event)248 translateKey(PuglView* view, XEvent* xevent, PuglEvent* event)
249 {
250 KeySym sym = 0;
251 char* str = (char*)event->key.utf8;
252 memset(str, 0, 8);
253 event->key.filter = XFilterEvent(xevent, None);
254 if (xevent->type == KeyRelease || event->key.filter || !view->impl->xic) {
255 if (XLookupString(&xevent->xkey, str, 7, &sym, NULL) == 1) {
256 event->key.character = str[0];
257 }
258 } else {
259 /* TODO: Not sure about this. On my system, some characters work with
260 Xutf8LookupString but not with XmbLookupString, and some are the
261 opposite. */
262 Status status = 0;
263 #ifdef X_HAVE_UTF8_STRING
264 const int n = Xutf8LookupString(
265 view->impl->xic, &xevent->xkey, str, 7, &sym, &status);
266 #else
267 const int n = XmbLookupString(
268 view->impl->xic, &xevent->xkey, str, 7, &sym, &status);
269 #endif
270 if (n > 0) {
271 event->key.character = puglDecodeUTF8((const uint8_t*)str);
272 }
273 }
274 event->key.special = keySymToSpecial(sym);
275 event->key.keycode = xevent->xkey.keycode;
276 }
277
278 static unsigned
translateModifiers(unsigned xstate)279 translateModifiers(unsigned xstate)
280 {
281 unsigned state = 0;
282 state |= (xstate & ShiftMask) ? PUGL_MOD_SHIFT : 0;
283 state |= (xstate & ControlMask) ? PUGL_MOD_CTRL : 0;
284 state |= (xstate & Mod1Mask) ? PUGL_MOD_ALT : 0;
285 state |= (xstate & Mod4Mask) ? PUGL_MOD_SUPER : 0;
286 return state;
287 }
288
289 static PuglEvent
translateEvent(PuglView * view,XEvent xevent)290 translateEvent(PuglView* view, XEvent xevent)
291 {
292 PuglEvent event;
293 memset(&event, 0, sizeof(event));
294
295 event.any.view = view;
296 if (xevent.xany.send_event) {
297 event.any.flags |= PUGL_IS_SEND_EVENT;
298 }
299
300 switch (xevent.type) {
301 case ClientMessage: {
302 char* type = XGetAtomName(view->impl->display,
303 xevent.xclient.message_type);
304 if (!strcmp(type, "WM_PROTOCOLS")) {
305 event.type = PUGL_CLOSE;
306 }
307 break;
308 }
309 case ConfigureNotify:
310 event.type = PUGL_CONFIGURE;
311 event.configure.x = xevent.xconfigure.x;
312 event.configure.y = xevent.xconfigure.y;
313 event.configure.width = xevent.xconfigure.width;
314 event.configure.height = xevent.xconfigure.height;
315 break;
316 case Expose:
317 event.type = PUGL_EXPOSE;
318 event.expose.x = xevent.xexpose.x;
319 event.expose.y = xevent.xexpose.y;
320 event.expose.width = xevent.xexpose.width;
321 event.expose.height = xevent.xexpose.height;
322 event.expose.count = xevent.xexpose.count;
323 break;
324 case MotionNotify:
325 event.type = PUGL_MOTION_NOTIFY;
326 event.motion.time = xevent.xmotion.time;
327 event.motion.x = xevent.xmotion.x;
328 event.motion.y = xevent.xmotion.y;
329 event.motion.x_root = xevent.xmotion.x_root;
330 event.motion.y_root = xevent.xmotion.y_root;
331 event.motion.state = translateModifiers(xevent.xmotion.state);
332 event.motion.is_hint = (xevent.xmotion.is_hint == NotifyHint);
333 break;
334 case ButtonPress:
335 if (xevent.xbutton.button >= 4 && xevent.xbutton.button <= 7) {
336 event.type = PUGL_SCROLL;
337 event.scroll.time = xevent.xbutton.time;
338 event.scroll.x = xevent.xbutton.x;
339 event.scroll.y = xevent.xbutton.y;
340 event.scroll.x_root = xevent.xbutton.x_root;
341 event.scroll.y_root = xevent.xbutton.y_root;
342 event.scroll.state = translateModifiers(xevent.xbutton.state);
343 event.scroll.dx = 0.0;
344 event.scroll.dy = 0.0;
345 switch (xevent.xbutton.button) {
346 case 4: event.scroll.dy = 1.0f; break;
347 case 5: event.scroll.dy = -1.0f; break;
348 case 6: event.scroll.dx = -1.0f; break;
349 case 7: event.scroll.dx = 1.0f; break;
350 }
351 // fallthru
352 }
353 // fallthru
354 case ButtonRelease:
355 if (xevent.xbutton.button < 4 || xevent.xbutton.button > 7) {
356 event.button.type = ((xevent.type == ButtonPress)
357 ? PUGL_BUTTON_PRESS
358 : PUGL_BUTTON_RELEASE);
359 event.button.time = xevent.xbutton.time;
360 event.button.x = xevent.xbutton.x;
361 event.button.y = xevent.xbutton.y;
362 event.button.x_root = xevent.xbutton.x_root;
363 event.button.y_root = xevent.xbutton.y_root;
364 event.button.state = translateModifiers(xevent.xbutton.state);
365 event.button.button = xevent.xbutton.button;
366 }
367 break;
368 case KeyPress:
369 case KeyRelease:
370 event.type = ((xevent.type == KeyPress)
371 ? PUGL_KEY_PRESS
372 : PUGL_KEY_RELEASE);
373 event.key.time = xevent.xkey.time;
374 event.key.x = xevent.xkey.x;
375 event.key.y = xevent.xkey.y;
376 event.key.x_root = xevent.xkey.x_root;
377 event.key.y_root = xevent.xkey.y_root;
378 event.key.state = translateModifiers(xevent.xkey.state);
379 translateKey(view, &xevent, &event);
380 break;
381 case EnterNotify:
382 case LeaveNotify:
383 event.type = ((xevent.type == EnterNotify)
384 ? PUGL_ENTER_NOTIFY
385 : PUGL_LEAVE_NOTIFY);
386 event.crossing.time = xevent.xcrossing.time;
387 event.crossing.x = xevent.xcrossing.x;
388 event.crossing.y = xevent.xcrossing.y;
389 event.crossing.x_root = xevent.xcrossing.x_root;
390 event.crossing.y_root = xevent.xcrossing.y_root;
391 event.crossing.state = translateModifiers(xevent.xcrossing.state);
392 event.crossing.mode = PUGL_CROSSING_NORMAL;
393 if (xevent.xcrossing.mode == NotifyGrab) {
394 event.crossing.mode = PUGL_CROSSING_GRAB;
395 } else if (xevent.xcrossing.mode == NotifyUngrab) {
396 event.crossing.mode = PUGL_CROSSING_UNGRAB;
397 }
398 break;
399
400 case FocusIn:
401 case FocusOut:
402 event.type = ((xevent.type == FocusIn)
403 ? PUGL_FOCUS_IN
404 : PUGL_FOCUS_OUT);
405 event.focus.grab = (xevent.xfocus.mode != NotifyNormal);
406 break;
407
408 default:
409 break;
410 }
411
412 return event;
413 }
414
415 void
puglGrabFocus(PuglView * view)416 puglGrabFocus(PuglView* view)
417 {
418 XSetInputFocus(
419 view->impl->display, view->impl->win, RevertToPointerRoot, CurrentTime);
420 }
421
422 PuglStatus
puglWaitForEvent(PuglView * view)423 puglWaitForEvent(PuglView* view)
424 {
425 XEvent xevent;
426 XPeekEvent(view->impl->display, &xevent);
427 return PUGL_SUCCESS;
428 }
429
430 static void
merge_expose_events(PuglEvent * dst,const PuglEvent * src)431 merge_expose_events(PuglEvent* dst, const PuglEvent* src)
432 {
433 if (!dst->type) {
434 *dst = *src;
435 } else {
436 const double max_x = MAX(dst->expose.x + dst->expose.width,
437 src->expose.x + src->expose.width);
438 const double max_y = MAX(dst->expose.y + dst->expose.height,
439 src->expose.y + src->expose.height);
440
441 dst->expose.x = MIN(dst->expose.x, src->expose.x);
442 dst->expose.y = MIN(dst->expose.y, src->expose.y);
443 dst->expose.width = max_x - dst->expose.x;
444 dst->expose.height = max_y - dst->expose.y;
445 dst->expose.count = MIN(dst->expose.count, src->expose.count);
446 }
447 }
448
449 PuglStatus
puglProcessEvents(PuglView * view)450 puglProcessEvents(PuglView* view)
451 {
452 /* Maintain a single expose/configure event to execute after all pending
453 events. This avoids redundant drawing/configuration which prevents a
454 series of window resizes in the same loop from being laggy. */
455 PuglEvent expose_event = {};
456 PuglEvent config_event = {};
457 XEvent xevent;
458 while (XPending(view->impl->display) > 0) {
459 XNextEvent(view->impl->display, &xevent);
460 if (xevent.type == KeyRelease) {
461 // Ignore key repeat if necessary
462 if (view->ignoreKeyRepeat &&
463 XEventsQueued(view->impl->display, QueuedAfterReading)) {
464 XEvent next;
465 XPeekEvent(view->impl->display, &next);
466 if (next.type == KeyPress &&
467 next.xkey.time == xevent.xkey.time &&
468 next.xkey.keycode == xevent.xkey.keycode) {
469 XNextEvent(view->impl->display, &xevent);
470 continue;
471 }
472 }
473 } else if (xevent.type == FocusIn) {
474 XSetICFocus(view->impl->xic);
475 } else if (xevent.type == FocusOut) {
476 XUnsetICFocus(view->impl->xic);
477 }
478
479 // Translate X11 event to Pugl event
480 const PuglEvent event = translateEvent(view, xevent);
481
482 if (event.type == PUGL_EXPOSE) {
483 // Expand expose event to be dispatched after loop
484 merge_expose_events(&expose_event, &event);
485 } else if (event.type == PUGL_CONFIGURE) {
486 // Expand configure event to be dispatched after loop
487 config_event = event;
488 } else {
489 // Dispatch event to application immediately
490 puglDispatchEvent(view, &event);
491 }
492 }
493
494 if (config_event.type) {
495 // Resize drawing context before dispatching
496 view->impl->ctx.resize(view,
497 (int)config_event.configure.width,
498 (int)config_event.configure.height);
499 puglDispatchEvent(view, (const PuglEvent*)&config_event);
500 }
501
502 if (view->redisplay) {
503 expose_event.expose.type = PUGL_EXPOSE;
504 expose_event.expose.view = view;
505 expose_event.expose.x = 0;
506 expose_event.expose.y = 0;
507 expose_event.expose.width = view->width;
508 expose_event.expose.height = view->height;
509 view->redisplay = false;
510 }
511
512 if (expose_event.type) {
513 puglDispatchEvent(view, (const PuglEvent*)&expose_event);
514 }
515
516 return PUGL_SUCCESS;
517 }
518
519 void
puglPostRedisplay(PuglView * view)520 puglPostRedisplay(PuglView* view)
521 {
522 view->redisplay = true;
523 }
524
525 PuglNativeWindow
puglGetNativeWindow(PuglView * view)526 puglGetNativeWindow(PuglView* view)
527 {
528 return view->impl->win;
529 }
530
531 void*
puglGetContext(PuglView * view)532 puglGetContext(PuglView* view)
533 {
534 return view->impl->ctx.getHandle(view);
535 }
536