1 /**
2  * X11 window implementation.
3  *
4  * Copyright: Copyright (C) 2017 Richard Andrew Cattermole
5  *            Copyright (C) 2017 Ethan Reker
6  *            Copyright (C) 2017 Lukasz Pelszynski
7  *            Copyright (C) 2019-2020 Guillaume Piolat 
8  *
9  * Bugs:
10  *     - X11 does not support double clicks, it is sometimes emulated https://github.com/glfw/glfw/issues/462
11  *
12  * License:   $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
13  * Authors:   Richard (Rikki) Andrew Cattermole
14  */
15 module dplug.window.x11window;
16 
17 version(linux): // because of static linking with the X11 library
18 
19 import core.atomic;
20 import core.stdc.string;
21 import core.sys.posix.unistd;
22 import core.sys.posix.time;
23 
24 import dplug.math.box;
25 
26 import derelict.x11.X;
27 import derelict.x11.Xlib;
28 import derelict.x11.keysym;
29 import derelict.x11.keysymdef;
30 import derelict.x11.Xutil;
31 
32 import dplug.core.runtime;
33 import dplug.core.nogc;
34 import dplug.core.thread;
35 import dplug.core.sync;
36 import dplug.core.map;
37 
38 import dplug.graphics.image;
39 import dplug.window.window;
40 
41 nothrow:
42 @nogc:
43 
44 //debug = logX11Window;
45 
46 debug(logX11Window)
47 {
48     import core.stdc.stdio;
49 }
50 
51 
52 // This is an extension to X11, almost always should exist on modern systems
53 // If it becomes a problem, version out its usage, it'll work just won't be as nice event wise
54 extern(C) bool XkbSetDetectableAutoRepeat(Display*, bool, bool*);
55 
56 final class X11Window : IWindow
57 {
58 nothrow:
59 @nogc:
60 public:
61 
62     this(WindowUsage usage, void* parentWindow, IWindowListener listener, int width, int height)
63     {
64         debug(logX11Window) printf(">X11Window.this()\n");
65 
66         // Detect clock
67         {
68             timespec ts;
69             _monotonicClock = false;
70             if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0)
71                 _monotonicClock = true;
72         }
73 
74         _eventMutex = makeMutex();
75         _dirtyAreaMutex = makeMutex();
76         _isHostWindow = (usage == WindowUsage.host);
77 
78         assert(listener !is null);
79         _listener = listener;
80 
81         bool isChildWindow = parentWindow !is null;
82         acquireX11(isChildWindow);
83 
84         lockX11();
85 
86         //_parentID = cast(Window)parentWindow;
87         _screen = DefaultScreen(_display);
88         _visual = XDefaultVisual(_display, _screen);
89 
90         XkbSetDetectableAutoRepeat(_display, true, null);
91 
92         version(VST3) 
93             _useXEmbed = true;
94         else
95             _useXEmbed = false;
96 
97         createX11Window(parentWindow, width, height);
98         createHiddenCursor();
99 
100         _timeAtCreationInUs = getTimeUs();
101         _lastMeasturedTimeInUs = _timeAtCreationInUs;
102         _lastClickTimeUs = _timeAtCreationInUs;
103 
104         _timerLoop = makeThread(&timerLoopFunc);
105         _timerLoop.start();
106         _eventLoop = makeThread(&eventLoopFunc);
107         _eventLoop.start();
108 
109         unlockX11();
110         debug(logX11Window) printf("<X11Window.this()\n");
111     }
112 
113     ~this()
114     {     
115         debug(logX11Window) printf(">X11Window.~this()\n");
116         atomicStore(_terminateThreads, true);
117 
118         // Terminate event loop thread
119         _eventLoop.join();
120 
121         // Terminate time thread
122         _timerLoop.join();
123 
124         releaseX11Window();
125 
126         _eventMutex.destroy();
127 
128         releaseX11();
129         debug(logX11Window) printf("<X11Window.~this()\n");
130     }
131 
132     // <Implements IWindow>
133     override void waitEventAndDispatch()
134     {
135         XEvent event;
136         lockX11();
137         XWindowEvent(_display, _windowID, windowEventMask(), &event);
138         unlockX11();
139         processEvent(&event);
140     }
141 
142     override bool terminated()
143     {
144         return atomicLoad(_userRequestedTermination);
145     }
146 
147     override uint getTimeMs()
148     {
149         // TODO: remove getTimeMs from the IWindow interface.
150 
151         static uint perform() 
152         {
153             import core.sys.posix.sys.time;
154             timeval tv;
155             gettimeofday(&tv, null);
156             return cast(uint)((tv.tv_sec) * 1000 + (tv.tv_usec) / 1000 ) ;
157         }
158         return assumeNothrowNoGC(&perform)();
159     }
160 
161     // For more precise animation
162     ulong getTimeUs()
163     {
164         static ulong perform(bool monotonic) 
165         {
166             import core.sys.posix.sys.time;
167 
168             if (monotonic)
169             {
170                 timespec ts;
171                 clock_gettime(CLOCK_MONOTONIC, &ts); // gives a time in ns
172                 return ( ts.tv_sec * cast(ulong)1_000_000 ) + (cast(ulong)ts.tv_nsec) / 1000;
173             }
174             else
175             {
176                 timeval tv;
177                 gettimeofday(&tv, null);
178                 return cast(ulong)(tv.tv_sec) * 1_000_000 + tv.tv_usec;
179             }
180         }
181         return assumeNothrowNoGC(&perform)(_monotonicClock);
182     }
183 
184     override void* systemHandle()
185     {
186         return cast(void*)_windowID;
187     }
188 
189     override bool requestResize(int widthLogicalPixels, int heightLogicalPixels, bool alsoResizeParentWindow)
190     {
191         assert(!alsoResizeParentWindow); // unsupported here
192         lockX11();
193         XResizeWindow(_display, _windowID, widthLogicalPixels, heightLogicalPixels);
194         unlockX11();
195         return true;
196     }
197 
198     // </Implements IWindow>
199 
200 private:
201 
202     enum int BIT_DEPTH = 24;
203     enum int XEMBED_VERSION = 0;
204     enum int XEMBED_MAPPED = (1 << 0);
205 
206     /// Usage of this window (host or plugin)
207     bool _isHostWindow;
208 
209     /// window listener.
210     IWindowListener _listener = null;  
211 
212     /// Current width of window.
213     int _width = -1;
214 
215     /// Current height of window.
216     int _height = -1;
217 
218     /// Framebuffer reference
219     ImageRef!RGBA _wfb;
220 
221     /// Because timer events can arrive after the first Expose (first seen in JUCE's AudioPluginHost see Issue #572)
222     bool _recomputeDirtyAreasWasCalled;
223 
224     /// Flag to tell threads to terminate. Used in thread finalization only.
225     shared(bool) _terminateThreads = false; 
226 
227     /// Did the user closed the window? (when used as host window).
228     shared(bool) _userRequestedTermination = false;
229     
230     // Time when creating this window, in microseconds.
231     ulong _timeAtCreationInUs;
232 
233     /// Last time in microseconds.
234     ulong  _lastMeasturedTimeInUs;
235 
236     /// Last click time (for double click emulation).
237     ulong _lastClickTimeUs;
238 
239     /// This thread pump event.
240     Thread _eventLoop;
241 
242     /// This thread acts like a timer.
243     Thread _timerLoop;
244 
245     /// Are we using XEmbed?
246     bool _useXEmbed;
247 
248     /// Last mouse position.
249     bool _firstMouseMoveAfterEntering = true;
250     int _lastMouseX;
251     int _lastMouseY;
252 
253     /// Prevent onAnimate and all other events from going on at the same time,
254     /// notably including onDraw.
255     /// That way, `onAnimate`, `onDraw` and input callbacks are not concurrent,
256     /// like in Windows and macOS.
257     UncheckedMutex _eventMutex;   
258 
259     /// Prevent recomputeDirtyAreas() and onDraw() to be called simulatneously.
260     /// This is masking a race in dplug:gui. See Dplug issue #572.
261     /// Other window systems (Windows and Mac) also have this kind of shenanigans.
262     UncheckedMutex _dirtyAreaMutex;
263 
264     //
265     // <X11 resources>
266     //
267 
268     /// X11 ID of this window.
269     Window _windowID;
270 
271     /// The default X11 screen of _display.
272     int _screen;
273 
274     /// The default Visual of _display.
275     Visual* _visual;
276 
277     /// Colormap associated with this window.
278     Colormap _cmap;
279 
280     // XEmbed stuff
281     Atom _XEMBED;
282     Atom _XEMBED_INFO;
283 
284     Atom _closeAtom;
285 
286     /// X11 Graphics Context
287     derelict.x11.Xlib.GC _graphicGC;
288     XImage* _graphicImage;
289 
290     // Last MouseCursor used. This is to avoid updating the cursor
291     // more often than necessary
292     // Default value of pointer
293     MouseCursor _lastMouseCursor = MouseCursor.pointer;
294 
295     // empty pixmap for creating an invisible cursor
296     Pixmap _bitmapNoData;
297 
298     // custom defined cursor that has empty data to appear invisible
299     Cursor _hiddenCursor;
300 
301     // If we have access to CLOCK_MONOTONIC.
302     bool _monotonicClock;
303 
304     //
305     // </X11 resources>
306     //
307 
308     void eventLoopFunc() nothrow @nogc
309     {
310         // Pump events until told to terminate.
311         uint pauseTimeUs = 0;
312 
313         while (true)
314         {
315             if (atomicLoad(_terminateThreads))
316                 break;
317 
318             XEvent event;
319             lockX11();
320             Bool found = XCheckWindowEvent(_display, _windowID, windowEventMask(), &event);
321             unlockX11();
322             if (found == False)
323             {
324                 pauseTimeUs = pauseTimeUs * 2 + 1000; // exponential pause
325                 if (pauseTimeUs > 100_000)
326                     pauseTimeUs = 100_000; // max 100ms of pause            }
327                 usleep(pauseTimeUs);
328             }
329             else
330             {
331                 processEvent(&event);
332                 pauseTimeUs = 0;
333             }
334         }
335     }
336 
337     void timerLoopFunc() nothrow @nogc
338     {
339         // Send repaints until told to terminate.
340         while(true)
341         {
342             if (atomicLoad(_terminateThreads))
343                 break;
344 
345             doAnimation();
346             sendRepaintIfUIDirty();
347             setCursor();
348 
349             if (atomicLoad(_terminateThreads))
350                 break;
351 
352             // Sleep 1 / 60th of a second
353             enum long durationInNanosecs = 1_000_000_000 / 60;
354             timespec time;
355             timespec rem;
356             time.tv_nsec = durationInNanosecs;
357             time.tv_sec  = 0;
358             nanosleep(&time, &rem);
359         }
360     }
361 
362     void doAnimation()
363     {
364         ulong now = getTimeUs();
365         double dt = (now - _lastMeasturedTimeInUs) * 0.000001;
366         double time = (now - _timeAtCreationInUs) * 0.000001; // hopefully no plug-in will be open more than 49 days
367         _lastMeasturedTimeInUs = now;
368 
369         _eventMutex.lock();
370         _listener.onAnimate(dt, time);
371         _eventMutex.unlock();
372     }
373 
374     void setCursor()
375     {
376         version(legacyMouseCursor)
377         {}
378         else
379         {
380             MouseCursor cursor = _listener.getMouseCursor();
381 
382             if(cursor != _lastMouseCursor)
383             {
384                 lockX11();
385                 immutable int x11CursorFont = convertCursorToX11CursorFont(cursor);
386                 auto c = cursor == MouseCursor.hidden ? _hiddenCursor : XCreateFontCursor(_display, x11CursorFont); 
387                 XDefineCursor(_display, _windowID, c);
388                 unlockX11();
389             }
390             _lastMouseCursor = cursor;
391         }
392     }
393 
394     void processEvent(XEvent* event)
395     {
396         switch(event.type)
397         {
398         case ConfigureNotify:
399             notifySize(event.xconfigure.width, event.xconfigure.height);
400             break;
401 
402         case EnterNotify:
403             _firstMouseMoveAfterEntering = true;
404             return; // nothing to do
405 
406         case LeaveNotify:
407             _listener.onMouseExitedWindow();
408             return;
409 
410         case Expose:
411 
412             // Same thing that under Cocoa and Windows: the first Expose could happen before the timer is called.
413             // See Issue #523 and #572.
414             _dirtyAreaMutex.lock();
415             if (!_recomputeDirtyAreasWasCalled)
416             {
417                 _listener.recomputeDirtyAreas();
418                 _recomputeDirtyAreasWasCalled = true;
419             }
420             _dirtyAreaMutex.unlock();
421 
422             // Draw UI
423             _eventMutex.lock();
424             _dirtyAreaMutex.lock();
425             _listener.onDraw(WindowPixelFormat.BGRA8);
426             _dirtyAreaMutex.unlock();
427             _eventMutex.unlock();
428 
429             XExposeEvent* xpose = cast(XExposeEvent*)event;
430 
431             // Find dirty rect from event
432             int x = xpose.x;
433             int y = xpose.y;
434             int width = xpose.width;
435             int height = xpose.height;
436 
437             lockX11();
438             XPutImage(_display, 
439                       _windowID,
440                       _graphicGC, 
441                       _graphicImage, 
442                       x, y, // source position
443                       x, y, // dest position
444                       width,
445                       height);
446             unlockX11();
447             break;
448 
449         case ReparentNotify:
450             break; // do nothing
451 
452         case KeyPress:
453             KeySym symbol;
454             lockX11();
455             XLookupString(&event.xkey, null, 0, &symbol, null);
456             unlockX11();
457             _eventMutex.lock();
458             _listener.onKeyDown(convertKeyFromX11(symbol));
459             _eventMutex.unlock();
460             break;
461 
462         case KeyRelease:
463             KeySym symbol;
464             lockX11();
465             XLookupString(&event.xkey, null, 0, &symbol, null);
466             unlockX11();
467             _eventMutex.lock();
468             _listener.onKeyUp(convertKeyFromX11(symbol));
469             _eventMutex.unlock();
470             break;
471 
472         case MotionNotify:
473             int newMouseX = event.xmotion.x;
474             int newMouseY = event.xmotion.y;
475 
476             if (_firstMouseMoveAfterEntering)
477             {
478                 _lastMouseX = newMouseX;
479                 _lastMouseY = newMouseY;
480                 _firstMouseMoveAfterEntering = false;                    
481             }
482 
483             int dx = newMouseX - _lastMouseX;
484             int dy = newMouseY - _lastMouseY;
485             _eventMutex.lock();
486             _listener.onMouseMove(newMouseX, newMouseY, dx, dy, mouseStateFromX11(event.xbutton.state));
487             _eventMutex.unlock();
488             _lastMouseX = newMouseX;
489             _lastMouseY = newMouseY;
490             break;
491 
492         case ButtonPress:
493             int newMouseX = event.xbutton.x;
494             int newMouseY = event.xbutton.y;
495             MouseButton button;
496             if (event.xbutton.button == Button1)
497                 button = MouseButton.left;
498             else if (event.xbutton.button == Button3)
499                 button = MouseButton.right;
500             else if (event.xbutton.button == Button2)
501                 button = MouseButton.middle;
502             else if (event.xbutton.button == Button4)
503                 button = MouseButton.x1;
504             else if (event.xbutton.button == Button5)
505                 button = MouseButton.x2;
506 
507             ulong now = getTimeUs();
508             bool isDoubleClick = now - _lastClickTimeUs <= 500_000; // 500 ms
509             _lastClickTimeUs = now;
510 
511             _lastMouseX = newMouseX;
512             _lastMouseY = newMouseY;
513 
514             _eventMutex.lock();
515             if (event.xbutton.button == Button4 || event.xbutton.button == Button5)
516             {
517                 _listener.onMouseWheel(newMouseX, newMouseY, 0, event.xbutton.button == Button4 ? 1 : -1,
518                     mouseStateFromX11(event.xbutton.state));
519             }
520             else
521             {
522                 _listener.onMouseClick(newMouseX, newMouseY, button, isDoubleClick, mouseStateFromX11(event.xbutton.state));
523             }
524             _eventMutex.unlock();
525             break;
526 
527         case ButtonRelease:
528             int newMouseX = event.xbutton.x;
529             int newMouseY = event.xbutton.y;
530 
531             MouseButton button;
532 
533             _lastMouseX = newMouseX;
534             _lastMouseY = newMouseY;
535 
536             if (event.xbutton.button == Button1)
537                 button = MouseButton.left;
538             else if (event.xbutton.button == Button3)
539                 button = MouseButton.right;
540             else if (event.xbutton.button == Button2)
541                 button = MouseButton.middle;
542             else if (event.xbutton.button == Button4 || event.xbutton.button == Button5)
543                 break;
544 
545             _eventMutex.lock();
546             _listener.onMouseRelease(newMouseX, newMouseY, button, mouseStateFromX11(event.xbutton.state));
547             _eventMutex.unlock();
548             break;
549 
550         case DestroyNotify:
551             atomicStore(_userRequestedTermination, true);
552             break;
553 
554         default:
555             string s = X11EventTypeString(event.type);           
556             debug(logX11Window) printf("Unhandled event %d (%s)\n", event.type, s.ptr);
557         }
558     }
559 
560     void notifySize(int width, int height)
561     {
562         if (width != _width || height != _height)
563         {
564             _width = width;
565             _height = height;
566 
567             _eventMutex.lock();
568             _dirtyAreaMutex.lock();
569             _wfb = _listener.onResized(width, height);
570             _dirtyAreaMutex.unlock();
571             _eventMutex.unlock();
572 
573             // reallocates backbuffer (if any)
574             freeBackbuffer();
575 
576             // and recreates it at the right size
577             assert(_visual);
578             lockX11();
579             int bytes_per_line = cast(int)(_wfb.pitch);
580             _graphicImage = XCreateImage(_display, 
581                             _visual, 
582                             BIT_DEPTH, 
583                             ZPixmap, 
584                             0, 
585                             cast(char*)_wfb.pixels, 
586                             _width, 
587                             _height, 
588                             32, 
589                             bytes_per_line);
590             unlockX11();
591         }
592     }
593 
594     void sendRepaintIfUIDirty() nothrow @nogc
595     {
596         box2i dirtyRect;
597         _dirtyAreaMutex.lock();
598         {
599             _listener.recomputeDirtyAreas();
600             _recomputeDirtyAreasWasCalled = true;
601             dirtyRect = _listener.getDirtyRectangle();
602         }
603         _dirtyAreaMutex.unlock();
604 
605         if (!dirtyRect.empty())
606         {
607             XEvent evt;
608             memset(&evt, 0, XEvent.sizeof);
609             evt.type = Expose;
610             evt.xexpose.window = _windowID;
611             evt.xexpose.display = _display;
612             evt.xexpose.x = dirtyRect.min.x;
613             evt.xexpose.y = dirtyRect.min.y;
614             evt.xexpose.width = dirtyRect.width;
615             evt.xexpose.height = dirtyRect.height;
616             lockX11();
617             XSendEvent(_display, _windowID, False, ExposureMask, &evt);
618             XFlush(_display);
619             unlockX11();
620         }
621     }
622 
623     void createX11Window(void* parentWindow, int width, int height)
624     {
625         // Note: this is already locked by constructor
626 
627         // Find the parent Window ID if none provided
628         Window parentWindowID;
629         if (parentWindow is null)
630             parentWindowID = RootWindow(_display, _screen);
631         else
632             parentWindowID = cast(Window)parentWindow;
633 
634         _cmap = XCreateColormap(_display, parentWindowID, _visual, AllocNone);
635 
636         XSetWindowAttributes attr;
637         memset(&attr, 0, XSetWindowAttributes.sizeof);
638         attr.border_pixel = BlackPixel(_display, _screen);
639         attr.colormap     = _cmap;
640         attr.event_mask   = windowEventMask();
641 
642         int left = 0;
643         int top = 0;
644         int border_width = 0;
645         _windowID = XCreateWindow(_display, 
646                                   parentWindowID, 
647                                   left, top, 
648                                   width, height, border_width, 
649                                   BIT_DEPTH, 
650                                   InputOutput, 
651                                   _visual, 
652                                   CWBorderPixel | CWColormap | CWEventMask,  // TODO we need all this?
653                                   &attr);
654         
655 
656         // in case ConfigureNotiy isn't sent
657         // this create t
658         notifySize(width, height);
659 
660         // MAYDO: Is it necessary? seems not.
661         // XResizeWindow(_display, _windowID, width, height);
662 
663         _closeAtom = XInternAtom(_display, "WM_DELETE_WINDOW", False);
664         XSetWMProtocols(_display, _windowID, &_closeAtom , 1);
665         
666         if(_useXEmbed)
667         {
668             //Setup XEMBED atoms
669             _XEMBED = XInternAtom(_display, "_XEMBED", false);
670             _XEMBED_INFO = XInternAtom(_display, "_XEMBED_INFO", false);
671             uint[2] data = [XEMBED_VERSION, XEMBED_MAPPED];
672             enum XA_CARDINAL = 6;
673             XChangeProperty(_display, _windowID, _XEMBED_INFO,
674                             XA_CARDINAL, 32, PropModeReplace,
675                             cast(ubyte*) data, 2);
676 
677             if(parentWindow)
678             {
679                 XReparentWindow(_display, _windowID, parentWindowID, 0, 0);
680             }
681         }
682 
683         // MAYDO possible could be XMapWindow I guess
684         XMapRaised(_display, _windowID);
685         XSelectInput(_display, _windowID, windowEventMask());
686 
687         _graphicGC = XCreateGC(_display, _windowID, 0, null);
688     }
689 
690     void releaseX11Window()
691     {
692         // release all X11 resource allocated by createX11Window
693         lockX11();
694         XFreeCursor(_display, _hiddenCursor);
695         XFreePixmap(_display, _bitmapNoData);
696         XFreeColormap(_display, _cmap);
697         XFreeGC(_display, _graphicGC);
698         freeBackbuffer();
699         XDestroyWindow(_display, _windowID);
700 
701         // We need to flush all window events from the queue that are related to this window.
702         // Else another open instance may read message from this window and crash.
703         XSync(_display, false);
704         XEvent event;
705         while(true)
706         {
707             Bool found = XCheckWindowEvent(_display, _windowID, windowEventMask(), &event);
708             if (!found) break;
709         }
710 
711         unlockX11();
712     }
713 
714     // this frees _graphicImage, but not the data it points to since it is owned elsewhere
715     void freeBackbuffer()
716     {
717         if (_graphicImage)
718         {
719             lockX11();
720             XFree(_graphicImage);
721             unlockX11();
722             _graphicImage = null;
723         }
724     }    
725 
726     // which X11 events we are interested in
727 
728     uint windowEventMask() 
729     {
730         return ExposureMask 
731              | StructureNotifyMask
732              | KeyReleaseMask
733              | KeyPressMask
734              | ButtonReleaseMask
735              | ButtonPressMask
736              | PointerMotionMask
737              | EnterWindowMask
738              | LeaveWindowMask;
739     }
740 
741     void createHiddenCursor()
742     {
743         XColor black;
744         static char[] noData = [0,0,0,0,0,0,0,0];
745         black.red = black.green = black.blue = 0;
746 
747         _bitmapNoData = XCreateBitmapFromData(_display, _windowID, noData.ptr, 8, 8);
748         _hiddenCursor = XCreatePixmapCursor(_display, _bitmapNoData, _bitmapNoData, &black, &black, 0, 0);
749     }
750 }
751 
752 private:
753 
754 debug(logX11Window) 
755 {
756     extern(C) int X11ErrorHandler(Display* display, XErrorEvent* event)
757     {
758         char[128] buf;
759         lockX11();
760         XGetErrorText(display, event.error_code, buf.ptr, 128); 
761         unlockX11();
762         printf("Error = %s\n", buf.ptr);
763         assert(false);
764     }
765 }
766 
767 Key convertKeyFromX11(KeySym symbol)
768 {
769     switch(symbol)
770     {
771         case XK_space:
772             return Key.space;
773 
774         case XK_Up:
775             return Key.upArrow;
776 
777         case XK_Down:
778             return Key.downArrow;
779 
780         case XK_Left:
781             return Key.leftArrow;
782 
783         case XK_Right:
784             return Key.rightArrow;
785 
786         case XK_0: .. case XK_9:
787             return cast(Key)(Key.digit0 + (symbol - XK_0));
788 
789         case XK_KP_0: .. case XK_KP_9:
790             return cast(Key)(Key.digit0 + (symbol - XK_KP_0));
791 
792         case XK_A: .. case XK_Z:
793             return cast(Key)(Key.A + (symbol - XK_A));
794 
795         case XK_a: .. case XK_z:
796             return cast(Key)(Key.a + (symbol - XK_a));
797 
798         case XK_Return:
799         case XK_KP_Enter:
800             return Key.enter;
801 
802         case XK_Escape:
803             return Key.escape;
804 
805         case XK_Delete:
806             return Key.suppr;
807 
808         case XK_BackSpace:
809             return Key.backspace;
810 
811         default:
812             return Key.unsupported;
813     }
814 }
815 
816 MouseState mouseStateFromX11(uint state) 
817 {
818     return MouseState(
819         (state & Button1Mask) == Button1Mask,
820         (state & Button3Mask) == Button3Mask,
821         (state & Button2Mask) == Button2Mask,
822         false, false,
823         (state & ControlMask) == ControlMask,
824         (state & ShiftMask) == ShiftMask,
825         (state & Mod1Mask) == Mod1Mask);
826 }
827 
828 // Check if the X11 operation has completed correctly
829 void checkX11Status(Status err)
830 {
831     if (err == 0)
832         assert(false); // There was an error
833 }
834 
835 
836 // <X11 initialization>
837 
838 shared(int) _x11Counter = 0;
839 __gshared Display* _display;
840 
841 /// Protects every X11 call. This is because as a plugin we cannot call XInitThreads() 
842 /// to ensure thread safety.
843 /// Note that like the connection, this is shared across plugin instances...
844 __gshared UncheckedMutex _x11Mutex;
845 
846 void lockX11()
847 {
848     _x11Mutex.lock();
849 }
850 
851 void unlockX11()
852 {
853     _x11Mutex.unlock();
854 }
855 
856 void acquireX11(bool isAChildwindow)
857 {
858     // Note: this is racey, if you open two plugins exactly at once, it might race
859     if (atomicOp!"+="(_x11Counter, 1) == 1)
860     {
861         _x11Mutex = makeMutex();
862         debug(logX11Window)
863         {
864             XSetErrorHandler(&X11ErrorHandler);
865         }
866 
867         // "On a POSIX-conformant system, if the display_name is NULL, 
868         // it defaults to the value of the DISPLAY environment variable."
869         _display = XOpenDisplay(null);
870 
871         if(_display == null)
872             assert(false);
873 
874         debug(logX11Window)
875             XSynchronize(_display, False);
876     }
877     else
878     {
879         usleep(20); // Dumb protection against X11 initialization race.
880     }
881 }
882 
883 void releaseX11()
884 {
885     if (atomicOp!"-="(_x11Counter, 1) == 0)
886     {
887         XCloseDisplay(_display);
888         _x11Mutex.destroy();
889     }
890 }
891 
892 
893 string X11EventTypeString(int type)
894 {
895     string s = "Unknown event";
896     if (type == 2) s = "KeyPress";
897     if (type == 3) s = "KeyRelease";
898     if (type == 4) s = "ButtonPress";
899     if (type == 5) s = "ButtonRelease";
900     if (type == 6) s = "MotionNotify";
901     if (type == 7) s = "EnterNotify";
902     if (type == 8) s = "LeaveNotify";
903     if (type == 9) s = "FocusIn";
904     if (type == 10) s = "FocusOut";
905     if (type == 11) s = "KeymapNotify";
906     if (type == 12) s = "Expose";
907     if (type == 13) s = "GraphicsExpose";
908     if (type == 14) s = "NoExpose";
909     if (type == 15) s = "VisibilityNotify";
910     if (type == 16) s = "CreateNotify";
911     if (type == 17) s = "DestroyNotify";
912     if (type == 18) s = "UnmapNotify";
913     if (type == 19) s = "MapNotify";
914     if (type == 20) s = "MapRequest";
915     if (type == 21) s = "ReparentNotify";
916     if (type == 22) s = "ConfigureNotify";
917     if (type == 23) s = "ConfigureRequest";
918     if (type == 24) s = "GravityNotify";
919     if (type == 25) s = "ResizeRequest";
920     if (type == 26) s = "CirculateNotify";
921     if (type == 27) s = "CirculateRequest";
922     if (type == 28) s = "PropertyNotify";
923     if (type == 29) s = "SelectionClear";
924     if (type == 30) s = "SelectionRequest";
925     if (type == 31) s = "SelectionNotify";
926     if (type == 32) s = "ColormapNotify";
927     if (type == 33) s = "ClientMessage";
928     if (type == 34) s = "MappingNotify";
929     if (type == 35) s = "GenericEvent";
930     if (type == 36) s = "LASTEvent";
931     return s;
932 }
933 
934 int convertCursorToX11CursorFont(MouseCursor cursor)
935 {
936     switch(cursor)
937     {
938 
939         case cursor.linkSelect:
940             return 60;
941         case cursor.drag:
942             return 58;
943         case cursor.move:
944             return 34;
945         case cursor.horizontalResize:
946             return 108;
947         case cursor.verticalResize:
948             return 116;
949         case cursor.diagonalResize:
950             return 14;
951         case cursor.pointer:
952         default:
953             return 2;
954     }
955 }