1 module dlangui.platforms.x11.x11app;
2 
3 public import dlangui.core.config;
4 static if (BACKEND_X11):
5 
6 import dlangui.core.logger;
7 import dlangui.core.events;
8 import dlangui.core.files;
9 import dlangui.core.types : toUTF8;
10 import dlangui.graphics.drawbuf;
11 import dlangui.graphics.fonts;
12 import dlangui.graphics.ftfonts;
13 import dlangui.graphics.resources;
14 import dlangui.widgets.styles;
15 import dlangui.widgets.widget;
16 import dlangui.platforms.common.platform;
17 
18 import core.stdc.string;
19 import std.stdio;
20 import std.string;
21 import std.utf;
22 private import core.stdc.config : c_ulong, c_long;
23 
24 import x11.Xlib;
25 import x11.Xutil;
26 import x11.Xtos;
27 import x11.Xatom;
28 import x11.X;
29 
30 static if (ENABLE_OPENGL) {
31     import bindbc.opengl;
32     import dlangui.graphics.gldrawbuf;
33     import dlangui.graphics.glsupport;
34     import glx.glx;
35 
36     private __gshared XVisualInfo * x11visual;
37     private __gshared Colormap x11cmap;
38     private __gshared bool _gl3Reloaded = false;
39 }
40 
41 //pragma(lib, "X11");
42 
43 alias XWindow = x11.Xlib.Window;
44 alias DWindow = dlangui.platforms.common.platform.Window;
45 
46 private struct MwmHints
47 {
48     int flags;
49     int functions;
50     int decorations;
51     int input_mode;
52     int status;
53 }
54 
55 private enum
56 {
57     MWM_HINTS_FUNCTIONS = (1L << 0),
58     MWM_HINTS_DECORATIONS =  (1L << 1),
59 
60     MWM_FUNC_ALL = (1L << 0),
61     MWM_FUNC_RESIZE = (1L << 1),
62     MWM_FUNC_MOVE = (1L << 2),
63     MWM_FUNC_MINIMIZE = (1L << 3),
64     MWM_FUNC_MAXIMIZE = (1L << 4),
65     MWM_FUNC_CLOSE = (1L << 5)
66 }
67 
68 private __gshared
69 {
70     Display * x11display;
71     Display * x11display2;
72     int x11screen;
73     XIM xim;
74 
75     bool _enableOpengl = false;
76 
77     Cursor[CursorType.Hand + 1] x11cursors;
78 
79     Atom   atom_UTF8_STRING;
80     Atom   atom_CLIPBOARD;
81     Atom   atom_TARGETS;
82 
83     Atom   atom_WM_PROTOCOLS;
84     Atom   atom_WM_DELETE_WINDOW;
85 
86     Atom   atom_NET_WM_ICON;
87     Atom   atom_NET_WM_NAME;
88     Atom   atom_NET_WM_ICON_NAME;
89 
90     Atom   atom_NET_WM_STATE;
91     Atom   atom_NET_WM_STATE_MODAL;
92     Atom   atom_NET_WM_STATE_MAXIMIZED_VERT;
93     Atom   atom_NET_WM_STATE_MAXIMIZED_HORZ;
94     Atom   atom_NET_WM_STATE_HIDDEN;
95     Atom   atom_NET_WM_STATE_FULLSCREEN;
96 
97     Atom   atom_MOTIF_WM_HINTS;
98 
99     Atom   atom_DLANGUI_TIMER_EVENT;
100     Atom   atom_DLANGUI_TASK_EVENT;
101     Atom   atom_DLANGUI_CLOSE_WINDOW_EVENT;
102     Atom   atom_DLANGUI_CLIPBOARD_BUFFER;
103     Atom   atom_DLANGUI_REDRAW_EVENT;
104 }
105 
106 static void setupX11Atoms()
107 {
108     assert(x11display !is null, "X Connection must be established before getting atoms");
109     //TODO: not sure which atoms should be taken with or without onlyIfExists flag
110     atom_UTF8_STRING = XInternAtom(x11display, "UTF8_STRING", True);
111     atom_CLIPBOARD   = XInternAtom(x11display, "CLIPBOARD", True);
112     atom_TARGETS     = XInternAtom(x11display, "TARGETS", True);
113     atom_WM_PROTOCOLS = XInternAtom(x11display, "WM_PROTOCOLS", False);
114     atom_WM_DELETE_WINDOW = XInternAtom(x11display, "WM_DELETE_WINDOW", False);
115     atom_NET_WM_ICON = XInternAtom(x11display, "_NET_WM_ICON", True);
116     atom_NET_WM_NAME = XInternAtom(x11display, "_NET_WM_NAME", True);
117     atom_NET_WM_ICON_NAME = XInternAtom(x11display, "_NET_WM_ICON_NAME", True);
118     atom_NET_WM_STATE = XInternAtom(x11display, "_NET_WM_STATE", True);
119     atom_NET_WM_STATE_MODAL = XInternAtom(x11display, "_NET_WM_STATE_MODAL", True);
120     atom_NET_WM_STATE_MAXIMIZED_VERT = XInternAtom(x11display, "_NET_WM_STATE_MAXIMIZED_VERT", True);
121     atom_NET_WM_STATE_MAXIMIZED_HORZ = XInternAtom(x11display, "_NET_WM_STATE_MAXIMIZED_HORZ", True);
122     atom_NET_WM_STATE_HIDDEN = XInternAtom(x11display, "_NET_WM_STATE_HIDDEN", True);
123     atom_NET_WM_STATE_FULLSCREEN = XInternAtom(x11display, "_NET_WM_STATE_FULLSCREEN", True);
124     atom_MOTIF_WM_HINTS = XInternAtom(x11display, "_MOTIF_WM_HINTS", True);
125 
126     atom_DLANGUI_TIMER_EVENT     = XInternAtom(x11display, "DLANGUI_TIMER_EVENT", False);
127     atom_DLANGUI_TASK_EVENT     = XInternAtom(x11display, "DLANGUI_TASK_EVENT", False);
128     atom_DLANGUI_CLOSE_WINDOW_EVENT = XInternAtom(x11display, "DLANGUI_CLOSE_WINDOW_EVENT", False);
129     atom_DLANGUI_CLIPBOARD_BUFFER = XInternAtom(x11display, "DLANGUI_CLIPBOARD_BUFFER", False);
130     atom_DLANGUI_REDRAW_EVENT = XInternAtom(x11display, "DLANGUI_REDRAW_EVENT", False);
131 }
132 
133 // Cursor font constants
134 enum {
135     XC_X_cursor=0,
136     XC_arrow=2,
137     XC_based_arrow_down=4,
138     XC_based_arrow_up=6,
139     XC_boat = 8,
140     XC_bogosity = 10,
141     XC_bottom_left_corner=12,
142     XC_bottom_right_corner=14,
143     XC_bottom_side=16,
144     XC_bottom_tee=18,
145     XC_box_spiral=20,
146     XC_center_ptr=22,
147     XC_circle=24,
148     XC_clock=26,
149     XC_coffee_mug=28,
150     XC_cross=30,
151     XC_cross_reverse=32,
152     XC_crosshair=34,
153     XC_diamond_cross=36,
154     XC_dot=38,
155     XC_dotbox=40,
156     XC_double_arrow=42,
157     XC_draft_large=44,
158     XC_draft_small=46,
159     XC_draped_box=48,
160     XC_exchange=50,
161     XC_fleur=52,
162     XC_gobbler=54,
163     XC_gumby=56,
164     XC_hand1=58,
165     XC_hand2=60,
166     XC_heart=62,
167     XC_icon=64,
168     XC_iron_cross=66,
169     XC_left_ptr=68,
170     XC_left_side=70,
171     XC_left_tee=72,
172     XC_leftbutton=74,
173     XC_ll_angle=76,
174     XC_lr_angle=78,
175     XC_man=80,
176     XC_middlebutton=82,
177     XC_mouse=84,
178     XC_pencil=86,
179     XC_pirate=88,
180     XC_plus=90,
181     XC_question_arrow=92,
182     XC_right_ptr=94,
183     XC_right_side=96,
184     XC_right_tee=98,
185     XC_rightbutton=100,
186     XC_rtl_logo=102,
187     XC_sailboat=104,
188     XC_sb_down_arrow=106,
189     XC_sb_h_double_arrow=108,
190     XC_sb_left_arrow=110,
191     XC_sb_right_arrow=112,
192     XC_sb_up_arrow=114,
193     XC_sb_v_double_arrow=116,
194     XC_shuttle=118,
195     XC_sizing=120,
196     XC_spider=122,
197     XC_spraycan=124,
198     XC_star=126,
199     XC_target=128,
200     XC_tcross=130,
201     XC_top_left_arrow=132,
202     XC_top_left_corner=134,
203     XC_top_right_corner=136,
204     XC_top_side=138,
205     XC_top_tee=140,
206     XC_trek=142,
207     XC_ul_angle=144,
208     XC_umbrella=146,
209     XC_ur_angle=148,
210     XC_watch=150,
211     XC_xterm=152,
212 }
213 
214 private GC createGC(Display* display, XWindow win)
215 {
216     GC gc;                /* handle of newly created GC.  */
217     uint valuemask = GCFunction | GCBackground | GCForeground | GCPlaneMask;        /* which values in 'values' to  */
218     /* check when creating the GC.  */
219     XGCValues values;            /* initial values for the GC.   */
220     values.plane_mask = AllPlanes;
221     int screen_num = DefaultScreen(display);
222     values.function_ = GXcopy;
223     values.background = WhitePixel(display, screen_num);
224     values.foreground = BlackPixel(display, screen_num);
225 
226     gc = XCreateGC(display, win, valuemask, &values);
227     if (!gc) {
228         Log.e("X11: Cannot create GC");
229         return null;
230         //fprintf(stderr, "XCreateGC: \n");
231     }
232 
233     uint line_width = 2;        /* line width for the GC.       */
234     int line_style = LineSolid;        /* style for lines drawing and  */
235     int cap_style = CapButt;        /* style of the line's edje and */
236     int join_style = JoinBevel;        /*  joined lines.        */
237 
238     /* define the style of lines that will be drawn using this GC. */
239     XSetLineAttributes(display, gc,
240         line_width, line_style, cap_style, join_style);
241 
242     /* define the fill style for the GC. to be 'solid filling'. */
243     XSetFillStyle(display, gc, FillSolid);
244 
245     return gc;
246 }
247 
248 class X11Window : DWindow {
249     protected X11Platform _platform;
250     protected dstring _caption;
251     protected XWindow _win;
252     protected GC _gc;
253     private __gshared XIC xic;
254 
255     X11Window[] _children;
256     X11Window _parent;
257 
258     int _cachedWidth, _cachedHeight;
259 
260     static if (ENABLE_OPENGL) {
261         GLXContext _glc;
262     }
263 
264     this(X11Platform platform, dstring caption, DWindow parent, uint flags, uint width = 0, uint height = 0) {
265         _platform = platform;
266         //backgroundColor = 0xFFFFFF;
267         debug Log.d("X11Window: Creating window");
268         if (width == 0)
269             width = 500;
270         if (height == 0)
271             height = 300;
272         _cachedWidth = _dx = width;
273         _cachedHeight = _dy = height;
274         _flags = flags;
275 
276         /* get the colors black and white (see section for details) */
277         c_ulong black, white;
278         black = BlackPixel(x11display, x11screen);    /* get color black */
279         white = WhitePixel(x11display, x11screen);  /* get color white */
280 
281         /* once the display is initialized, create the window.
282                This window will be have be 200 pixels across and 300 down.
283                It will have the foreground white and background black
284         */
285 
286         Log.d("Creating window of size ", _dx, "x", _dy);
287         static if (true) {
288             XSetWindowAttributes swa;
289             uint swamask = CWEventMask | CWBackPixel;
290             swa.background_pixel = white;
291             swa.event_mask = KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask |
292                 EnterWindowMask | LeaveWindowMask | PointerMotionMask | ButtonMotionMask | ExposureMask | VisibilityChangeMask |
293                     FocusChangeMask | KeymapStateMask | StructureNotifyMask | PropertyChangeMask;
294             Visual * visual = DefaultVisual(x11display, x11screen);
295             int depth = DefaultDepth(x11display, x11screen);
296             static if (ENABLE_OPENGL) {
297                 if (_enableOpengl) {
298                     swamask |= CWColormap;
299                     swa.colormap = x11cmap;
300                     visual = cast(Visual*)x11visual.visual;
301                     depth = x11visual.depth;
302                 }
303             }
304 
305             _win = XCreateWindow(x11display, DefaultRootWindow(x11display),
306                 0, 0,
307                 _dx, _dy, 0,
308                 depth,
309                 InputOutput,
310                 visual,
311                 swamask,
312                 &swa);
313 //            _win = XCreateSimpleWindow(x11display, DefaultRootWindow(x11display),
314 //                0, 0,
315 //                _dx, _dy, 5, black, white);
316         } else {
317             XSetWindowAttributes attr;
318             attr.do_not_propagate_mask = 0;
319             attr.override_redirect = True;
320             attr.cursor = Cursor();
321             attr.event_mask = ExposureMask | KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask;
322             attr.background_pixel = white;
323 
324             _win = XCreateWindow(x11display, DefaultRootWindow(x11display),
325                 0, 0,
326                 _dx, _dy, 5,
327                 CopyFromParent, // depth
328                 CopyFromParent, // class
329                 cast(Visual*)CopyFromParent, // visual
330                 CWEventMask|CWBackPixel|CWCursor|CWDontPropagate,
331                 &attr
332                 );
333             if (!_win)
334                 return;
335 
336         }
337         windowCaption = caption;
338         XSetWMProtocols(x11display, _win, &atom_WM_DELETE_WINDOW, 1);
339         _windowState = WindowState.hidden;
340 
341         auto classHint = XAllocClassHint();
342         if (classHint) {
343             classHint.res_name = platform._classname;
344             classHint.res_class = platform._classname;
345             XSetClassHint(x11display, _win, classHint);
346             XFree(classHint);
347         }
348 
349         _children.reserve(20);
350         _parent = cast(X11Window) parent;
351         if (_parent)
352             _parent._children ~= this;
353 
354         if (!(flags & WindowFlag.Resizable)) {
355             XSizeHints sizeHints;
356             sizeHints.min_width = width;
357             sizeHints.min_height = height;
358             sizeHints.max_width = width;
359             sizeHints.max_height = height;
360             sizeHints.flags = PMaxSize | PMinSize;
361             XSetWMNormalHints(x11display, _win, &sizeHints);
362         }
363         if (flags & WindowFlag.Fullscreen) {
364             if (atom_NET_WM_STATE_FULLSCREEN != None) {
365                 changeWindowState(_NET_WM_STATE_ADD, atom_NET_WM_STATE_FULLSCREEN);
366             }
367             else
368                 Log.w("Missing _NET_WM_STATE_FULLSCREEN atom");
369         }
370         if (flags & WindowFlag.Borderless) {
371             if (atom_MOTIF_WM_HINTS != None) {
372                 MwmHints hints;
373                 hints.flags = MWM_HINTS_DECORATIONS;
374                 XChangeProperty(x11display, _win, atom_MOTIF_WM_HINTS, atom_MOTIF_WM_HINTS, 32, PropModeReplace, cast(ubyte*)&hints, hints.sizeof / 4);
375             }
376         }
377         if (flags & WindowFlag.Modal) {
378             if (_parent) {
379                 XSetTransientForHint(x11display, _win, _parent._win);
380             } else {
381                 Log.w("Top-level modal window");
382             }
383             if (atom_NET_WM_STATE_MODAL != None) {
384                 changeWindowState(_NET_WM_STATE_ADD, atom_NET_WM_STATE_MODAL);
385             } else {
386                 Log.w("Missing _NET_WM_STATE_MODAL atom");
387             }
388         }
389         /* this routine determines which types of input are allowed in
390                the input.  see the appropriate section for details...
391         */
392 //        XSelectInput(x11display, _win, KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask |
393 //            EnterWindowMask | LeaveWindowMask | PointerMotionMask | ButtonMotionMask | ExposureMask | VisibilityChangeMask |
394 //            FocusChangeMask | KeymapStateMask | StructureNotifyMask);
395 
396         /* create the Graphics Context */
397         _gc = createGC(x11display, _win);
398         //_gc = XCreateGC(x11display, _win, 0, cast(XGCValues*)null);
399         //Log.d("X11Window: windowId=", _win, " gc=", _gc);
400 
401 
402 
403 
404         /* here is another routine to set the foreground and background
405                colors _currently_ in use in the window.
406         */
407         //XSetBackground(x11display, _gc, white);
408         //XSetForeground(x11display, _gc, black);
409 
410         /* clear the window and bring it on top of the other windows */
411         //XClearWindow(x11display, _win);
412         //XFlush(x11display);
413 
414         handleWindowStateChange(WindowState.unspecified, Rect(0, 0, _dx, _dy));
415 
416         if (platform.defaultWindowIcon.length != 0)
417             windowIcon = drawableCache.getImage(platform.defaultWindowIcon);
418     }
419 
420     ~this() {
421         debug Log.d("Destroying X11 window");
422         if (timer) {
423             timer.stop();
424         }
425         if (_parent) {
426             import std.algorithm : countUntil, remove;
427             ptrdiff_t index = countUntil(_parent._children,this);
428             if (index > -1 ) {
429                 _parent._children = _parent._children.remove(index);
430             }
431             _parent = null;
432         }
433         static if (ENABLE_OPENGL) {
434             if (_glc) {
435                 glXDestroyContext(x11display, _glc);
436                 _glc = null;
437             }
438         }
439         if (_drawbuf)
440             destroy(_drawbuf);
441         if (_gc) {
442             XFreeGC(x11display, _gc);
443             _gc = null;
444         }
445         if (_win) {
446             XDestroyWindow(x11display, _win);
447             _win = 0;
448         }
449     }
450 
451     private bool hasVisibleModalChild() {
452         foreach (X11Window w;_children) {
453             if (w.flags & WindowFlag.Modal && w._windowState != WindowState.hidden)
454                 return true;
455         }
456         return false;
457     }
458 
459     /// show window
460     override void show() {
461         Log.d("X11Window.show");
462         XMapRaised(x11display, _win);
463 
464         static if (ENABLE_OPENGL) {
465             if (_enableOpengl) {
466                 _glc = glXCreateContext(x11display, x11visual, null, GL_TRUE);
467                 if (!_glc) {
468                     _enableOpengl = false;
469                 } else {
470                     glXMakeCurrent(x11display, cast(uint)_win, _glc);
471                     _enableOpengl = initGLSupport(_platform.GLVersionMajor < 3);
472                     if (!_enableOpengl && _glc) {
473                         glXDestroyContext(x11display, _glc);
474                         _glc = null;
475                     }
476                 }
477             }
478             if (_enableOpengl) {
479                 Log.d("Open GL support is enabled");
480             } else {
481                 Log.d("Open GL support is disabled");
482             }
483         }
484         if (_mainWidget) {
485             _mainWidget.measure(SIZE_UNSPECIFIED, SIZE_UNSPECIFIED);
486             if (flags & WindowFlag.MeasureSize)
487                 resizeWindow(Point(_mainWidget.measuredWidth, _mainWidget.measuredHeight));
488             else
489                 adjustWindowOrContentSize(_mainWidget.measuredWidth, _mainWidget.measuredHeight);
490 
491             adjustPositionDuringShow();
492 
493             _mainWidget.setFocus();
494         }
495         XFlush(x11display);
496     }
497 
498     override protected void handleWindowStateChange(WindowState newState, Rect newWindowRect = RECT_VALUE_IS_NOT_SET) {
499         super.handleWindowStateChange(newState, newWindowRect);
500     }
501 
502     protected final void changeWindowState(int action, Atom firstProperty, Atom secondProperty = None) nothrow
503     {
504         XEvent ev;
505         memset(&ev, 0, ev.sizeof);
506         ev.xany.type = ClientMessage;
507         ev.xclient.window = _win;
508         ev.xclient.message_type = atom_NET_WM_STATE;
509         ev.xclient.format = 32;
510         ev.xclient.data.l[0] = action;
511         ev.xclient.data.l[1] = firstProperty;
512         if (secondProperty != None)
513             ev.xclient.data.l[2] = secondProperty;
514         ev.xclient.data.l[3] = 0;
515         XSendEvent(x11display, RootWindow(x11display, x11screen), false, SubstructureNotifyMask|SubstructureRedirectMask, &ev);
516     }
517 
518     protected enum {
519         _NET_WM_STATE_REMOVE = 0,
520         _NET_WM_STATE_ADD,
521         _NET_WM_STATE_TOGGLE
522     }
523 
524     override bool setWindowState(WindowState newState, bool activate = false, Rect newWindowRect = RECT_VALUE_IS_NOT_SET) {
525         if (_win == None) {
526             return false;
527         }
528         bool result = false;
529         switch(newState) {
530             case WindowState.maximized:
531                 if (atom_NET_WM_STATE != None && atom_NET_WM_STATE_MAXIMIZED_HORZ != None && atom_NET_WM_STATE_MAXIMIZED_VERT != None) {
532                     changeWindowState(_NET_WM_STATE_ADD, atom_NET_WM_STATE_MAXIMIZED_HORZ, atom_NET_WM_STATE_MAXIMIZED_VERT);
533                     result = true;
534                 }
535                 break;
536             case WindowState.minimized:
537                 if (XIconifyWindow(x11display, _win, x11screen))
538                     result = true;
539                 break;
540             case WindowState.hidden:
541                 XUnmapWindow(x11display, _win);
542                 result = true;
543                 break;
544             case WindowState.normal:
545                 if (atom_NET_WM_STATE != None &&
546                     atom_NET_WM_STATE_MAXIMIZED_HORZ != None &&
547                     atom_NET_WM_STATE_MAXIMIZED_VERT != None &&
548                     atom_NET_WM_STATE_HIDDEN != None)
549                 {
550                     changeWindowState(_NET_WM_STATE_REMOVE, atom_NET_WM_STATE_MAXIMIZED_HORZ, atom_NET_WM_STATE_MAXIMIZED_VERT);
551                     changeWindowState(_NET_WM_STATE_REMOVE, atom_NET_WM_STATE_HIDDEN);
552                     changeWindowState(_NET_WM_STATE_REMOVE, atom_NET_WM_STATE_FULLSCREEN);
553                     result = true;
554                 }
555                 break;
556             case WindowState.fullscreen:
557                 if (atom_NET_WM_STATE != None && atom_NET_WM_STATE_FULLSCREEN != None) {
558                     changeWindowState(_NET_WM_STATE_ADD, atom_NET_WM_STATE_FULLSCREEN);
559                     result = true;
560                 }
561                 break;
562             default:
563                 break;
564         }
565 
566         // change size and/or position
567         bool rectChanged = false;
568         if (newWindowRect != RECT_VALUE_IS_NOT_SET && (newState == WindowState.normal || newState == WindowState.unspecified)) {
569             // change position
570             if (newWindowRect.top != int.min && newWindowRect.left != int.min) {
571                 XMoveWindow(x11display, _win, newWindowRect.left, newWindowRect.top);
572                 rectChanged = true;
573                 result = true;
574             }
575 
576             // change size
577             if (newWindowRect.bottom != int.min && newWindowRect.right != int.min) {
578                 if (!(flags & WindowFlag.Resizable)) {
579                     XSizeHints sizeHints;
580                     sizeHints.min_width = newWindowRect.right;
581                     sizeHints.min_height = newWindowRect.bottom;
582                     sizeHints.max_width = newWindowRect.right;
583                     sizeHints.max_height = newWindowRect.bottom;
584                     sizeHints.flags = PMaxSize | PMinSize;
585                     XSetWMNormalHints(x11display, _win, &sizeHints);
586                 }
587                 XResizeWindow(x11display, _win, newWindowRect.right, newWindowRect.bottom);
588                 rectChanged = true;
589                 result = true;
590             }
591         }
592 
593         if (activate) {
594             XMapRaised(x11display, _win);
595             result = true;
596         }
597         XFlush(x11display);
598 
599         //needed here to make _windowRect and _windowState valid
600         //example: change size by resizeWindow() and make some calculations using windowRect
601         if (rectChanged) {
602             handleWindowStateChange(newState, Rect(newWindowRect.left == int.min ? _windowRect.left : newWindowRect.left,
603                 newWindowRect.top == int.min ? _windowRect.top : newWindowRect.top, newWindowRect.right == int.min ? _windowRect.right : newWindowRect.right,
604                 newWindowRect.bottom == int.min ? _windowRect.bottom : newWindowRect.bottom));
605         }
606         else
607             handleWindowStateChange(newState, RECT_VALUE_IS_NOT_SET);
608 
609 
610         return result;
611     }
612 
613     override @property DWindow parentWindow() {
614         return _parent;
615     }
616 
617     private bool _isActive;
618     override protected void handleWindowActivityChange(bool isWindowActive) {
619         _isActive = isWindowActive;
620         super.handleWindowActivityChange(isWindowActive);
621     }
622 
623     override @property bool isActive() {
624         return _isActive;
625     }
626 
627     override @property dstring windowCaption() const {
628         return _caption;
629     }
630 
631     override @property void windowCaption(dstring caption) {
632         _caption = caption;
633         auto captionc = _caption.toUTF8;
634         auto captionz = cast(ubyte*)captionc.toStringz;
635         XTextProperty nameProperty;
636         nameProperty.value = captionz;
637         nameProperty.encoding = atom_UTF8_STRING;
638         nameProperty.format = 8;
639         nameProperty.nitems = cast(uint)captionc.length;
640         XStoreName(x11display, _win, cast(char*)captionz); // this may not support unicode
641         XSetWMName(x11display, _win, &nameProperty);
642         XChangeProperty(x11display, _win, atom_NET_WM_NAME, atom_UTF8_STRING, 8, PropModeReplace, captionz, cast(int)captionc.length);
643         //XFlush(x11display); //TODO: not sure if XFlush is required
644     }
645 
646     /// sets window icon
647     override @property void windowIcon(DrawBufRef buf) {
648         ColorDrawBuf icon = cast(ColorDrawBuf)buf.get;
649         if (!icon) {
650             Log.e("Trying to set null icon for window");
651             return;
652         }
653         immutable int iconw = 32;
654         immutable int iconh = 32;
655         ColorDrawBuf iconDraw = new ColorDrawBuf(iconw, iconh);
656         scope(exit) destroy(iconDraw);
657         iconDraw.fill(0xFF000000);
658         iconDraw.drawRescaled(Rect(0, 0, iconw, iconh), icon, Rect(0, 0, icon.width, icon.height));
659         iconDraw.invertAndPreMultiplyAlpha();
660         c_long[] propData = new c_long[2 + iconw * iconh];
661         propData[0] = iconw;
662         propData[1] = iconh;
663         auto iconData = iconDraw.scanLine(0);
664         foreach(i; 0..iconw*iconh) {
665             propData[i+2] = iconData[i];
666         }
667         XChangeProperty(x11display, _win, atom_NET_WM_ICON, XA_CARDINAL, 32, PropModeReplace, cast(ubyte*)propData.ptr, cast(int)propData.length);
668     }
669 
670     uint _lastRedrawEventCode;
671     /// request window redraw
672     override void invalidate() {
673         XEvent ev;
674         ev.xclient.type = ClientMessage;
675         ev.xclient.message_type = atom_DLANGUI_REDRAW_EVENT;
676         ev.xclient.window = _win;
677         ev.xclient.display = x11display;
678         ev.xclient.format = 32;
679         ev.xclient.data.l[0] = ++_lastRedrawEventCode;
680         XSendEvent(x11display, _win, false, StructureNotifyMask, &ev);
681         XFlush(x11display);
682     }
683 
684     /// close window
685     override void close() {
686         Log.d("X11Window.close()");
687         _platform.closeWindow(this);
688     }
689 
690     ColorDrawBuf _drawbuf;
691     protected void drawUsingBitmap() {
692         if (_dx > 0 && _dy > 0) {
693             //Log.d("drawUsingBitmap()");
694             // prepare drawbuf
695             if (_drawbuf is null)
696                 _drawbuf = new ColorDrawBuf(_dx, _dy);
697             else
698                 _drawbuf.resize(_dx, _dy);
699             _drawbuf.resetClipping();
700             // draw widgets into buffer
701             _drawbuf.fill(backgroundColor);
702             onDraw(_drawbuf);
703             // draw buffer on X11 window
704             XImage img;
705             img.width = _drawbuf.width;
706             img.height = _drawbuf.height;
707             img.xoffset = 0;
708             img.format = ZPixmap;
709             img.data = cast(char*)_drawbuf.scanLine(0);
710             img.bitmap_unit = 32;
711             img.bitmap_pad = 32;
712             img.bitmap_bit_order = LSBFirst;
713             img.depth = 24;
714             img.chars_per_line = _drawbuf.width * 4;
715             img.bits_per_pixel = 32;
716             img.red_mask = 0xFF0000;
717             img.green_mask = 0x00FF00;
718             img.blue_mask = 0x0000FF;
719             XInitImage(&img);
720             //XSetClipOrigin(x11display, _gc, 0, 0);
721             XPutImage(x11display, _win,
722                 _gc, //DefaultGC(x11display, DefaultScreen(x11display)),
723                 &img,
724                 0, 0, 0, 0,
725                 _drawbuf.width,
726                 _drawbuf.height);
727             //XFlush(x11display); // no need to XFlush since it will be called in event loop
728         }
729     }
730 
731     protected void drawUsingOpengl() {
732         static if (ENABLE_OPENGL) {
733             //Log.d("drawUsingOpengl()");
734             glXMakeCurrent(x11display, cast(uint)_win, _glc);
735             glDisable(GL_DEPTH_TEST);
736             glViewport(0, 0, _dx, _dy);
737             float a = 1.0f;
738             float r = ((_backgroundColor >> 16) & 255) / 255.0f;
739             float g = ((_backgroundColor >> 8) & 255) / 255.0f;
740             float b = ((_backgroundColor >> 0) & 255) / 255.0f;
741             glClearColor(r, g, b, a);
742             glClear(GL_COLOR_BUFFER_BIT);
743             GLDrawBuf buf = new GLDrawBuf(_dx, _dy, false);
744             buf.beforeDrawing();
745             onDraw(buf);
746             buf.afterDrawing();
747             glXSwapBuffers(x11display, cast(uint)_win);
748             destroy(buf);
749         }
750     }
751 
752     void redraw() {
753         _lastRedrawEventCode = 0;
754         //Use values cached by ConfigureNotify to avoid XGetWindowAttributes call.
755         //XWindowAttributes window_attributes_return;
756         //XGetWindowAttributes(x11display, _win, &window_attributes_return);
757         //Log.d(format("XGetWindowAttributes reported size %d, %d", window_attributes_return.width, window_attributes_return.height));
758         immutable width = _cachedWidth;
759         immutable height = _cachedHeight;
760         if (width > 0 && height > 0)
761             onResize(width, height);
762         debug(x11) Log.d(format("redraw(%d, %d)", width, height));
763         if (_enableOpengl)
764             drawUsingOpengl();
765         else
766             drawUsingBitmap();
767     }
768 
769     protected ButtonDetails _lbutton;
770     protected ButtonDetails _mbutton;
771     protected ButtonDetails _rbutton;
772 
773     // x11 gives flags from time prior event so if left button is pressed there is not Button1Mask
774     ushort convertMouseFlags(uint flags, MouseButton btn, bool pressed) {
775         ushort res = 0;
776         if (btn == MouseButton.Left) {
777             if (pressed)
778                 res |= MouseFlag.LButton;
779             else
780                 res &= ~MouseFlag.LButton;
781         }
782         else
783             if (flags & Button1Mask)
784                 res |= MouseFlag.LButton;
785 
786         if (btn == MouseButton.Middle) {
787             if (pressed)
788                 res |= MouseFlag.MButton;
789             else
790                 res &= ~MouseFlag.MButton;
791         }
792         else
793             if (flags & Button2Mask)
794                 res |= MouseFlag.MButton;
795 
796         if (btn == MouseButton.Right) {
797             if (pressed)
798                 res |= MouseFlag.RButton;
799             else
800                 res &= ~MouseFlag.RButton;
801         }
802         else
803             if (flags & Button3Mask)
804                 res |= MouseFlag.RButton;
805 
806         return res;
807     }
808 
809     MouseButton convertMouseButton(uint button) {
810         if (button == Button1)
811             return MouseButton.Left;
812         if (button == Button2)
813             return MouseButton.Middle;
814         if (button == Button3)
815             return MouseButton.Right;
816         return MouseButton.None;
817     }
818 
819     short lastx;
820     short lasty;
821     uint _keyFlags;
822 
823     void processMouseEvent(MouseAction action, uint button, uint state, int x, int y) {
824         MouseEvent event = null;
825         ushort lastFlags;
826 
827         if (action == MouseAction.Wheel) {
828             // handle wheel
829             short wheelDelta = cast(short)y;
830             if (_keyFlags & KeyFlag.Shift)
831                 lastFlags |= MouseFlag.Shift;
832             else
833                 lastFlags &= ~MouseFlag.Shift;
834             if (_keyFlags & KeyFlag.Control)
835                 lastFlags |= MouseFlag.Control;
836             else
837                 lastFlags &= ~MouseFlag.Control;
838             if (_keyFlags & KeyFlag.Alt)
839                 lastFlags |= MouseFlag.Alt;
840             else
841                 lastFlags &= ~MouseFlag.Alt;
842             if (wheelDelta)
843                 event = new MouseEvent(action, MouseButton.None, lastFlags, lastx, lasty, wheelDelta);
844         } else {
845             MouseButton btn = convertMouseButton(button);
846             lastFlags = convertMouseFlags(state, btn, action == MouseAction.ButtonDown);
847 
848             if (_keyFlags & KeyFlag.Shift)
849                 lastFlags |= MouseFlag.Shift;
850             if (_keyFlags & KeyFlag.Control)
851                 lastFlags |= MouseFlag.Control;
852             if (_keyFlags & KeyFlag.Alt)
853                 lastFlags |= MouseFlag.Alt;
854             lastx = cast(short)x;
855             lasty = cast(short)y;
856             event = new MouseEvent(action, btn, lastFlags, lastx, lasty);
857         }
858         if (event) {
859             ButtonDetails * pbuttonDetails = null;
860             if (button == MouseButton.Left)
861                 pbuttonDetails = &_lbutton;
862             else if (button == MouseButton.Right)
863                 pbuttonDetails = &_rbutton;
864             else if (button == MouseButton.Middle)
865                 pbuttonDetails = &_mbutton;
866             if (pbuttonDetails) {
867                 if (action == MouseAction.ButtonDown) {
868                     pbuttonDetails.down(cast(short)x, cast(short)y, lastFlags);
869                 } else if (action == MouseAction.ButtonUp) {
870                     pbuttonDetails.up(cast(short)x, cast(short)y, lastFlags);
871                 }
872             }
873             event.lbutton = _lbutton;
874             event.rbutton = _rbutton;
875             event.mbutton = _mbutton;
876 
877             bool res = dispatchMouseEvent(event);
878             if (res) {
879                 debug(mouse) Log.d("Calling update() after mouse event");
880                 update();
881                 //invalidate();
882             }
883         }
884     }
885 
886 
887     uint convertKeyCode(uint keyCode) {
888         import x11.keysymdef;
889         alias KeyCode = dlangui.core.events.KeyCode;
890         switch(keyCode) {
891             case XK_0:
892                 return KeyCode.KEY_0;
893             case XK_1:
894                 return KeyCode.KEY_1;
895             case XK_2:
896                 return KeyCode.KEY_2;
897             case XK_3:
898                 return KeyCode.KEY_3;
899             case XK_4:
900                 return KeyCode.KEY_4;
901             case XK_5:
902                 return KeyCode.KEY_5;
903             case XK_6:
904                 return KeyCode.KEY_6;
905             case XK_7:
906                 return KeyCode.KEY_7;
907             case XK_8:
908                 return KeyCode.KEY_8;
909             case XK_9:
910                 return KeyCode.KEY_9;
911             case XK_A:
912             case XK_a:
913                 return KeyCode.KEY_A;
914             case XK_B:
915             case XK_b:
916                 return KeyCode.KEY_B;
917             case XK_C:
918             case XK_c:
919                 return KeyCode.KEY_C;
920             case XK_D:
921             case XK_d:
922                 return KeyCode.KEY_D;
923             case XK_E:
924             case XK_e:
925                 return KeyCode.KEY_E;
926             case XK_F:
927             case XK_f:
928                 return KeyCode.KEY_F;
929             case XK_G:
930             case XK_g:
931                 return KeyCode.KEY_G;
932             case XK_H:
933             case XK_h:
934                 return KeyCode.KEY_H;
935             case XK_I:
936             case XK_i:
937                 return KeyCode.KEY_I;
938             case XK_J:
939             case XK_j:
940                 return KeyCode.KEY_J;
941             case XK_K:
942             case XK_k:
943                 return KeyCode.KEY_K;
944             case XK_L:
945             case XK_l:
946                 return KeyCode.KEY_L;
947             case XK_M:
948             case XK_m:
949                 return KeyCode.KEY_M;
950             case XK_N:
951             case XK_n:
952                 return KeyCode.KEY_N;
953             case XK_O:
954             case XK_o:
955                 return KeyCode.KEY_O;
956             case XK_P:
957             case XK_p:
958                 return KeyCode.KEY_P;
959             case XK_Q:
960             case XK_q:
961                 return KeyCode.KEY_Q;
962             case XK_R:
963             case XK_r:
964                 return KeyCode.KEY_R;
965             case XK_S:
966             case XK_s:
967                 return KeyCode.KEY_S;
968             case XK_T:
969             case XK_t:
970                 return KeyCode.KEY_T;
971             case XK_U:
972             case XK_u:
973                 return KeyCode.KEY_U;
974             case XK_V:
975             case XK_v:
976                 return KeyCode.KEY_V;
977             case XK_W:
978             case XK_w:
979                 return KeyCode.KEY_W;
980             case XK_X:
981             case XK_x:
982                 return KeyCode.KEY_X;
983             case XK_Y:
984             case XK_y:
985                 return KeyCode.KEY_Y;
986             case XK_Z:
987             case XK_z:
988                 return KeyCode.KEY_Z;
989             case XK_F1:
990                 return KeyCode.F1;
991             case XK_F2:
992                 return KeyCode.F2;
993             case XK_F3:
994                 return KeyCode.F3;
995             case XK_F4:
996                 return KeyCode.F4;
997             case XK_F5:
998                 return KeyCode.F5;
999             case XK_F6:
1000                 return KeyCode.F6;
1001             case XK_F7:
1002                 return KeyCode.F7;
1003             case XK_F8:
1004                 return KeyCode.F8;
1005             case XK_F9:
1006                 return KeyCode.F9;
1007             case XK_F10:
1008                 return KeyCode.F10;
1009             case XK_F11:
1010                 return KeyCode.F11;
1011             case XK_F12:
1012                 return KeyCode.F12;
1013             case XK_F13:
1014                 return KeyCode.F13;
1015             case XK_F14:
1016                 return KeyCode.F14;
1017             case XK_F15:
1018                 return KeyCode.F15;
1019             case XK_F16:
1020                 return KeyCode.F16;
1021             case XK_F17:
1022                 return KeyCode.F17;
1023             case XK_F18:
1024                 return KeyCode.F18;
1025             case XK_F19:
1026                 return KeyCode.F19;
1027             case XK_F20:
1028                 return KeyCode.F20;
1029             case XK_F21:
1030                 return KeyCode.F21;
1031             case XK_F22:
1032                 return KeyCode.F22;
1033             case XK_F23:
1034                 return KeyCode.F23;
1035             case XK_F24:
1036                 return KeyCode.F24;
1037             case XK_BackSpace:
1038                 return KeyCode.BACK;
1039             case XK_space:
1040                 return KeyCode.SPACE;
1041             case XK_Tab:
1042                 return KeyCode.TAB;
1043             case XK_Return:
1044             case XK_KP_Enter:
1045                 return KeyCode.RETURN;
1046             case XK_Escape:
1047                 return KeyCode.ESCAPE;
1048             case XK_KP_Delete:
1049             case XK_Delete:
1050             //case 0x40000063: // dirty hack for Linux - key on keypad
1051                 return KeyCode.DEL;
1052             case XK_Insert:
1053             case XK_KP_Insert:
1054                 //case 0x40000062: // dirty hack for Linux - key on keypad
1055                 return KeyCode.INS;
1056             case XK_KP_Home:
1057             case XK_Home:
1058             //case 0x4000005f: // dirty hack for Linux - key on keypad
1059                 return KeyCode.HOME;
1060             case XK_KP_Page_Up:
1061             case XK_Page_Up:
1062             //case 0x40000061: // dirty hack for Linux - key on keypad
1063                 return KeyCode.PAGEUP;
1064             case XK_KP_End:
1065             case XK_End:
1066             //case 0x40000059: // dirty hack for Linux - key on keypad
1067                 return KeyCode.END;
1068             case XK_KP_Page_Down:
1069             case XK_Page_Down:
1070             //case 0x4000005b: // dirty hack for Linux - key on keypad
1071                 return KeyCode.PAGEDOWN;
1072             case XK_KP_Left:
1073             case XK_Left:
1074             //case 0x4000005c: // dirty hack for Linux - key on keypad
1075                 return KeyCode.LEFT;
1076             case XK_KP_Right:
1077             case XK_Right:
1078             //case 0x4000005e: // dirty hack for Linux - key on keypad
1079                 return KeyCode.RIGHT;
1080             case XK_KP_Up:
1081             case XK_Up:
1082             //case 0x40000060: // dirty hack for Linux - key on keypad
1083                 return KeyCode.UP;
1084             case XK_KP_Down:
1085             case XK_Down:
1086             //case 0x4000005a: // dirty hack for Linux - key on keypad
1087                 return KeyCode.DOWN;
1088             case XK_Control_L:
1089                 return KeyCode.LCONTROL;
1090             case XK_Shift_L:
1091                 return KeyCode.LSHIFT;
1092             case XK_Alt_L:
1093                 return KeyCode.LALT;
1094             case XK_Control_R:
1095                 return KeyCode.RCONTROL;
1096             case XK_Shift_R:
1097                 return KeyCode.RSHIFT;
1098             case XK_Alt_R:
1099                 return KeyCode.RALT;
1100             case XK_slash:
1101             case XK_KP_Divide:
1102                 return KeyCode.KEY_DIVIDE;
1103             default:
1104                 return 0x10000 | keyCode;
1105         }
1106     }
1107 
1108     uint convertKeyFlags(uint flags) {
1109         uint res;
1110         if (flags & ControlMask)
1111             res |= KeyFlag.Control;
1112         if (flags & ShiftMask)
1113             res |= KeyFlag.Shift;
1114         if (flags & LockMask)
1115             res |= KeyFlag.Alt;
1116 //        if (flags & KMOD_RCTRL)
1117 //            res |= KeyFlag.RControl | KeyFlag.Control;
1118 //        if (flags & KMOD_RSHIFT)
1119 //            res |= KeyFlag.RShift | KeyFlag.Shift;
1120 //        if (flags & KMOD_RALT)
1121 //            res |= KeyFlag.RAlt | KeyFlag.Alt;
1122 //        if (flags & KMOD_LCTRL)
1123 //            res |= KeyFlag.LControl | KeyFlag.Control;
1124 //        if (flags & KMOD_LSHIFT)
1125 //            res |= KeyFlag.LShift | KeyFlag.Shift;
1126 //        if (flags & KMOD_LALT)
1127 //            res |= KeyFlag.LAlt | KeyFlag.Alt;
1128         return res;
1129     }
1130 
1131 
1132     bool processKeyEvent(KeyAction action, uint keyCode, uint flagsX11) {
1133         //debug(DebugSDL)
1134         Log.d("processKeyEvent ", action, " X11 key=0x", format("%08x", keyCode), " X11 flags=0x", format("%08x", flagsX11));
1135         keyCode = convertKeyCode(keyCode);
1136         uint flagsConverted = convertKeyFlags(flagsX11);
1137 
1138         alias KeyCode = dlangui.core.events.KeyCode;
1139 
1140         {
1141         	uint flags = 0;
1142 		    switch(keyCode) {
1143 		        case KeyCode.ALT:
1144 		            flags |= KeyFlag.Alt;
1145 		            break;
1146 		        case KeyCode.RALT:
1147 		            flags |= KeyFlag.Alt | KeyFlag.RAlt;
1148 		            break;
1149 		        case KeyCode.LALT:
1150 		            flags |= KeyFlag.Alt | KeyFlag.LAlt;
1151 		            break;
1152 		        case KeyCode.CONTROL:
1153 		            flags |= KeyFlag.Control;
1154 		            break;
1155 		        case KeyCode.RCONTROL:
1156 		            flags |= KeyFlag.Control | KeyFlag.RControl;
1157 		            break;
1158 		        case KeyCode.LCONTROL:
1159 		            flags |= KeyFlag.Control | KeyFlag.LControl;
1160 		            break;
1161 		        case KeyCode.SHIFT:
1162 		            flags |= KeyFlag.Shift;
1163 		            break;
1164 		        case KeyCode.RSHIFT:
1165 		            flags |= KeyFlag.Shift | KeyFlag.RShift;
1166 		            break;
1167 		        case KeyCode.LSHIFT:
1168 		            flags |= KeyFlag.Shift | KeyFlag.LShift;
1169 		            break;
1170 		        default:
1171 		            break;
1172 		    }
1173 		    if (action == KeyAction.KeyDown)
1174 		    	flagsConverted |= flags;
1175 		    else
1176 		    	flagsConverted &= ~flags;
1177 		}
1178         Log.d("processKeyEvent ", action, " converted key=0x", format("%08x", keyCode), " flags=0x", format("%08x", flagsConverted));
1179         _keyFlags = flagsConverted;
1180 
1181         debug(DebugSDL) Log.d("processKeyEvent ", action, " converted key=0x", format("%08x", keyCode), " converted flags=0x", format("%08x", flagsConverted));
1182         bool res = dispatchKeyEvent(new KeyEvent(action, keyCode, flagsConverted));
1183         //            if ((keyCode & 0x10000) && (keyCode & 0xF000) != 0xF000) {
1184         //                dchar[1] text;
1185         //                text[0] = keyCode & 0xFFFF;
1186         //                res = dispatchKeyEvent(new KeyEvent(KeyAction.Text, keyCode, flags, cast(dstring)text)) || res;
1187         //            }
1188         if (res) {
1189             debug(keys) Log.d("Calling update() after key event");
1190             //invalidate();
1191             update();
1192         }
1193         return res;
1194     }
1195 
1196     bool processTextInput(dstring ds, uint flags) {
1197         flags = convertKeyFlags(flags);
1198         bool res = dispatchKeyEvent(new KeyEvent(KeyAction.Text, 0, flags, ds));
1199         if (res) {
1200             debug(keys) Log.d("Calling update() after text event");
1201             invalidate();
1202         }
1203         return res;
1204     }
1205 
1206     /// after drawing, call to schedule redraw if animation is active
1207     override void scheduleAnimation() {
1208         invalidate();
1209     }
1210 
1211     TimerThread timer;
1212     private long _nextExpectedTimerTs;
1213 
1214     /// schedule timer for interval in milliseconds - call window.onTimer when finished
1215     override protected void scheduleSystemTimer(long intervalMillis) {
1216         if (!timer) {
1217             timer = new TimerThread(delegate() {
1218                 XEvent ev;
1219                 memset(&ev, 0, ev.sizeof);
1220                 //ev.xclient = XClientMessageEvent.init;
1221                 ev.xclient.type = ClientMessage;
1222                 ev.xclient.message_type = atom_DLANGUI_TIMER_EVENT;
1223                 ev.xclient.window = _win;
1224                 ev.xclient.display = x11display2;
1225                 ev.xclient.format = 32;
1226                 //Log.d("Sending timer event");
1227                 XLockDisplay(x11display2);
1228                 XSendEvent(x11display2, _win, false, StructureNotifyMask, &ev);
1229                 XFlush(x11display2);
1230                 XUnlockDisplay(x11display2);
1231             });
1232         }
1233         if (intervalMillis < 10)
1234             intervalMillis = 10;
1235         long nextts = currentTimeMillis + intervalMillis;
1236         if (_nextExpectedTimerTs == 0 || _nextExpectedTimerTs > nextts) {
1237             _nextExpectedTimerTs = nextts;
1238             timer.set(nextts);
1239         }
1240     }
1241 
1242     bool handleTimer() {
1243         if (!_nextExpectedTimerTs)
1244             return false;
1245         long ts = currentTimeMillis;
1246         if (ts >= _nextExpectedTimerTs) {
1247             _nextExpectedTimerTs = 0;
1248             onTimer();
1249             return true;
1250         }
1251         return false;
1252     }
1253 
1254     /// post event to handle in UI thread (this method can be used from background thread)
1255     override void postEvent(CustomEvent event) {
1256         super.postEvent(event);
1257         XEvent ev;
1258         memset(&ev, 0, ev.sizeof);
1259         ev.xclient.type = ClientMessage;
1260         ev.xclient.window = _win;
1261         ev.xclient.display = x11display2;
1262         ev.xclient.message_type = atom_DLANGUI_TASK_EVENT;
1263         ev.xclient.format = 32;
1264         ev.xclient.data.l[0] = event.uniqueId;
1265         XLockDisplay(x11display2);
1266         XSendEvent(x11display2, _win, false, StructureNotifyMask, &ev);
1267         XFlush(x11display2);
1268         XUnlockDisplay(x11display2);
1269         //        SDL_Event sdlevent;
1270 //        sdlevent.user.type = USER_EVENT_ID;
1271 //        sdlevent.user.code = cast(int)event.uniqueId;
1272 //        sdlevent.user.windowID = windowId;
1273 //        SDL_PushEvent(&sdlevent);
1274     }
1275 
1276     protected uint _lastCursorType = CursorType.None;
1277     /// sets cursor type for window
1278     override protected void setCursorType(uint cursorType) {
1279         if (_lastCursorType != cursorType) {
1280             Log.d("setCursorType(", cursorType, ")");
1281             _lastCursorType = cursorType;
1282             XDefineCursor(x11display, _win, x11cursors[cursorType]);
1283             XFlush(x11display);
1284         }
1285     }
1286 }
1287 
1288 private immutable int CUSTOM_EVENT = 32;
1289 private immutable int TIMER_EVENT = 8;
1290 
1291 class X11Platform : Platform {
1292 
1293     this() {
1294         static if(ENABLE_OPENGL)
1295             _enableOpengl = true;
1296         import std.file : thisExePath;
1297         import std.path : baseName;
1298         _classname = (baseName(thisExePath()) ~ "\0").dup.ptr;
1299     }
1300 
1301     private X11Window[XWindow] _windowMap;
1302     private char* _classname;
1303 
1304     /**
1305      * create window
1306      * Args:
1307      *         windowCaption = window caption text
1308      *         parent = parent Window, or null if no parent
1309      *         flags = WindowFlag bit set, combination of Resizable, Modal, Fullscreen
1310      *      width = window width
1311      *      height = window height
1312      *
1313      * Window w/o Resizable nor Fullscreen will be created with size based on measurement of its content widget
1314      */
1315     override DWindow createWindow(dstring windowCaption, DWindow parent, uint flags = WindowFlag.Resizable, uint width = 0, uint height = 0) {
1316         int newwidth = width;
1317         int newheight = height;
1318         X11Window window = new X11Window(this, windowCaption, parent, flags, newwidth, newheight);
1319         _windowMap[window._win] = window;
1320         return window;
1321     }
1322 
1323     X11Window findWindow(XWindow windowId) {
1324         if (windowId in _windowMap)
1325             return _windowMap[windowId];
1326         return null;
1327     }
1328 
1329     /**
1330      * close window
1331      *
1332      * Closes window earlier created with createWindow()
1333      */
1334     override void closeWindow(DWindow w) {
1335         X11Window window = cast(X11Window)w;
1336         XEvent ev;
1337         memset(&ev, 0, ev.sizeof);
1338         ev.xclient.type = ClientMessage;
1339         ev.xclient.message_type = atom_DLANGUI_CLOSE_WINDOW_EVENT;
1340         ev.xclient.window = window._win;
1341         ev.xclient.display = x11display2;
1342         ev.xclient.format = 32;
1343         Log.d("Sending close window event");
1344         XLockDisplay(x11display2);
1345         XSendEvent(x11display2, window._win, false, StructureNotifyMask, &ev);
1346         XFlush(x11display2);
1347         XUnlockDisplay(x11display2);
1348     }
1349 
1350     bool handleTimers() {
1351         bool handled = false;
1352         foreach(w; _windowMap) {
1353             if (w.handleTimer()) {
1354                 handled = true;
1355                 break;
1356             }
1357         }
1358         return handled;
1359     }
1360 
1361     final bool allWindowsClosed() {
1362         return _windowMap.length == 0;
1363     }
1364 
1365     protected int numberOfPendingEvents(int msecs = 10)
1366     {
1367         import core.sys.posix.sys.select;
1368         int x11displayFd = ConnectionNumber(x11display);
1369         fd_set fdSet;
1370         FD_ZERO(&fdSet);
1371         FD_SET(x11displayFd, &fdSet);
1372         scope(exit) FD_ZERO(&fdSet);
1373 
1374         int eventsInQueue = XEventsQueued(x11display, QueuedAlready);
1375         if (!eventsInQueue) {
1376             import core.stdc.errno;
1377             int selectResult;
1378             do {
1379                 timeval timeout;
1380                 timeout.tv_usec = msecs;
1381                 selectResult = select(x11displayFd + 1, &fdSet, null, null, &timeout);
1382             } while(selectResult == -1 && errno == EINTR);
1383             if (selectResult < 0) {
1384                 Log.e("X11: display fd select error");
1385             } else if (selectResult == 1) {
1386                 //Log.d("X11: XPending");
1387                 eventsInQueue = XPending(x11display);
1388             }
1389         }
1390         return eventsInQueue;
1391     }
1392 
1393     protected void processXEvent(ref XEvent event)
1394     {
1395         XComposeStatus compose;
1396         switch (event.type) {
1397             case ConfigureNotify:
1398                 X11Window w = findWindow(event.xconfigure.window);
1399                 if (w) {
1400                     w._cachedWidth = event.xconfigure.width;
1401                     w._cachedHeight = event.xconfigure.height;
1402                     w.handleWindowStateChange(WindowState.unspecified, Rect(event.xconfigure.x, event.xconfigure.y, event.xconfigure.width, event.xconfigure.height));
1403                 } else {
1404                     Log.e("ConfigureNotify: Window not found");
1405                 }
1406                 break;
1407             case PropertyNotify:
1408                 if (event.xproperty.atom == atom_NET_WM_STATE && event.xproperty.state == PropertyNewValue) {
1409                     X11Window w = findWindow(event.xproperty.window);
1410                     if (w) {
1411                         Atom type;
1412                         int format;
1413                         ubyte* properties;
1414                         c_ulong dataLength, overflow;
1415                         if (XGetWindowProperty(x11display, event.xproperty.window, atom_NET_WM_STATE,
1416                             0, int.max/4, False, AnyPropertyType, &type, &format, &dataLength, &overflow, &properties) == 0) {
1417                             scope(exit) XFree(properties);
1418                             // check for minimized
1419                             bool minimized = false;
1420                             for (int i=0; i < dataLength ; i++) {
1421                                 if (((cast(c_ulong*)properties)[i]) == atom_NET_WM_STATE_HIDDEN) {
1422                                     w.handleWindowStateChange(WindowState.minimized);
1423                                     minimized = true;
1424                                 }
1425                             }
1426                             if (!minimized) {
1427                                 bool maximizedH = false;
1428                                 bool maximizedV = false;
1429                                 for (int i=0; i < dataLength ; i++) {
1430                                     if (((cast(c_ulong*)properties)[i]) == atom_NET_WM_STATE_MAXIMIZED_VERT)
1431                                         maximizedV = true;
1432                                     if (((cast(c_ulong*)properties)[i]) == atom_NET_WM_STATE_MAXIMIZED_HORZ)
1433                                         maximizedH = true;
1434                                 }
1435 
1436                                 if (maximizedV && maximizedH)
1437                                     w.handleWindowStateChange(WindowState.maximized);
1438                                 else
1439                                     w.handleWindowStateChange(WindowState.normal);
1440 
1441                             }
1442                         }
1443                     }
1444                 }
1445                 break;
1446             case MapNotify:
1447                 X11Window w = findWindow(event.xmap.window);
1448                 if (w) {
1449                     w.handleWindowStateChange(WindowState.normal);
1450                 }
1451                 break;
1452             case UnmapNotify:
1453                 X11Window w = findWindow(event.xunmap.window);
1454                 if (w) {
1455                     w.handleWindowStateChange(WindowState.hidden);
1456                 }
1457                 break;
1458             case Expose:
1459                 if (event.xexpose.count == 0) {
1460                     X11Window w = findWindow(event.xexpose.window);
1461                     if (w) {
1462                         w.invalidate();
1463                     } else {
1464                         Log.e("Expose: Window not found");
1465                     }
1466                 } else {
1467                     Log.d("Expose: non-0 count");
1468                 }
1469                 break;
1470             case KeyPress:
1471                 Log.d("X11: KeyPress event");
1472                 X11Window w = findWindow(event.xkey.window);
1473                 if (w) {
1474                     char[100] buf;
1475                     KeySym ks;
1476                     Status s;
1477                     if (!w.xic) {
1478                         w.xic = XCreateIC(xim,
1479                             XNInputStyle, XIMPreeditNothing | XIMStatusNothing,
1480                             XNClientWindow, w._win, 0);
1481                         if (!w.xic) {
1482                             Log.e("Cannot create input context");
1483                         }
1484                     }
1485 
1486                     if (!w.xic)
1487                         XLookupString(&event.xkey, buf.ptr, buf.length - 1, &ks, &compose);
1488                     else {
1489                         Xutf8LookupString(w.xic, &event.xkey, buf.ptr, cast(int)buf.length - 1, &ks, &s);
1490                         if (s != XLookupChars && s != XLookupBoth)
1491                             XLookupString(&event.xkey, buf.ptr, buf.length - 1, &ks, &compose);
1492                     }
1493                     foreach(ref ch; buf) {
1494                         if (ch == 255 || ch < 32 || ch == 127)
1495                             ch = 0;
1496                     }
1497                     string txt = fromStringz(buf.ptr).dup;
1498                     import std.utf;
1499                     dstring dtext;
1500                     try {
1501                         if (txt.length)
1502                             dtext = toUTF32(txt);
1503                     } catch (UTFException e) {
1504                         // ignore, invalid text
1505                     }
1506                     debug(x11) Log.d("X11: KeyPress event bytes=", txt.length, " text=", txt, " dtext=", dtext);
1507                     if (dtext.length) {
1508                         w.processTextInput(dtext, event.xkey.state);
1509                     } else {
1510                         w.processKeyEvent(KeyAction.KeyDown, cast(uint)ks,
1511                             //event.xkey.keycode,
1512                             event.xkey.state);
1513                     }
1514                 } else {
1515                     Log.e("Window not found");
1516                 }
1517                 break;
1518             case KeyRelease:
1519                 Log.d("X11: KeyRelease event");
1520                 X11Window w = findWindow(event.xkey.window);
1521                 if (w) {
1522                     char[100] buf;
1523                     KeySym ks;
1524                     XLookupString(&event.xkey, buf.ptr, buf.length - 1, &ks, &compose);
1525                     w.processKeyEvent(KeyAction.KeyUp, cast(uint)ks,
1526                         //event.xkey.keycode,
1527                         event.xkey.state);
1528                 } else {
1529                     Log.e("Window not found");
1530                 }
1531                 break;
1532             case ButtonPress:
1533                 Log.d("X11: ButtonPress event");
1534                 X11Window w = findWindow(event.xbutton.window);
1535                 if (w) {
1536                     if (event.xbutton.button == 4 || event.xbutton.button == 5) {
1537                         w.processMouseEvent(MouseAction.Wheel, 0, 0, 0, event.xbutton.button == 4 ? 1 : -1);
1538                     } else {
1539                         w.processMouseEvent(MouseAction.ButtonDown, event.xbutton.button, event.xbutton.state, event.xbutton.x, event.xbutton.y);
1540                     }
1541                 } else {
1542                     Log.e("Window not found");
1543                 }
1544                 break;
1545             case ButtonRelease:
1546                 Log.d("X11: ButtonRelease event");
1547                 X11Window w = findWindow(event.xbutton.window);
1548                 if (w) {
1549                     w.processMouseEvent(MouseAction.ButtonUp, event.xbutton.button, event.xbutton.state, event.xbutton.x, event.xbutton.y);
1550                 } else {
1551                     Log.e("Window not found");
1552                 }
1553                 break;
1554             case MotionNotify:
1555                 debug(x11) Log.d("X11: MotionNotify event");
1556                 X11Window w = findWindow(event.xmotion.window);
1557                 if (w) {
1558                     w.processMouseEvent(MouseAction.Move, 0, event.xmotion.state, event.xmotion.x, event.xmotion.y);
1559                 } else {
1560                     Log.e("Window not found");
1561                 }
1562                 break;
1563             case EnterNotify:
1564                 Log.d("X11: EnterNotify event");
1565                 X11Window w = findWindow(event.xcrossing.window);
1566                 if (w) {
1567                     w.processMouseEvent(MouseAction.Move, 0, event.xmotion.state, event.xcrossing.x, event.xcrossing.y);
1568                 } else {
1569                     Log.e("Window not found");
1570                 }
1571                 break;
1572             case LeaveNotify:
1573                 Log.d("X11: LeaveNotify event");
1574                 X11Window w = findWindow(event.xcrossing.window);
1575                 if (w) {
1576                     w.processMouseEvent(MouseAction.Leave, 0, event.xcrossing.state, event.xcrossing.x, event.xcrossing.y);
1577                 } else {
1578                     Log.e("Window not found");
1579                 }
1580                 break;
1581             case CreateNotify:
1582                 Log.d("X11: CreateNotify event");
1583                 X11Window w = findWindow(event.xcreatewindow.window);
1584                 if (!w) {
1585                     Log.e("Window not found");
1586                 }
1587                 break;
1588             case DestroyNotify:
1589                 Log.d("X11: DestroyNotify event");
1590                 break;
1591             case ResizeRequest:
1592                 Log.d("X11: ResizeRequest event");
1593                 X11Window w = findWindow(event.xresizerequest.window);
1594                 if (!w) {
1595                     Log.e("Window not found");
1596                 }
1597                 break;
1598             case FocusIn:
1599                 Log.d("X11: FocusIn event");
1600                 X11Window w = findWindow(event.xfocus.window);
1601                 if (w)
1602                     w.handleWindowActivityChange(true);
1603                 else
1604                     Log.e("Window not found");
1605                 break;
1606             case FocusOut:
1607                 Log.d("X11: FocusOut event");
1608                 X11Window w = findWindow(event.xfocus.window);
1609                 if (w)
1610                     w.handleWindowActivityChange(false);
1611                 else
1612                     Log.e("Window not found");
1613                 break;
1614             case KeymapNotify:
1615                 Log.d("X11: KeymapNotify event");
1616                 X11Window w = findWindow(event.xkeymap.window);
1617                 break;
1618             case SelectionClear:
1619                 Log.d("X11: SelectionClear event");
1620                 break;
1621             case SelectionRequest:
1622                 debug(x11) Log.d("X11: SelectionRequest event");
1623                 if (event.xselectionrequest.owner in _windowMap) {
1624                     XSelectionRequestEvent *selectionRequest = &event.xselectionrequest;
1625 
1626                     XEvent selectionEvent;
1627                     memset(&selectionEvent, 0, selectionEvent.sizeof);
1628                     selectionEvent.xany.type = SelectionNotify;
1629                     selectionEvent.xselection.selection = selectionRequest.selection;
1630                     selectionEvent.xselection.target = selectionRequest.target;
1631                     selectionEvent.xselection.property = None;
1632                     selectionEvent.xselection.requestor = selectionRequest.requestor;
1633                     selectionEvent.xselection.time = selectionRequest.time;
1634 
1635                     if (selectionRequest.target == XA_STRING || selectionRequest.target == atom_UTF8_STRING) {
1636                         int currentSelectionFormat;
1637                         Atom currentSelectionType;
1638                         c_ulong selectionDataLength, overflow;
1639                         ubyte* selectionDataPtr;
1640                         if (XGetWindowProperty(x11display, DefaultRootWindow(x11display), atom_DLANGUI_CLIPBOARD_BUFFER,
1641                             0, int.max/4, False, selectionRequest.target,
1642                             &currentSelectionType, &currentSelectionFormat, &selectionDataLength,
1643                             &overflow, &selectionDataPtr) == 0)
1644                         {
1645                             scope(exit) XFree(selectionDataPtr);
1646                             XChangeProperty(x11display, selectionRequest.requestor, selectionRequest.property,
1647                                 selectionRequest.target, 8, PropModeReplace,
1648                                 selectionDataPtr, cast(int)selectionDataLength);
1649                         }
1650                         selectionEvent.xselection.property = selectionRequest.property;
1651                     } else if (selectionRequest.target == atom_TARGETS) {
1652                         Atom[3] supportedFormats = [atom_UTF8_STRING, XA_STRING, atom_TARGETS];
1653                         XChangeProperty(x11display, selectionRequest.requestor, selectionRequest.property,
1654                             XA_ATOM, 32, PropModeReplace,
1655                             cast(ubyte*)supportedFormats.ptr, cast(int)supportedFormats.length);
1656                         selectionEvent.xselection.property = selectionRequest.property;
1657                     }
1658                     XSendEvent(x11display, selectionRequest.requestor, False, 0, &selectionEvent);
1659                 }
1660                 break;
1661             case SelectionNotify:
1662                 debug(x11) Log.d("X11: SelectionNotify event");
1663                 X11Window w = findWindow(event.xselection.requestor);
1664                 if (w) {
1665                     waitingForSelection = false;
1666                 }
1667                 break;
1668             case ClientMessage:
1669                 debug(x11) Log.d("X11: ClientMessage event");
1670                 X11Window w = findWindow(event.xclient.window);
1671                 if (w) {
1672                     if (event.xclient.message_type == atom_DLANGUI_TASK_EVENT) {
1673                         w.handlePostedEvent(cast(uint)event.xclient.data.l[0]);
1674                     } else if (event.xclient.message_type == atom_DLANGUI_TIMER_EVENT) {
1675                         w.handleTimer();
1676                     } else if (event.xclient.message_type == atom_DLANGUI_REDRAW_EVENT) {
1677                         if (event.xclient.data.l[0] == w._lastRedrawEventCode)
1678                             w.redraw();
1679                     } else if (event.xclient.message_type == atom_WM_PROTOCOLS) {
1680                         Log.d("Handling WM_PROTOCOLS");
1681                         if ((event.xclient.format == 32) && (event.xclient.data.l[0]) == atom_WM_DELETE_WINDOW) {
1682                             Log.d("Handling WM_DELETE_WINDOW");
1683                             _windowMap.remove(w._win);
1684                             destroy(w);
1685                         }
1686                     } else if (event.xclient.message_type == atom_DLANGUI_CLOSE_WINDOW_EVENT) {
1687                         _windowMap.remove(w._win);
1688                         destroy(w);
1689                     }
1690                 } else {
1691                     Log.e("Window not found");
1692                 }
1693                 break;
1694             default:
1695                 break;
1696         }
1697     }
1698 
1699     protected void pumpEvents()
1700     {
1701         XFlush(x11display);
1702         // Note:  only events we set the mask for are detected!
1703         while(numberOfPendingEvents())
1704         {
1705             if (allWindowsClosed())
1706                 break;
1707             XEvent event;        /* the XEvent declaration !!! */
1708             XNextEvent(x11display, &event);
1709             processXEvent(event);
1710         }
1711     }
1712 
1713     /**
1714      * Starts application message loop.
1715      *
1716      * When returned from this method, application is shutting down.
1717      */
1718     override int enterMessageLoop() {
1719         Log.d("enterMessageLoop()");
1720 
1721         while(!allWindowsClosed()) {
1722             pumpEvents();
1723         }
1724         return 0;
1725     }
1726 
1727     /// check has clipboard text
1728     override bool hasClipboardText(bool mouseBuffer = false) {
1729         const selectionType = mouseBuffer ? XA_PRIMARY : atom_CLIPBOARD;
1730         return XGetSelectionOwner(x11display, selectionType) != None;
1731     }
1732 
1733     protected bool waitingForSelection;
1734     /// retrieves text from clipboard (when mouseBuffer == true, use mouse selection clipboard - under linux)
1735     override dstring getClipboardText(bool mouseBuffer = false) {
1736         const selectionType = mouseBuffer ? XA_PRIMARY : atom_CLIPBOARD;
1737         auto owner = XGetSelectionOwner(x11display, selectionType);
1738         if (owner == None) {
1739             Log.d("Selection owner is none");
1740             return ""d;
1741         } else {
1742             // Find any top-level window
1743             XWindow xwindow;
1744             foreach(w; _windowMap) {
1745                 if (w._parent is null && w._win != None) {
1746                     xwindow = w._win;
1747                     break;
1748                 }
1749             }
1750             if (xwindow != None) {
1751                 import std.datetime;
1752                 waitingForSelection = true;
1753                 XConvertSelection(x11display, selectionType, atom_UTF8_STRING, atom_DLANGUI_CLIPBOARD_BUFFER, xwindow, CurrentTime);
1754                 auto stopWaiting = Clock.currTime() + dur!"msecs"(500);
1755                 while(waitingForSelection) {
1756                     if (stopWaiting <= Clock.currTime()) {
1757                         waitingForSelection = false;
1758                         setClipboardText(""d);
1759                         Log.e("Waiting for clipboard contents timeout");
1760                         return ""d;
1761                     }
1762                     pumpEvents();
1763                 }
1764                 Atom selectionTarget;
1765                 int selectionFormat;
1766                 c_ulong selectionDataLength, overflow;
1767                 ubyte* selectionDataPtr;
1768                 if (XGetWindowProperty(x11display, xwindow, atom_DLANGUI_CLIPBOARD_BUFFER, 0, int.max/4, False, 0, &selectionTarget, &selectionFormat, &selectionDataLength, &overflow, &selectionDataPtr) == 0)
1769                 {
1770                     scope(exit) XFree(selectionDataPtr);
1771                     if (selectionTarget == XA_STRING || selectionTarget == atom_UTF8_STRING) {
1772                         char[] selectionText = cast(char[])selectionDataPtr[0..selectionDataLength];
1773                         return toUTF32(selectionText);
1774                     } else {
1775                         Log.d("Selection type is not a string!");
1776                     }
1777                 }
1778             } else {
1779                 Log.d("Could not find any window to get selection");
1780             }
1781         }
1782         return ""d;
1783     }
1784 
1785     /// sets text to clipboard (when mouseBuffer == true, use mouse selection clipboard - under linux)
1786     override void setClipboardText(dstring text, bool mouseBuffer = false) {
1787         if (!mouseBuffer && atom_CLIPBOARD == None) {
1788             Log.e("No CLIPBOARD atom available");
1789             return;
1790         }
1791         auto selection = mouseBuffer ? XA_PRIMARY : atom_CLIPBOARD;
1792         XWindow xwindow = None;
1793         // Find any top-level window
1794         foreach(w; _windowMap) {
1795             if (w._parent is null && w._win != None) {
1796                 xwindow = w._win;
1797             }
1798         }
1799         if (xwindow == None) {
1800             Log.e("Could not find window to save clipboard text");
1801             return;
1802         }
1803 
1804         auto textc = text.toUTF8;
1805         XChangeProperty(x11display, DefaultRootWindow(x11display), atom_DLANGUI_CLIPBOARD_BUFFER, atom_UTF8_STRING, 8, PropModeReplace, cast(ubyte*)textc.ptr, cast(int)textc.length);
1806 
1807         if (XGetSelectionOwner(x11display, selection) != xwindow) {
1808             XSetSelectionOwner(x11display, selection, xwindow, CurrentTime);
1809         }
1810     }
1811 
1812     /// calls request layout for all windows
1813     override void requestLayout() {
1814         foreach(w; _windowMap) {
1815             w.requestLayout();
1816         }
1817     }
1818 
1819     /// returns true if there is some modal window opened above this window, and this window should not process mouse/key input and should not allow closing
1820     override bool hasModalWindowsAbove(DWindow w) {
1821         X11Window x11Win = cast (X11Window) w;
1822         if (x11Win) {
1823             return x11Win.hasVisibleModalChild();
1824         }
1825         return false;
1826     }
1827 
1828     /// handle theme change: e.g. reload some themed resources
1829     override void onThemeChanged() {
1830         super.onThemeChanged();
1831         foreach(w; _windowMap)
1832             w.dispatchThemeChanged();
1833     }
1834 
1835 }
1836 
1837 import core.thread;
1838 import core.sync.mutex;
1839 import core.sync.condition;
1840 class TimerThread : Thread {
1841     Mutex mutex;
1842     Condition condition;
1843     bool stopped;
1844     long nextEventTs;
1845     void delegate() callback;
1846 
1847     this(void delegate() timerCallback) {
1848         callback = timerCallback;
1849         mutex = new Mutex();
1850         condition = new Condition(mutex);
1851         super(&run);
1852         start();
1853     }
1854 
1855     ~this() {
1856         stop();
1857         destroy(condition);
1858         destroy(mutex);
1859     }
1860 
1861     void set(long nextTs) {
1862         mutex.lock();
1863         if (nextEventTs == 0 || nextEventTs > nextTs) {
1864             nextEventTs = nextTs;
1865             condition.notify();
1866         }
1867         mutex.unlock();
1868     }
1869     void run() {
1870 
1871         while (!stopped) {
1872             bool expired = false;
1873 
1874             mutex.lock();
1875 
1876             long ts = currentTimeMillis;
1877             long timeToWait = nextEventTs == 0 ? 1000000 : nextEventTs - ts;
1878             if (timeToWait < 10)
1879                 timeToWait = 10;
1880 
1881             if (nextEventTs == 0)
1882                 condition.wait();
1883             else
1884                 condition.wait(dur!"msecs"(timeToWait));
1885 
1886             if (stopped) {
1887                 mutex.unlock();
1888                 break;
1889             }
1890             ts = currentTimeMillis;
1891             if (nextEventTs && nextEventTs < ts && !stopped) {
1892                 expired = true;
1893                 nextEventTs = 0;
1894             }
1895 
1896             mutex.unlock();
1897 
1898             if (expired)
1899                 callback();
1900         }
1901     }
1902     void stop() {
1903         if (stopped)
1904             return;
1905         stopped = true;
1906         mutex.lock();
1907         condition.notify();
1908         mutex.unlock();
1909         join();
1910     }
1911 }
1912 
1913 
1914 extern(C) int DLANGUImain(string[] args)
1915 {
1916     initLogs();
1917 
1918     if (!initFontManager()) {
1919         Log.e("******************************************************************");
1920         Log.e("No font files found!!!");
1921         Log.e("Currently, only hardcoded font paths implemented.");
1922         Log.e("Probably you can modify sdlapp.d to add some fonts for your system.");
1923         Log.e("TODO: use fontconfig");
1924         Log.e("******************************************************************");
1925         assert(false);
1926     }
1927     initResourceManagers();
1928 
1929     currentTheme = createDefaultTheme();
1930 
1931     XInitThreads();
1932 
1933     /* use the information from the environment variable DISPLAY
1934        to create the X connection:
1935     */
1936     x11display = XOpenDisplay(null);
1937     if (!x11display) {
1938         Log.e("Cannot open X11 display");
1939         return 1;
1940     }
1941     x11display2 = XOpenDisplay(null);
1942     if (!x11display2) {
1943         Log.e("Cannot open secondary connection for X11 display");
1944         return 1;
1945     }
1946 
1947     x11screen = DefaultScreen(x11display);
1948 
1949     static if (ENABLE_OPENGL) {
1950         try {
1951             import bindbc.opengl : GLVersion = GLSupport;
1952             GLVersion support = loadOpenGL();
1953             if(support == GLVersion.badLibrary)
1954             {
1955                 _enableOpengl = false;
1956                 Log.e("Error loading OpenGL library");
1957             }
1958             else
1959             {
1960                 Log.d("OpenGL library loaded ok");
1961                 GLint[] att = [ GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None ];
1962                 XWindow root;
1963                 root = DefaultRootWindow(x11display);
1964                 x11visual = glXChooseVisual(x11display, 0, cast(int*)att.ptr);
1965                 if (x11visual) {
1966                     x11cmap = XCreateColormap(x11display, root, cast(Visual*)x11visual.visual, AllocNone);
1967                     _enableOpengl = true;
1968                 } else {
1969                     Log.e("Cannot find suitable Visual for using of OpenGL");
1970                 }
1971             }
1972         } catch (Exception e) {
1973             Log.e("Cannot load OpenGL library", e);
1974         }
1975     }
1976 
1977 
1978     setupX11Atoms();
1979 
1980     x11cursors[CursorType.None] = XCreateFontCursor(x11display, XC_arrow);
1981     x11cursors[CursorType.NotSet] = XCreateFontCursor(x11display, XC_arrow);
1982     x11cursors[CursorType.Arrow] = XCreateFontCursor(x11display, XC_left_ptr);
1983     x11cursors[CursorType.IBeam] = XCreateFontCursor(x11display, XC_xterm);
1984     x11cursors[CursorType.Wait] = XCreateFontCursor(x11display, XC_watch);
1985     x11cursors[CursorType.Crosshair] = XCreateFontCursor(x11display, XC_tcross);
1986     x11cursors[CursorType.WaitArrow] = XCreateFontCursor(x11display, XC_watch);
1987     x11cursors[CursorType.SizeNWSE] = XCreateFontCursor(x11display, XC_fleur);
1988     x11cursors[CursorType.SizeNESW] = XCreateFontCursor(x11display, XC_fleur);
1989     x11cursors[CursorType.SizeWE] = XCreateFontCursor(x11display, XC_sb_h_double_arrow);
1990     x11cursors[CursorType.SizeNS] = XCreateFontCursor(x11display, XC_sb_v_double_arrow);
1991     x11cursors[CursorType.SizeAll] = XCreateFontCursor(x11display, XC_fleur);
1992     x11cursors[CursorType.No] = XCreateFontCursor(x11display, XC_pirate);
1993     x11cursors[CursorType.Hand] = XCreateFontCursor(x11display, XC_hand2);
1994 
1995     xim = XOpenIM(x11display, null, null, null);
1996     if (!xim) {
1997         Log.e("Cannot open input method");
1998     }
1999 
2000     Log.d("X11 display=", x11display, " screen=", x11screen);
2001 
2002 
2003 
2004     X11Platform x11platform = new X11Platform();
2005 
2006     Platform.setInstance(x11platform);
2007 
2008     int res = 0;
2009 
2010     version (unittest) {
2011     } else {
2012         res = UIAppMain(args);
2013     }
2014 
2015     //Log.e("Widget instance count after UIAppMain: ", Widget.instanceCount());
2016 
2017     Log.d("Destroying X11 platform");
2018     Platform.setInstance(null);
2019 
2020     releaseResourcesOnAppExit();
2021 
2022 
2023     XCloseDisplay(x11display);
2024     XCloseDisplay(x11display2);
2025 
2026     Log.d("Exiting main width result=", res);
2027 
2028     return res;
2029 }