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