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