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