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     ushort lastFlags;
821     short lastx;
822     short lasty;
823     uint _keyFlags;
824     void processMouseEvent(MouseAction action, uint button, uint state, int x, int y) {
825         MouseEvent event = null;
826         if (action == MouseAction.Wheel) {
827             // handle wheel
828             short wheelDelta = cast(short)y;
829             if (_keyFlags & KeyFlag.Shift)
830                 lastFlags |= MouseFlag.Shift;
831             else
832                 lastFlags &= ~MouseFlag.Shift;
833             if (_keyFlags & KeyFlag.Control)
834                 lastFlags |= MouseFlag.Control;
835             else
836                 lastFlags &= ~MouseFlag.Control;
837             if (_keyFlags & KeyFlag.Alt)
838                 lastFlags |= MouseFlag.Alt;
839             else
840                 lastFlags &= ~MouseFlag.Alt;
841             if (wheelDelta)
842                 event = new MouseEvent(action, MouseButton.None, lastFlags, lastx, lasty, wheelDelta);
843         } else {
844             MouseButton btn = convertMouseButton(button);
845             lastFlags = convertMouseFlags(state, btn, action == MouseAction.ButtonDown);
846 
847             if (_keyFlags & KeyFlag.Shift)
848                 lastFlags |= MouseFlag.Shift;
849             if (_keyFlags & KeyFlag.Control)
850                 lastFlags |= MouseFlag.Control;
851             if (_keyFlags & KeyFlag.Alt)
852                 lastFlags |= MouseFlag.Alt;
853             lastx = cast(short)x;
854             lasty = cast(short)y;
855             event = new MouseEvent(action, btn, lastFlags, lastx, lasty);
856         }
857         if (event) {
858             ButtonDetails * pbuttonDetails = null;
859             if (button == MouseButton.Left)
860                 pbuttonDetails = &_lbutton;
861             else if (button == MouseButton.Right)
862                 pbuttonDetails = &_rbutton;
863             else if (button == MouseButton.Middle)
864                 pbuttonDetails = &_mbutton;
865             if (pbuttonDetails) {
866                 if (action == MouseAction.ButtonDown) {
867                     pbuttonDetails.down(cast(short)x, cast(short)y, lastFlags);
868                 } else if (action == MouseAction.ButtonUp) {
869                     pbuttonDetails.up(cast(short)x, cast(short)y, lastFlags);
870                 }
871             }
872             event.lbutton = _lbutton;
873             event.rbutton = _rbutton;
874             event.mbutton = _mbutton;
875 
876             bool res = dispatchMouseEvent(event);
877             if (res) {
878                 debug(mouse) Log.d("Calling update() after mouse event");
879                 update();
880                 //invalidate();
881             }
882         }
883     }
884 
885     uint convertKeyCode(uint keyCode) {
886         import x11.keysymdef;
887         alias KeyCode = dlangui.core.events.KeyCode;
888         switch(keyCode) {
889             case XK_0:
890                 return KeyCode.KEY_0;
891             case XK_1:
892                 return KeyCode.KEY_1;
893             case XK_2:
894                 return KeyCode.KEY_2;
895             case XK_3:
896                 return KeyCode.KEY_3;
897             case XK_4:
898                 return KeyCode.KEY_4;
899             case XK_5:
900                 return KeyCode.KEY_5;
901             case XK_6:
902                 return KeyCode.KEY_6;
903             case XK_7:
904                 return KeyCode.KEY_7;
905             case XK_8:
906                 return KeyCode.KEY_8;
907             case XK_9:
908                 return KeyCode.KEY_9;
909             case XK_A:
910             case XK_a:
911                 return KeyCode.KEY_A;
912             case XK_B:
913             case XK_b:
914                 return KeyCode.KEY_B;
915             case XK_C:
916             case XK_c:
917                 return KeyCode.KEY_C;
918             case XK_D:
919             case XK_d:
920                 return KeyCode.KEY_D;
921             case XK_E:
922             case XK_e:
923                 return KeyCode.KEY_E;
924             case XK_F:
925             case XK_f:
926                 return KeyCode.KEY_F;
927             case XK_G:
928             case XK_g:
929                 return KeyCode.KEY_G;
930             case XK_H:
931             case XK_h:
932                 return KeyCode.KEY_H;
933             case XK_I:
934             case XK_i:
935                 return KeyCode.KEY_I;
936             case XK_J:
937             case XK_j:
938                 return KeyCode.KEY_J;
939             case XK_K:
940             case XK_k:
941                 return KeyCode.KEY_K;
942             case XK_L:
943             case XK_l:
944                 return KeyCode.KEY_L;
945             case XK_M:
946             case XK_m:
947                 return KeyCode.KEY_M;
948             case XK_N:
949             case XK_n:
950                 return KeyCode.KEY_N;
951             case XK_O:
952             case XK_o:
953                 return KeyCode.KEY_O;
954             case XK_P:
955             case XK_p:
956                 return KeyCode.KEY_P;
957             case XK_Q:
958             case XK_q:
959                 return KeyCode.KEY_Q;
960             case XK_R:
961             case XK_r:
962                 return KeyCode.KEY_R;
963             case XK_S:
964             case XK_s:
965                 return KeyCode.KEY_S;
966             case XK_T:
967             case XK_t:
968                 return KeyCode.KEY_T;
969             case XK_U:
970             case XK_u:
971                 return KeyCode.KEY_U;
972             case XK_V:
973             case XK_v:
974                 return KeyCode.KEY_V;
975             case XK_W:
976             case XK_w:
977                 return KeyCode.KEY_W;
978             case XK_X:
979             case XK_x:
980                 return KeyCode.KEY_X;
981             case XK_Y:
982             case XK_y:
983                 return KeyCode.KEY_Y;
984             case XK_Z:
985             case XK_z:
986                 return KeyCode.KEY_Z;
987             case XK_F1:
988                 return KeyCode.F1;
989             case XK_F2:
990                 return KeyCode.F2;
991             case XK_F3:
992                 return KeyCode.F3;
993             case XK_F4:
994                 return KeyCode.F4;
995             case XK_F5:
996                 return KeyCode.F5;
997             case XK_F6:
998                 return KeyCode.F6;
999             case XK_F7:
1000                 return KeyCode.F7;
1001             case XK_F8:
1002                 return KeyCode.F8;
1003             case XK_F9:
1004                 return KeyCode.F9;
1005             case XK_F10:
1006                 return KeyCode.F10;
1007             case XK_F11:
1008                 return KeyCode.F11;
1009             case XK_F12:
1010                 return KeyCode.F12;
1011             case XK_F13:
1012                 return KeyCode.F13;
1013             case XK_F14:
1014                 return KeyCode.F14;
1015             case XK_F15:
1016                 return KeyCode.F15;
1017             case XK_F16:
1018                 return KeyCode.F16;
1019             case XK_F17:
1020                 return KeyCode.F17;
1021             case XK_F18:
1022                 return KeyCode.F18;
1023             case XK_F19:
1024                 return KeyCode.F19;
1025             case XK_F20:
1026                 return KeyCode.F20;
1027             case XK_F21:
1028                 return KeyCode.F21;
1029             case XK_F22:
1030                 return KeyCode.F22;
1031             case XK_F23:
1032                 return KeyCode.F23;
1033             case XK_F24:
1034                 return KeyCode.F24;
1035             case XK_BackSpace:
1036                 return KeyCode.BACK;
1037             case XK_space:
1038                 return KeyCode.SPACE;
1039             case XK_Tab:
1040                 return KeyCode.TAB;
1041             case XK_Return:
1042             case XK_KP_Enter:
1043                 return KeyCode.RETURN;
1044             case XK_Escape:
1045                 return KeyCode.ESCAPE;
1046             case XK_KP_Delete:
1047             case XK_Delete:
1048             //case 0x40000063: // dirty hack for Linux - key on keypad
1049                 return KeyCode.DEL;
1050             case XK_Insert:
1051             case XK_KP_Insert:
1052                 //case 0x40000062: // dirty hack for Linux - key on keypad
1053                 return KeyCode.INS;
1054             case XK_KP_Home:
1055             case XK_Home:
1056             //case 0x4000005f: // dirty hack for Linux - key on keypad
1057                 return KeyCode.HOME;
1058             case XK_KP_Page_Up:
1059             case XK_Page_Up:
1060             //case 0x40000061: // dirty hack for Linux - key on keypad
1061                 return KeyCode.PAGEUP;
1062             case XK_KP_End:
1063             case XK_End:
1064             //case 0x40000059: // dirty hack for Linux - key on keypad
1065                 return KeyCode.END;
1066             case XK_KP_Page_Down:
1067             case XK_Page_Down:
1068             //case 0x4000005b: // dirty hack for Linux - key on keypad
1069                 return KeyCode.PAGEDOWN;
1070             case XK_KP_Left:
1071             case XK_Left:
1072             //case 0x4000005c: // dirty hack for Linux - key on keypad
1073                 return KeyCode.LEFT;
1074             case XK_KP_Right:
1075             case XK_Right:
1076             //case 0x4000005e: // dirty hack for Linux - key on keypad
1077                 return KeyCode.RIGHT;
1078             case XK_KP_Up:
1079             case XK_Up:
1080             //case 0x40000060: // dirty hack for Linux - key on keypad
1081                 return KeyCode.UP;
1082             case XK_KP_Down:
1083             case XK_Down:
1084             //case 0x4000005a: // dirty hack for Linux - key on keypad
1085                 return KeyCode.DOWN;
1086             case XK_Control_L:
1087                 return KeyCode.LCONTROL;
1088             case XK_Shift_L:
1089                 return KeyCode.LSHIFT;
1090             case XK_Alt_L:
1091                 return KeyCode.LALT;
1092             case XK_Control_R:
1093                 return KeyCode.RCONTROL;
1094             case XK_Shift_R:
1095                 return KeyCode.RSHIFT;
1096             case XK_Alt_R:
1097                 return KeyCode.RALT;
1098             case XK_slash:
1099             case XK_KP_Divide:
1100                 return KeyCode.KEY_DIVIDE;
1101             default:
1102                 return 0x10000 | keyCode;
1103         }
1104     }
1105 
1106     uint convertKeyFlags(uint flags) {
1107         uint res;
1108         if (flags & ControlMask)
1109             res |= KeyFlag.Control;
1110         if (flags & ShiftMask)
1111             res |= KeyFlag.Shift;
1112         if (flags & LockMask)
1113             res |= KeyFlag.Alt;
1114 //        if (flags & KMOD_RCTRL)
1115 //            res |= KeyFlag.RControl | KeyFlag.Control;
1116 //        if (flags & KMOD_RSHIFT)
1117 //            res |= KeyFlag.RShift | KeyFlag.Shift;
1118 //        if (flags & KMOD_RALT)
1119 //            res |= KeyFlag.RAlt | KeyFlag.Alt;
1120 //        if (flags & KMOD_LCTRL)
1121 //            res |= KeyFlag.LControl | KeyFlag.Control;
1122 //        if (flags & KMOD_LSHIFT)
1123 //            res |= KeyFlag.LShift | KeyFlag.Shift;
1124 //        if (flags & KMOD_LALT)
1125 //            res |= KeyFlag.LAlt | KeyFlag.Alt;
1126         return res;
1127     }
1128 
1129 
1130     bool processKeyEvent(KeyAction action, uint keyCode, uint flags) {
1131         //debug(DebugSDL)
1132         Log.d("processKeyEvent ", action, " X11 key=0x", format("%08x", keyCode), " X11 flags=0x", format("%08x", flags));
1133         keyCode = convertKeyCode(keyCode);
1134         flags = convertKeyFlags(flags);
1135         Log.d("processKeyEvent ", action, " converted key=0x", format("%08x", keyCode), " flags=0x", format("%08x", flags));
1136 
1137         alias KeyCode = dlangui.core.events.KeyCode;
1138         if (action == KeyAction.KeyDown) {
1139             switch(keyCode) {
1140                 case KeyCode.ALT:
1141                     flags |= KeyFlag.Alt;
1142                     break;
1143                 case KeyCode.RALT:
1144                     flags |= KeyFlag.Alt | KeyFlag.RAlt;
1145                     break;
1146                 case KeyCode.LALT:
1147                     flags |= KeyFlag.Alt | KeyFlag.LAlt;
1148                     break;
1149                 case KeyCode.CONTROL:
1150                     flags |= KeyFlag.Control;
1151                     break;
1152                 case KeyCode.RCONTROL:
1153                     flags |= KeyFlag.Control | KeyFlag.RControl;
1154                     break;
1155                 case KeyCode.LCONTROL:
1156                     flags |= KeyFlag.Control | KeyFlag.LControl;
1157                     break;
1158                 case KeyCode.SHIFT:
1159                     flags |= KeyFlag.Shift;
1160                     break;
1161                 case KeyCode.RSHIFT:
1162                     flags |= KeyFlag.Shift | KeyFlag.RShift;
1163                     break;
1164                 case KeyCode.LSHIFT:
1165                     flags |= KeyFlag.Shift | KeyFlag.LShift;
1166                     break;
1167                 default:
1168                     break;
1169             }
1170         }
1171         _keyFlags = flags;
1172 
1173         debug(DebugSDL) Log.d("processKeyEvent ", action, " converted key=0x", format("%08x", keyCode), " converted flags=0x", format("%08x", flags));
1174         bool res = dispatchKeyEvent(new KeyEvent(action, keyCode, flags));
1175         //            if ((keyCode & 0x10000) && (keyCode & 0xF000) != 0xF000) {
1176         //                dchar[1] text;
1177         //                text[0] = keyCode & 0xFFFF;
1178         //                res = dispatchKeyEvent(new KeyEvent(KeyAction.Text, keyCode, flags, cast(dstring)text)) || res;
1179         //            }
1180         if (res) {
1181             debug(keys) Log.d("Calling update() after key event");
1182             //invalidate();
1183             update();
1184         }
1185         return res;
1186     }
1187 
1188     bool processTextInput(dstring ds, uint flags) {
1189         flags = convertKeyFlags(flags);
1190         bool res = dispatchKeyEvent(new KeyEvent(KeyAction.Text, 0, flags, ds));
1191         if (res) {
1192             debug(keys) Log.d("Calling update() after text event");
1193             invalidate();
1194         }
1195         return res;
1196     }
1197 
1198     /// after drawing, call to schedule redraw if animation is active
1199     override void scheduleAnimation() {
1200         invalidate();
1201     }
1202 
1203     TimerThread timer;
1204     private long _nextExpectedTimerTs;
1205 
1206     /// schedule timer for interval in milliseconds - call window.onTimer when finished
1207     override protected void scheduleSystemTimer(long intervalMillis) {
1208         if (!timer) {
1209             timer = new TimerThread(delegate() {
1210                 XEvent ev;
1211                 memset(&ev, 0, ev.sizeof);
1212                 //ev.xclient = XClientMessageEvent.init;
1213                 ev.xclient.type = ClientMessage;
1214                 ev.xclient.message_type = atom_DLANGUI_TIMER_EVENT;
1215                 ev.xclient.window = _win;
1216                 ev.xclient.display = x11display2;
1217                 ev.xclient.format = 32;
1218                 //Log.d("Sending timer event");
1219                 XLockDisplay(x11display2);
1220                 XSendEvent(x11display2, _win, false, StructureNotifyMask, &ev);
1221                 XFlush(x11display2);
1222                 XUnlockDisplay(x11display2);
1223             });
1224         }
1225         if (intervalMillis < 10)
1226             intervalMillis = 10;
1227         long nextts = currentTimeMillis + intervalMillis;
1228         if (_nextExpectedTimerTs == 0 || _nextExpectedTimerTs > nextts) {
1229             _nextExpectedTimerTs = nextts;
1230             timer.set(nextts);
1231         }
1232     }
1233 
1234     bool handleTimer() {
1235         if (!_nextExpectedTimerTs)
1236             return false;
1237         long ts = currentTimeMillis;
1238         if (ts >= _nextExpectedTimerTs) {
1239             _nextExpectedTimerTs = 0;
1240             onTimer();
1241             return true;
1242         }
1243         return false;
1244     }
1245 
1246     /// post event to handle in UI thread (this method can be used from background thread)
1247     override void postEvent(CustomEvent event) {
1248         super.postEvent(event);
1249         XEvent ev;
1250         memset(&ev, 0, ev.sizeof);
1251         ev.xclient.type = ClientMessage;
1252         ev.xclient.window = _win;
1253         ev.xclient.display = x11display2;
1254         ev.xclient.message_type = atom_DLANGUI_TASK_EVENT;
1255         ev.xclient.format = 32;
1256         ev.xclient.data.l[0] = event.uniqueId;
1257         XLockDisplay(x11display2);
1258         XSendEvent(x11display2, _win, false, StructureNotifyMask, &ev);
1259         XFlush(x11display2);
1260         XUnlockDisplay(x11display2);
1261         //        SDL_Event sdlevent;
1262 //        sdlevent.user.type = USER_EVENT_ID;
1263 //        sdlevent.user.code = cast(int)event.uniqueId;
1264 //        sdlevent.user.windowID = windowId;
1265 //        SDL_PushEvent(&sdlevent);
1266     }
1267 
1268     protected uint _lastCursorType = CursorType.None;
1269     /// sets cursor type for window
1270     override protected void setCursorType(uint cursorType) {
1271         if (_lastCursorType != cursorType) {
1272             Log.d("setCursorType(", cursorType, ")");
1273             _lastCursorType = cursorType;
1274             XDefineCursor(x11display, _win, x11cursors[cursorType]);
1275             XFlush(x11display);
1276         }
1277     }
1278 }
1279 
1280 private immutable int CUSTOM_EVENT = 32;
1281 private immutable int TIMER_EVENT = 8;
1282 
1283 class X11Platform : Platform {
1284 
1285     this() {
1286         import std.file : thisExePath;
1287         import std.path : baseName;
1288         _classname = (baseName(thisExePath()) ~ "\0").dup.ptr;
1289     }
1290 
1291     private X11Window[XWindow] _windowMap;
1292     private char* _classname;
1293 
1294     /**
1295      * create window
1296      * Args:
1297      *         windowCaption = window caption text
1298      *         parent = parent Window, or null if no parent
1299      *         flags = WindowFlag bit set, combination of Resizable, Modal, Fullscreen
1300      *      width = window width
1301      *      height = window height
1302      *
1303      * Window w/o Resizable nor Fullscreen will be created with size based on measurement of its content widget
1304      */
1305     override DWindow createWindow(dstring windowCaption, DWindow parent, uint flags = WindowFlag.Resizable, uint width = 0, uint height = 0) {
1306         int newwidth = width;
1307         int newheight = height;
1308         X11Window window = new X11Window(this, windowCaption, parent, flags, newwidth, newheight);
1309         _windowMap[window._win] = window;
1310         return window;
1311     }
1312 
1313     X11Window findWindow(XWindow windowId) {
1314         if (windowId in _windowMap)
1315             return _windowMap[windowId];
1316         return null;
1317     }
1318 
1319     /**
1320      * close window
1321      *
1322      * Closes window earlier created with createWindow()
1323      */
1324     override void closeWindow(DWindow w) {
1325         X11Window window = cast(X11Window)w;
1326         XEvent ev;
1327         memset(&ev, 0, ev.sizeof);
1328         ev.xclient.type = ClientMessage;
1329         ev.xclient.message_type = atom_DLANGUI_CLOSE_WINDOW_EVENT;
1330         ev.xclient.window = window._win;
1331         ev.xclient.display = x11display2;
1332         ev.xclient.format = 32;
1333         Log.d("Sending close window event");
1334         XLockDisplay(x11display2);
1335         XSendEvent(x11display2, window._win, false, StructureNotifyMask, &ev);
1336         XFlush(x11display2);
1337         XUnlockDisplay(x11display2);
1338     }
1339 
1340     bool handleTimers() {
1341         bool handled = false;
1342         foreach(w; _windowMap) {
1343             if (w.handleTimer()) {
1344                 handled = true;
1345                 break;
1346             }
1347         }
1348         return handled;
1349     }
1350 
1351     final bool allWindowsClosed() {
1352         return _windowMap.length == 0;
1353     }
1354 
1355     protected int numberOfPendingEvents(int msecs = 10)
1356     {
1357         import core.sys.posix.sys.select;
1358         int x11displayFd = ConnectionNumber(x11display);
1359         fd_set fdSet;
1360         FD_ZERO(&fdSet);
1361         FD_SET(x11displayFd, &fdSet);
1362         scope(exit) FD_ZERO(&fdSet);
1363 
1364         int eventsInQueue = XEventsQueued(x11display, QueuedAlready);
1365         if (!eventsInQueue) {
1366             import core.stdc.errno;
1367             int selectResult;
1368             do {
1369                 timeval timeout;
1370                 timeout.tv_usec = msecs;
1371                 selectResult = select(x11displayFd + 1, &fdSet, null, null, &timeout);
1372             } while(selectResult == -1 && errno == EINTR);
1373             if (selectResult < 0) {
1374                 Log.e("X11: display fd select error");
1375             } else if (selectResult == 1) {
1376                 //Log.d("X11: XPending");
1377                 eventsInQueue = XPending(x11display);
1378             }
1379         }
1380         return eventsInQueue;
1381     }
1382 
1383     protected void processXEvent(ref XEvent event)
1384     {
1385         XComposeStatus compose;
1386         switch (event.type) {
1387             case ConfigureNotify:
1388                 X11Window w = findWindow(event.xconfigure.window);
1389                 if (w) {
1390                     w._cachedWidth = event.xconfigure.width;
1391                     w._cachedHeight = event.xconfigure.height;
1392                     w.handleWindowStateChange(WindowState.unspecified, Rect(event.xconfigure.x, event.xconfigure.y, event.xconfigure.width, event.xconfigure.height));
1393                 } else {
1394                     Log.e("ConfigureNotify: Window not found");
1395                 }
1396                 break;
1397             case PropertyNotify:
1398                 if (event.xproperty.atom == atom_NET_WM_STATE && event.xproperty.state == PropertyNewValue) {
1399                     X11Window w = findWindow(event.xproperty.window);
1400                     if (w) {
1401                         Atom type;
1402                         int format;
1403                         ubyte* properties;
1404                         c_ulong dataLength, overflow;
1405                         if (XGetWindowProperty(x11display, event.xproperty.window, atom_NET_WM_STATE,
1406                             0, int.max/4, False, AnyPropertyType, &type, &format, &dataLength, &overflow, &properties) == 0) {
1407                             scope(exit) XFree(properties);
1408                             // check for minimized
1409                             bool minimized = false;
1410                             for (int i=0; i < dataLength ; i++) {
1411                                 if (((cast(c_ulong*)properties)[i]) == atom_NET_WM_STATE_HIDDEN) {
1412                                     w.handleWindowStateChange(WindowState.minimized);
1413                                     minimized = true;
1414                                 }
1415                             }
1416                             if (!minimized) {
1417                                 bool maximizedH = false;
1418                                 bool maximizedV = false;
1419                                 for (int i=0; i < dataLength ; i++) {
1420                                     if (((cast(c_ulong*)properties)[i]) == atom_NET_WM_STATE_MAXIMIZED_VERT)
1421                                         maximizedV = true;
1422                                     if (((cast(c_ulong*)properties)[i]) == atom_NET_WM_STATE_MAXIMIZED_HORZ)
1423                                         maximizedH = true;
1424                                 }
1425 
1426                                 if (maximizedV && maximizedH)
1427                                     w.handleWindowStateChange(WindowState.maximized);
1428                                 else
1429                                     w.handleWindowStateChange(WindowState.normal);
1430 
1431                             }
1432                         }
1433                     }
1434                 }
1435                 break;
1436             case MapNotify:
1437                 X11Window w = findWindow(event.xmap.window);
1438                 if (w) {
1439                     w.handleWindowStateChange(WindowState.normal);
1440                 }
1441                 break;
1442             case UnmapNotify:
1443                 X11Window w = findWindow(event.xunmap.window);
1444                 if (w) {
1445                     w.handleWindowStateChange(WindowState.hidden);
1446                 }
1447                 break;
1448             case Expose:
1449                 if (event.xexpose.count == 0) {
1450                     X11Window w = findWindow(event.xexpose.window);
1451                     if (w) {
1452                         w.invalidate();
1453                     } else {
1454                         Log.e("Expose: Window not found");
1455                     }
1456                 } else {
1457                     Log.d("Expose: non-0 count");
1458                 }
1459                 break;
1460             case KeyPress:
1461                 Log.d("X11: KeyPress event");
1462                 X11Window w = findWindow(event.xkey.window);
1463                 if (w) {
1464                     char[100] buf;
1465                     KeySym ks;
1466                     Status s;
1467                     if (!w.xic) {
1468                         w.xic = XCreateIC(xim,
1469                             XNInputStyle, XIMPreeditNothing | XIMStatusNothing,
1470                             XNClientWindow, w._win, 0);
1471                         if (!w.xic) {
1472                             Log.e("Cannot create input context");
1473                         }
1474                     }
1475 
1476                     if (!w.xic)
1477                         XLookupString(&event.xkey, buf.ptr, buf.length - 1, &ks, &compose);
1478                     else {
1479                         Xutf8LookupString(w.xic, &event.xkey, buf.ptr, cast(int)buf.length - 1, &ks, &s);
1480                         if (s != XLookupChars && s != XLookupBoth)
1481                             XLookupString(&event.xkey, buf.ptr, buf.length - 1, &ks, &compose);
1482                     }
1483                     foreach(ref ch; buf) {
1484                         if (ch == 255 || ch < 32 || ch == 127)
1485                             ch = 0;
1486                     }
1487                     string txt = fromStringz(buf.ptr).dup;
1488                     import std.utf;
1489                     dstring dtext;
1490                     try {
1491                         if (txt.length)
1492                             dtext = toUTF32(txt);
1493                     } catch (UTFException e) {
1494                         // ignore, invalid text
1495                     }
1496                     debug(x11) Log.d("X11: KeyPress event bytes=", txt.length, " text=", txt, " dtext=", dtext);
1497                     if (dtext.length) {
1498                         w.processTextInput(dtext, event.xkey.state);
1499                     } else {
1500                         w.processKeyEvent(KeyAction.KeyDown, cast(uint)ks,
1501                             //event.xkey.keycode,
1502                             event.xkey.state);
1503                     }
1504                 } else {
1505                     Log.e("Window not found");
1506                 }
1507                 break;
1508             case KeyRelease:
1509                 Log.d("X11: KeyRelease event");
1510                 X11Window w = findWindow(event.xkey.window);
1511                 if (w) {
1512                     char[100] buf;
1513                     KeySym ks;
1514                     XLookupString(&event.xkey, buf.ptr, buf.length - 1, &ks, &compose);
1515                     w.processKeyEvent(KeyAction.KeyUp, cast(uint)ks,
1516                         //event.xkey.keycode,
1517                         event.xkey.state);
1518                 } else {
1519                     Log.e("Window not found");
1520                 }
1521                 break;
1522             case ButtonPress:
1523                 Log.d("X11: ButtonPress event");
1524                 X11Window w = findWindow(event.xbutton.window);
1525                 if (w) {
1526                     if (event.xbutton.button == 4 || event.xbutton.button == 5) {
1527                         w.processMouseEvent(MouseAction.Wheel, 0, 0, 0, event.xbutton.button == 4 ? 1 : -1);
1528                     } else {
1529                         w.processMouseEvent(MouseAction.ButtonDown, event.xbutton.button, event.xbutton.state, event.xbutton.x, event.xbutton.y);
1530                     }
1531                 } else {
1532                     Log.e("Window not found");
1533                 }
1534                 break;
1535             case ButtonRelease:
1536                 Log.d("X11: ButtonRelease event");
1537                 X11Window w = findWindow(event.xbutton.window);
1538                 if (w) {
1539                     w.processMouseEvent(MouseAction.ButtonUp, event.xbutton.button, event.xbutton.state, event.xbutton.x, event.xbutton.y);
1540                 } else {
1541                     Log.e("Window not found");
1542                 }
1543                 break;
1544             case MotionNotify:
1545                 debug(x11) Log.d("X11: MotionNotify event");
1546                 X11Window w = findWindow(event.xmotion.window);
1547                 if (w) {
1548                     w.processMouseEvent(MouseAction.Move, 0, event.xmotion.state, event.xmotion.x, event.xmotion.y);
1549                 } else {
1550                     Log.e("Window not found");
1551                 }
1552                 break;
1553             case EnterNotify:
1554                 Log.d("X11: EnterNotify event");
1555                 X11Window w = findWindow(event.xcrossing.window);
1556                 if (w) {
1557                     w.processMouseEvent(MouseAction.Move, 0, event.xmotion.state, event.xcrossing.x, event.xcrossing.y);
1558                 } else {
1559                     Log.e("Window not found");
1560                 }
1561                 break;
1562             case LeaveNotify:
1563                 Log.d("X11: LeaveNotify event");
1564                 X11Window w = findWindow(event.xcrossing.window);
1565                 if (w) {
1566                     w.processMouseEvent(MouseAction.Leave, 0, event.xcrossing.state, event.xcrossing.x, event.xcrossing.y);
1567                 } else {
1568                     Log.e("Window not found");
1569                 }
1570                 break;
1571             case CreateNotify:
1572                 Log.d("X11: CreateNotify event");
1573                 X11Window w = findWindow(event.xcreatewindow.window);
1574                 if (!w) {
1575                     Log.e("Window not found");
1576                 }
1577                 break;
1578             case DestroyNotify:
1579                 Log.d("X11: DestroyNotify event");
1580                 break;
1581             case ResizeRequest:
1582                 Log.d("X11: ResizeRequest event");
1583                 X11Window w = findWindow(event.xresizerequest.window);
1584                 if (!w) {
1585                     Log.e("Window not found");
1586                 }
1587                 break;
1588             case FocusIn:
1589                 Log.d("X11: FocusIn event");
1590                 X11Window w = findWindow(event.xfocus.window);
1591                 if (w)
1592                     w.handleWindowActivityChange(true);
1593                 else
1594                     Log.e("Window not found");
1595                 break;
1596             case FocusOut:
1597                 Log.d("X11: FocusOut event");
1598                 X11Window w = findWindow(event.xfocus.window);
1599                 if (w)
1600                     w.handleWindowActivityChange(false);
1601                 else
1602                     Log.e("Window not found");
1603                 break;
1604             case KeymapNotify:
1605                 Log.d("X11: KeymapNotify event");
1606                 X11Window w = findWindow(event.xkeymap.window);
1607                 break;
1608             case SelectionClear:
1609                 Log.d("X11: SelectionClear event");
1610                 break;
1611             case SelectionRequest:
1612                 debug(x11) Log.d("X11: SelectionRequest event");
1613                 if (event.xselectionrequest.owner in _windowMap) {
1614                     XSelectionRequestEvent *selectionRequest = &event.xselectionrequest;
1615 
1616                     XEvent selectionEvent;
1617                     memset(&selectionEvent, 0, selectionEvent.sizeof);
1618                     selectionEvent.xany.type = SelectionNotify;
1619                     selectionEvent.xselection.selection = selectionRequest.selection;
1620                     selectionEvent.xselection.target = selectionRequest.target;
1621                     selectionEvent.xselection.property = None;
1622                     selectionEvent.xselection.requestor = selectionRequest.requestor;
1623                     selectionEvent.xselection.time = selectionRequest.time;
1624 
1625                     if (selectionRequest.target == XA_STRING || selectionRequest.target == atom_UTF8_STRING) {
1626                         int currentSelectionFormat;
1627                         Atom currentSelectionType;
1628                         c_ulong selectionDataLength, overflow;
1629                         ubyte* selectionDataPtr;
1630                         if (XGetWindowProperty(x11display, DefaultRootWindow(x11display), atom_DLANGUI_CLIPBOARD_BUFFER,
1631                             0, int.max/4, False, selectionRequest.target,
1632                             &currentSelectionType, &currentSelectionFormat, &selectionDataLength,
1633                             &overflow, &selectionDataPtr) == 0)
1634                         {
1635                             scope(exit) XFree(selectionDataPtr);
1636                             XChangeProperty(x11display, selectionRequest.requestor, selectionRequest.property,
1637                                 selectionRequest.target, 8, PropModeReplace,
1638                                 selectionDataPtr, cast(int)selectionDataLength);
1639                         }
1640                         selectionEvent.xselection.property = selectionRequest.property;
1641                     } else if (selectionRequest.target == atom_TARGETS) {
1642                         Atom[3] supportedFormats = [atom_UTF8_STRING, XA_STRING, atom_TARGETS];
1643                         XChangeProperty(x11display, selectionRequest.requestor, selectionRequest.property,
1644                             XA_ATOM, 32, PropModeReplace,
1645                             cast(ubyte*)supportedFormats.ptr, cast(int)supportedFormats.length);
1646                         selectionEvent.xselection.property = selectionRequest.property;
1647                     }
1648                     XSendEvent(x11display, selectionRequest.requestor, False, 0, &selectionEvent);
1649                 }
1650                 break;
1651             case SelectionNotify:
1652                 debug(x11) Log.d("X11: SelectionNotify event");
1653                 X11Window w = findWindow(event.xselection.requestor);
1654                 if (w) {
1655                     waitingForSelection = false;
1656                 }
1657                 break;
1658             case ClientMessage:
1659                 debug(x11) Log.d("X11: ClientMessage event");
1660                 X11Window w = findWindow(event.xclient.window);
1661                 if (w) {
1662                     if (event.xclient.message_type == atom_DLANGUI_TASK_EVENT) {
1663                         w.handlePostedEvent(cast(uint)event.xclient.data.l[0]);
1664                     } else if (event.xclient.message_type == atom_DLANGUI_TIMER_EVENT) {
1665                         w.handleTimer();
1666                     } else if (event.xclient.message_type == atom_DLANGUI_REDRAW_EVENT) {
1667                         if (event.xclient.data.l[0] == w._lastRedrawEventCode)
1668                             w.redraw();
1669                     } else if (event.xclient.message_type == atom_WM_PROTOCOLS) {
1670                         Log.d("Handling WM_PROTOCOLS");
1671                         if ((event.xclient.format == 32) && (event.xclient.data.l[0]) == atom_WM_DELETE_WINDOW) {
1672                             Log.d("Handling WM_DELETE_WINDOW");
1673                             _windowMap.remove(w._win);
1674                             destroy(w);
1675                         }
1676                     } else if (event.xclient.message_type == atom_DLANGUI_CLOSE_WINDOW_EVENT) {
1677                         _windowMap.remove(w._win);
1678                         destroy(w);
1679                     }
1680                 } else {
1681                     Log.e("Window not found");
1682                 }
1683                 break;
1684             default:
1685                 break;
1686         }
1687     }
1688 
1689     protected void pumpEvents()
1690     {
1691         XFlush(x11display);
1692         // Note:  only events we set the mask for are detected!
1693         while(numberOfPendingEvents())
1694         {
1695             if (allWindowsClosed())
1696                 break;
1697             XEvent event;        /* the XEvent declaration !!! */
1698             XNextEvent(x11display, &event);
1699             processXEvent(event);
1700         }
1701     }
1702 
1703     /**
1704      * Starts application message loop.
1705      *
1706      * When returned from this method, application is shutting down.
1707      */
1708     override int enterMessageLoop() {
1709         Log.d("enterMessageLoop()");
1710 
1711         while(!allWindowsClosed()) {
1712             pumpEvents();
1713         }
1714         return 0;
1715     }
1716 
1717     /// check has clipboard text
1718     override bool hasClipboardText(bool mouseBuffer = false) {
1719         const selectionType = mouseBuffer ? XA_PRIMARY : atom_CLIPBOARD;
1720         return XGetSelectionOwner(x11display, selectionType) != None;
1721     }
1722 
1723     protected bool waitingForSelection;
1724     /// retrieves text from clipboard (when mouseBuffer == true, use mouse selection clipboard - under linux)
1725     override dstring getClipboardText(bool mouseBuffer = false) {
1726         const selectionType = mouseBuffer ? XA_PRIMARY : atom_CLIPBOARD;
1727         auto owner = XGetSelectionOwner(x11display, selectionType);
1728         if (owner == None) {
1729             Log.d("Selection owner is none");
1730             return ""d;
1731         } else {
1732             // Find any top-level window
1733             XWindow xwindow;
1734             foreach(w; _windowMap) {
1735                 if (w._parent is null && w._win != None) {
1736                     xwindow = w._win;
1737                     break;
1738                 }
1739             }
1740             if (xwindow != None) {
1741                 import std.datetime;
1742                 waitingForSelection = true;
1743                 XConvertSelection(x11display, selectionType, atom_UTF8_STRING, atom_DLANGUI_CLIPBOARD_BUFFER, xwindow, CurrentTime);
1744                 auto stopWaiting = Clock.currTime() + dur!"msecs"(500);
1745                 while(waitingForSelection) {
1746                     if (stopWaiting <= Clock.currTime()) {
1747                         waitingForSelection = false;
1748                         setClipboardText(""d);
1749                         Log.e("Waiting for clipboard contents timeout");
1750                         return ""d;
1751                     }
1752                     pumpEvents();
1753                 }
1754                 Atom selectionTarget;
1755                 int selectionFormat;
1756                 c_ulong selectionDataLength, overflow;
1757                 ubyte* selectionDataPtr;
1758                 if (XGetWindowProperty(x11display, xwindow, atom_DLANGUI_CLIPBOARD_BUFFER, 0, int.max/4, False, 0, &selectionTarget, &selectionFormat, &selectionDataLength, &overflow, &selectionDataPtr) == 0)
1759                 {
1760                     scope(exit) XFree(selectionDataPtr);
1761                     if (selectionTarget == XA_STRING || selectionTarget == atom_UTF8_STRING) {
1762                         char[] selectionText = cast(char[])selectionDataPtr[0..selectionDataLength];
1763                         return toUTF32(selectionText);
1764                     } else {
1765                         Log.d("Selection type is not a string!");
1766                     }
1767                 }
1768             } else {
1769                 Log.d("Could not find any window to get selection");
1770             }
1771         }
1772         return ""d;
1773     }
1774 
1775     /// sets text to clipboard (when mouseBuffer == true, use mouse selection clipboard - under linux)
1776     override void setClipboardText(dstring text, bool mouseBuffer = false) {
1777         if (!mouseBuffer && atom_CLIPBOARD == None) {
1778             Log.e("No CLIPBOARD atom available");
1779             return;
1780         }
1781         auto selection = mouseBuffer ? XA_PRIMARY : atom_CLIPBOARD;
1782         XWindow xwindow = None;
1783         // Find any top-level window
1784         foreach(w; _windowMap) {
1785             if (w._parent is null && w._win != None) {
1786                 xwindow = w._win;
1787             }
1788         }
1789         if (xwindow == None) {
1790             Log.e("Could not find window to save clipboard text");
1791             return;
1792         }
1793 
1794         auto textc = text.toUTF8;
1795         XChangeProperty(x11display, DefaultRootWindow(x11display), atom_DLANGUI_CLIPBOARD_BUFFER, atom_UTF8_STRING, 8, PropModeReplace, cast(ubyte*)textc.ptr, cast(int)textc.length);
1796 
1797         if (XGetSelectionOwner(x11display, selection) != xwindow) {
1798             XSetSelectionOwner(x11display, selection, xwindow, CurrentTime);
1799         }
1800     }
1801 
1802     /// calls request layout for all windows
1803     override void requestLayout() {
1804         foreach(w; _windowMap) {
1805             w.requestLayout();
1806         }
1807     }
1808 
1809     /// 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
1810     override bool hasModalWindowsAbove(DWindow w) {
1811         X11Window x11Win = cast (X11Window) w;
1812         if (x11Win) {
1813             return x11Win.hasVisibleModalChild();
1814         }
1815         return false;
1816     }
1817 
1818     /// handle theme change: e.g. reload some themed resources
1819     override void onThemeChanged() {
1820         super.onThemeChanged();
1821         foreach(w; _windowMap)
1822             w.dispatchThemeChanged();
1823     }
1824 
1825 }
1826 
1827 import core.thread;
1828 import core.sync.mutex;
1829 import core.sync.condition;
1830 class TimerThread : Thread {
1831     Mutex mutex;
1832     Condition condition;
1833     bool stopped;
1834     long nextEventTs;
1835     void delegate() callback;
1836 
1837     this(void delegate() timerCallback) {
1838         callback = timerCallback;
1839         mutex = new Mutex();
1840         condition = new Condition(mutex);
1841         super(&run);
1842         start();
1843     }
1844 
1845     ~this() {
1846         stop();
1847         destroy(condition);
1848         destroy(mutex);
1849     }
1850 
1851     void set(long nextTs) {
1852         mutex.lock();
1853         if (nextEventTs == 0 || nextEventTs > nextTs) {
1854             nextEventTs = nextTs;
1855             condition.notify();
1856         }
1857         mutex.unlock();
1858     }
1859     void run() {
1860 
1861         while (!stopped) {
1862             bool expired = false;
1863 
1864             mutex.lock();
1865 
1866             long ts = currentTimeMillis;
1867             long timeToWait = nextEventTs == 0 ? 1000000 : nextEventTs - ts;
1868             if (timeToWait < 10)
1869                 timeToWait = 10;
1870 
1871             if (nextEventTs == 0)
1872                 condition.wait();
1873             else
1874                 condition.wait(dur!"msecs"(timeToWait));
1875 
1876             if (stopped) {
1877                 mutex.unlock();
1878                 break;
1879             }
1880             ts = currentTimeMillis;
1881             if (nextEventTs && nextEventTs < ts && !stopped) {
1882                 expired = true;
1883                 nextEventTs = 0;
1884             }
1885 
1886             mutex.unlock();
1887 
1888             if (expired)
1889                 callback();
1890         }
1891     }
1892     void stop() {
1893         if (stopped)
1894             return;
1895         stopped = true;
1896         mutex.lock();
1897         condition.notify();
1898         mutex.unlock();
1899         join();
1900     }
1901 }
1902 
1903 
1904 extern(C) int DLANGUImain(string[] args)
1905 {
1906     initLogs();
1907 
1908     if (!initFontManager()) {
1909         Log.e("******************************************************************");
1910         Log.e("No font files found!!!");
1911         Log.e("Currently, only hardcoded font paths implemented.");
1912         Log.e("Probably you can modify sdlapp.d to add some fonts for your system.");
1913         Log.e("TODO: use fontconfig");
1914         Log.e("******************************************************************");
1915         assert(false);
1916     }
1917     initResourceManagers();
1918 
1919     currentTheme = createDefaultTheme();
1920 
1921     XInitThreads();
1922 
1923     /* use the information from the environment variable DISPLAY
1924        to create the X connection:
1925     */
1926     x11display = XOpenDisplay(null);
1927     if (!x11display) {
1928         Log.e("Cannot open X11 display");
1929         return 1;
1930     }
1931     x11display2 = XOpenDisplay(null);
1932     if (!x11display2) {
1933         Log.e("Cannot open secondary connection for X11 display");
1934         return 1;
1935     }
1936 
1937     x11screen = DefaultScreen(x11display);
1938 
1939     static if (ENABLE_OPENGL) {
1940         try {
1941             DerelictGL3.missingSymbolCallback = &gl3MissingSymFunc;
1942             DerelictGL3.load();
1943             DerelictGL.missingSymbolCallback = &gl3MissingSymFunc;
1944             DerelictGL.load();
1945             Log.d("OpenGL library loaded ok");
1946             GLint[] att = [ GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None ];
1947             XWindow root;
1948             root = DefaultRootWindow(x11display);
1949             x11visual = glXChooseVisual(x11display, 0, cast(int*)att.ptr);
1950             if (x11visual) {
1951                 x11cmap = XCreateColormap(x11display, root, cast(Visual*)x11visual.visual, AllocNone);
1952                 _enableOpengl = true;
1953             } else {
1954                 Log.e("Cannot find suitable Visual for using of OpenGL");
1955             }
1956         } catch (Exception e) {
1957             Log.e("Cannot load OpenGL library", e);
1958         }
1959     }
1960 
1961 
1962     setupX11Atoms();
1963 
1964     x11cursors[CursorType.None] = XCreateFontCursor(x11display, XC_arrow);
1965     x11cursors[CursorType.NotSet] = XCreateFontCursor(x11display, XC_arrow);
1966     x11cursors[CursorType.Arrow] = XCreateFontCursor(x11display, XC_left_ptr);
1967     x11cursors[CursorType.IBeam] = XCreateFontCursor(x11display, XC_xterm);
1968     x11cursors[CursorType.Wait] = XCreateFontCursor(x11display, XC_watch);
1969     x11cursors[CursorType.Crosshair] = XCreateFontCursor(x11display, XC_tcross);
1970     x11cursors[CursorType.WaitArrow] = XCreateFontCursor(x11display, XC_watch);
1971     x11cursors[CursorType.SizeNWSE] = XCreateFontCursor(x11display, XC_fleur);
1972     x11cursors[CursorType.SizeNESW] = XCreateFontCursor(x11display, XC_fleur);
1973     x11cursors[CursorType.SizeWE] = XCreateFontCursor(x11display, XC_sb_h_double_arrow);
1974     x11cursors[CursorType.SizeNS] = XCreateFontCursor(x11display, XC_sb_v_double_arrow);
1975     x11cursors[CursorType.SizeAll] = XCreateFontCursor(x11display, XC_fleur);
1976     x11cursors[CursorType.No] = XCreateFontCursor(x11display, XC_pirate);
1977     x11cursors[CursorType.Hand] = XCreateFontCursor(x11display, XC_hand2);
1978 
1979     xim = XOpenIM(x11display, null, null, null);
1980     if (!xim) {
1981         Log.e("Cannot open input method");
1982     }
1983 
1984     Log.d("X11 display=", x11display, " screen=", x11screen);
1985 
1986 
1987 
1988     X11Platform x11platform = new X11Platform();
1989 
1990     Platform.setInstance(x11platform);
1991 
1992     int res = 0;
1993 
1994     version (unittest) {
1995     } else {
1996         res = UIAppMain(args);
1997     }
1998 
1999     //Log.e("Widget instance count after UIAppMain: ", Widget.instanceCount());
2000 
2001     Log.d("Destroying X11 platform");
2002     Platform.setInstance(null);
2003 
2004     releaseResourcesOnAppExit();
2005 
2006 
2007     XCloseDisplay(x11display);
2008     XCloseDisplay(x11display2);
2009 
2010     Log.d("Exiting main width result=", res);
2011 
2012     return res;
2013 }