1 // Written in the D programming language.
2 
3 /**
4 
5 This module contains implementation of Win32 platform support
6 
7 Provides XCB platform and window classes.
8 
9 Usually you don't need to use this module directly.
10 
11 NOTE: Current implementation of XCB backend does not work well. Consider using of SDL2 backend instead.
12 
13 Synopsis:
14 
15 ----
16 import dlangui.platforms.x11.x11app;
17 ----
18 
19 Copyright: Vadim Lopatin, 2014
20 License:   Boost License 1.0
21 Authors:   Vadim Lopatin, coolreader.org@gmail.com
22 */
23 module src.dlangui.platforms.x11.x11app;
24 
25 version(USE_XCB):
26 
27 import std.string;
28 import std.c.linux.X11.xcb.xcb;
29 import std.c.linux.X11.xcb.shm;
30 import std.c.linux.X11.xcb.xproto;
31 import std.c.linux.X11.xcb.image;
32 import std.c.linux.X11.keysymdef;
33 import std.c.linux.linux;
34 import std.c.stdlib;
35 import std.conv;
36 
37 import dlangui.core.logger;
38 import dlangui.core.events;
39 import dlangui.core.files;
40 import dlangui.graphics.drawbuf;
41 import dlangui.graphics.fonts;
42 import dlangui.graphics.ftfonts;
43 import dlangui.graphics.resources;
44 import dlangui.widgets.styles;
45 import dlangui.platforms.common.platform;
46 
47 version (USE_OPENGL) {
48 	import dlangui.graphics.glsupport;
49 }
50 
51 import derelict.opengl3.gl3;
52 import derelict.opengl3.glx;
53 
54 //	pragma(lib, "xcb");
55 //	pragma(lib, "xcb-shm");
56 //	pragma(lib, "xcb-image");
57 //	pragma(lib, "X11-xcb");
58 //	pragma(lib, "X11");
59 //	pragma(lib, "dl");
60 
61 extern (System)
62 xcb_connection_t *XGetXCBConnection(std.c.linux.X11.Xlib.Display *dpy);
63 enum XEventQueueOwner { XlibOwnsEventQueue = 0, XCBOwnsEventQueue };
64 extern (System)
65 void XSetEventQueueOwner(std.c.linux.X11.Xlib.Display *dpy, XEventQueueOwner owner);
66 
67 struct xcb_key_symbols_t {};
68 /* enumeration for col parameter? */
69 enum xcb_lookup_t {
70 	xcb_lookup_none_t   = 1,
71 	xcb_lookup_chars_t  = 2,
72 	xcb_lookup_key_sym_t = 3,
73 	xcb_lookup_both_t   = 4
74 };
75 
76 extern(C) xcb_key_symbols_t *xcb_key_symbols_alloc        (xcb_connection_t         *c);
77 
78 extern(C) void xcb_key_symbols_free         (xcb_key_symbols_t         *syms);
79 
80 extern(C) xcb_keysym_t xcb_key_symbols_get_keysym    (xcb_key_symbols_t         *syms,
81 					xcb_keycode_t             keycode,
82 					int                    col);
83 
84 extern(C) xcb_keysym_t xcb_key_press_lookup_keysym   (xcb_key_symbols_t         *syms,
85 						xcb_key_press_event_t      *event,
86 						int                    col);
87 
88 extern(C) xcb_keysym_t xcb_key_release_lookup_keysym (xcb_key_symbols_t         *syms,
89 						xcb_key_release_event_t    *event,
90 						int                    col);
91 class XCBWindow : Window {
92 	xcb_window_t         _w;
93 	xcb_gcontext_t       _g;
94 	xcb_image_t * 		_image;
95 	xcb_shm_segment_info_t shminfo;
96     version(USE_OPENGL) {
97         /* Create GLX Window */
98         private GLXDrawable _drawable;
99     	private GLXWindow _glxwindow;
100     	private GLXContext _context;
101     	private GLXFBConfig _fb_config;
102     }
103 	private int _visualID = 0;
104 	private xcb_colormap_t _colormap;
105 
106 
107 	@property xcb_window_t windowId() { return _w; }
108 	this(string caption, Window parent) {
109 		_caption = caption;
110 		Log.d("Creating XCB window");
111 		create();
112 	}
113 	~this() {
114 		Log.d("Destroying window");
115 	}
116 
117 	bool create() {
118 		import std.c.linux.X11.Xlib;
119 
120 		uint mask;
121 		uint[3] values;
122 
123 		// disable opengl for testing
124 		_enableOpengl = false;
125 		/* create black graphics context */
126 		if (true || !_enableOpengl) {
127 			_g = xcb_generate_id(_xcbconnection);
128 			_w = _xcbscreen.root;
129 			mask = XCB_GC_FOREGROUND | XCB_GC_GRAPHICS_EXPOSURES;
130 			values[0] = _xcbscreen.black_pixel;
131 			values[1] = 0;
132 			xcb_create_gc(_xcbconnection, _g, _w, mask, &values[0]);
133 		}
134 
135 		ubyte depth = _xcbscreen.root_depth;
136 		/* create window */
137 		_w = xcb_generate_id(_xcbconnection);
138 
139 		Log.d("window=", _w, " gc=", _g);
140         version (USE_OPENGL) {
141 			if (_enableOpengl) {
142 				int[] visual_attribs = [
143 					        GLX_RENDER_TYPE, GLX_RGBA_BIT,
144 					        GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT,
145 					        GLX_DOUBLEBUFFER, 1,
146 					        GLX_RED_SIZE, 8,
147 					        GLX_GREEN_SIZE, 8,
148 					        GLX_BLUE_SIZE, 8,
149 							std.c.linux.X11.Xlib.None];
150 
151 			    Log.d("Getting framebuffer config");
152 			    int fbcount;
153 			    GLXFBConfig *fbc = glXChooseFBConfig(_display, DefaultScreen(_display), visual_attribs.ptr, &fbcount);
154 			    if (!fbc)
155 			    {
156 			        Log.d("Failed to retrieve a framebuffer config");
157 			        //return 1;
158 			    }
159 
160 			    Log.d("Getting XVisualInfo");
161 				_fb_config = fbc[0];
162 			    auto vi = glXGetVisualFromFBConfig(_display, _fb_config);
163 
164 				//auto vi = glXChooseVisual(_display, std.c.linux.X11.Xlib.DefaultScreen(_display), attributeList.ptr);
165 				_visualID = vi.visualid;
166 				//swa.colormap = std.c.linux.X11.Xlib.XCreateColormap(_display, std.c.linux.X11.Xlib.RootWindow(_display, vi.screen), vi.visual, 0); // AllocNone
167 
168 				Log.d("Creating color map");
169 				_colormap = xcb_generate_id(_xcbconnection);
170 		        /* Create colormap */
171 		        xcb_create_colormap(
172 		            _xcbconnection,
173 		            XCB_COLORMAP_ALLOC_NONE,
174 		            _colormap,
175 		            _xcbscreen.root,
176 		            _visualID
177 		        );
178 				depth = cast(ubyte)vi.depth;
179 			}
180         }
181 		//mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;
182 		//values[0] = _xcbscreen.white_pixel;
183 
184 		int visualId;
185 		uint eventmask =
186 				XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE
187 			| XCB_EVENT_MASK_POINTER_MOTION | XCB_EVENT_MASK_BUTTON_MOTION
188 			| XCB_EVENT_MASK_ENTER_WINDOW   | XCB_EVENT_MASK_LEAVE_WINDOW
189 			| XCB_EVENT_MASK_KEY_PRESS      | XCB_EVENT_MASK_KEY_RELEASE
190 			| XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_VISIBILITY_CHANGE;
191 		if (_enableOpengl) {
192 			mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK | XCB_CW_COLORMAP;
193 			values[0] = _xcbscreen.white_pixel;
194 			values[1] = eventmask;
195 			values[2] = _colormap;
196 			//visualId = _xcbscreen.root_visual;
197 			visualId = _visualID;
198 		} else {
199 			mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;
200 			values[0] = _xcbscreen.white_pixel;
201 			values[1] = eventmask;
202 			visualId = _xcbscreen.root_visual;
203 		}
204 		Log.d("xcb_create_window - window=", _w, " VisualID=", _visualID);
205 		auto res = xcb_create_window(_xcbconnection,
206 			depth, //_xcbscreen.root_depth,
207 			//XCB_COPY_FROM_PARENT,//_xcbscreen.root_depth,
208 			_w,
209 			_xcbscreen.root,
210 		    30, 30, 800, 650,
211 			1,
212 		    XCB_WINDOW_CLASS_INPUT_OUTPUT,
213 			visualId,
214 		    mask,
215 			&values[0]);
216 		xcb_flush(_xcbconnection);
217 		windowCaption = _caption;
218 		return true;
219 	}
220 
221 	private int _imageDx;
222 	private int _imageDy;
223 	void createImage() {
224 		if (_image) {
225 			if (_imageDx == _imageDx && _imageDy == _dy)
226 				return; // already have image of proper size
227 	        Log.i("CRXCBScreen::createImage - destroying existing image");
228 	        xcb_image_destroy(_image);
229 			_image = null;
230 		}
231 		_imageDx = _dx;
232 		_imageDy = _dy;
233 	    Log.i("CRXCBScreen::createImage ", _dx, "x", _dy);
234 	    xcb_shm_query_version_reply_t * rep_shm;
235 	    rep_shm = xcb_shm_query_version_reply (_xcbconnection,
236 	            xcb_shm_query_version(_xcbconnection),
237 	            null);
238 	    if(rep_shm) {
239 	        xcb_image_format_t format;
240 	        int shmctl_status;
241 
242 	        if (rep_shm.shared_pixmaps &&
243 	                (rep_shm.major_version > 1 || rep_shm.minor_version > 0))
244 	            format = cast(xcb_image_format_t)rep_shm.pixmap_format;
245 	        else
246 	            format = XCB_IMAGE_FORMAT_Z_PIXMAP;
247 
248 	        _image = xcb_image_create_native (_xcbconnection, cast(short)_dx, cast(short)_dy,
249 	                format, _xcbscreendepth, null, ~0, null);
250 	        //format, depth, NULL, ~0, NULL);
251 	        //format, depth, NULL, ~0, NULL);
252 	        assert(_image);
253 
254 	        shminfo.shmid = shmget (IPC_PRIVATE,
255 	                _image.stride*_image.height,
256 	                IPC_CREAT | octal!777);
257 	        assert(shminfo.shmid != cast(uint)-1);
258 	        shminfo.shmaddr = cast(ubyte*)shmat (shminfo.shmid, null, 0);
259 	        assert(shminfo.shmaddr);
260 	        _image.data = shminfo.shmaddr;
261 	        Log.d("Created image depth=", _image.depth, " bpp=", _image.bpp, " stride=", _image.stride );
262 
263 	        shminfo.shmseg = xcb_generate_id (_xcbconnection);
264 	        xcb_shm_attach (_xcbconnection, shminfo.shmseg,
265 	                shminfo.shmid, 0);
266 	        shmctl_status = shmctl(shminfo.shmid, IPC_RMID, null);
267 	        assert(shmctl_status != -1);
268 	        free(rep_shm);
269 	    } else {
270 	        Log.e("Can't get shms");
271 	    }
272 	}
273 
274 	void draw(ColorDrawBuf buf) {
275 	    int i;
276 	    i = xcb_image_shm_get(_xcbconnection, _w,
277 	            _image, shminfo,
278 	            0, 0,
279 	            XCB_ALL_PLANES);
280 	    if (!i) {
281 	        Log.e("cannot get shm image");
282 	        return;
283 	    }
284 		Rect rc;
285 		rc.right = buf.width;
286 		rc.bottom = buf.height;
287 		switch ( _image.bpp ) {
288 	        case 32:
289 	            {
290 	                for (int y = rc.top; y<rc.bottom; y++) {
291 	                    uint * src = buf.scanLine(y);
292 	                    uint * dst = cast(uint *)(_image.data + _image.stride * y);
293 	                    for ( int x = 0; x < rc.right; x++ ) {
294 	                        uint data = src[x];
295 	                        dst[x] = data;
296 	                    }
297 	                }
298 	            }
299 	            break;
300 			default:
301 				Log.d("image bit depth not supported: ", _image.bpp);
302 				break;
303 		}
304 	    xcb_image_shm_put(_xcbconnection, _w, _g,
305 	            _image, shminfo,
306 	            cast(short)rc.left, cast(short)rc.top, cast(short)rc.left, cast(short)rc.top, cast(ushort)rc.width(), cast(ushort)rc.height(), 0);
307 	    xcb_flush(_xcbconnection);
308 	}
309 
310 	bool _derelictgl3Reloaded;
311 	override void show() {
312 		Log.d("XCBWindow.show()");
313 		/* map (show) the window */
314 		xcb_map_window(_xcbconnection, _w);
315 		xcb_flush(_xcbconnection);
316 		//_enableOpengl = false; // test
317         version (USE_OPENGL) {
318 			if (_enableOpengl && !_glxwindow) {
319 				Log.d("Calling glXCreateWindow display=", _display, " fbconfig=", _fb_config, " window=", _w);
320 	            _glxwindow = glXCreateWindow(
321 			                _display,
322 			                _fb_config,
323 			                _w,
324 			                null);
325 				if (!_glxwindow) {
326 					Log.e("Failed to create GLX window: disabling OpenGL");
327 					_enableOpengl = false;
328 				} else {
329 					import derelict.opengl3.glxext;
330 					import std.c.linux.X11.Xlib;
331 
332 					_drawable = _glxwindow;
333 
334 					if (!_derelictgl3Reloaded) {
335 
336 						int count;
337                     	glGetIntegerv( GL_NUM_EXTENSIONS, &count );
338 						Log.d("Number of extensions: ", count);
339                     	for( int i=0; i<count; ++i ) {
340 							auto e = glGetStringi( GL_EXTENSIONS, i );
341 							Log.d("Extension: ", fromStringz(e));
342                     	}
343 
344 
345 						Log.e("Reloading DerelictGL3");
346 						_derelictgl3Reloaded = true;
347 			            _context = glXCreateNewContext(_display, _fb_config, GLX_RGBA_TYPE, null, true);
348 						if (_context is null) {
349 							Log.e("Cannot create temporary context");
350 						}
351 						//glXMakeContextCurrent(_display, _drawable, _drawable, _context);
352 						glXMakeContextCurrent(_display, _glxwindow, _glxwindow, _context);
353 						//glXMakeCurrent(_display, _w, _context);
354 						DerelictGL3.reload();
355 						Log.e("Reloaded DerelictGL3 - removing temporary context");
356 						glXMakeCurrent(_display, 0, null);
357 						Log.e("Destroying context");
358 						glXDestroyContext(_display, _context);
359 						Log.e("DerelictGL3 initialized");
360 						_context = null;
361 					}
362 
363 
364 					// Get the default screen's GLX extension list
365       				const char *glxExts = glXQueryExtensionsString( _display,
366                                                     DefaultScreen( _display ) );
367 					Log.d("GLX Extensions: ", fromStringz(glxExts));
368 					const char * exts = glGetString( GL_EXTENSIONS );
369 					Log.d("Extensions: ", fromStringz(exts));
370 
371 
372 					Log.d("GLX_ARB_get_proc_address=", GLX_ARB_get_proc_address);
373 					Log.d("GLX_ARB_create_context=", GLX_ARB_create_context);
374 
375 					//da_glXCreateContextAttribsARB glXCreateContextAttribsARB;
376 					//Log.d("getting address of glXCreateContextAttribsARB");
377       				//glXCreateContextAttribsARB = cast(da_glXCreateContextAttribsARB)
378                		//		glXGetProcAddressARB( cast(const GLubyte *)("glXCreateContextAttribsARB".toStringz));
379 
380 					//Log.d("glXCreateContextAttribsARB = ", &glXCreateContextAttribsARB);
381 
382         			Log.d("Creating context");
383 			        //_context = glXCreateNewContext(_display, _fb_config, GLX_RGBA_TYPE, null, true);
384 					if (!GLX_ARB_create_context) {
385 						Log.e("glXCreateContextAttribsARB function is not found");
386 			            _context = glXCreateNewContext(_display, _fb_config, GLX_RGBA_TYPE, null, true);
387 					} else {
388 						int[] context_attribs =
389 					    [
390 					        GLX_CONTEXT_MAJOR_VERSION_ARB, 3,
391 					        GLX_CONTEXT_MINOR_VERSION_ARB, 0,
392 					        None
393 					    ];
394 						Log.e("calling glXCreateContextAttribsARB");
395 			        	_context = glXCreateContextAttribsARB(_display, _fb_config, null, true, context_attribs.ptr);
396 					}
397         			Log.d("Created context: ", _context);
398 
399 			        /* Create OpenGL context */
400 			        //auto context = glXCreateNewContext(_display, _fb_config, GLX_RGBA_TYPE, null, true);
401 			        if (!_context) {
402 						_enableOpengl = false;
403 						Log.e("Failed to create OpenGL context");
404 					} else {
405 
406 					}
407 
408 			        /* make OpenGL context current */
409 			        if(!glXMakeContextCurrent(_display, _drawable, _drawable, _context) || !initShaders()) {
410 						Log.e("Failed to make GL context current");
411 						_enableOpengl = false;
412 			            glXDestroyContext(_display, _context);
413 						_context = null;
414 					} else {
415 
416 					}
417 				}
418 			}
419         }
420 	}
421 
422 	protected string _caption;
423 
424 	override @property string windowCaption() {
425 		return _caption;
426 	}
427 
428 	override @property void windowCaption(string caption) {
429 		_caption = caption;
430 		const char * title = _caption.toStringz;
431 	    xcb_change_property (_xcbconnection,
432 	                            XCB_PROP_MODE_REPLACE,
433 	                            _w,
434 	                            XCB_ATOM_WM_NAME,
435 	                            XCB_ATOM_STRING,
436 	                            8,
437 	                            cast(uint)_caption.length,
438 	                            cast(void*)title);
439 	}
440 
441 	void redraw() {
442 
443 		if (_enableOpengl) {
444             version(USE_OPENGL) {
445 				import std.c.linux.X11.Xlib;
446 				Log.d("Drawing using opengl ", _dx, "x", _dy, " context=", _context);
447 				//glXMakeContextCurrent(_display, _drawable, _drawable, _context);
448 				glXMakeContextCurrent(_display, _glxwindow, _glxwindow, _context);
449 		        glEnable(GL_BLEND);
450 		        glDisable(GL_CULL_FACE);
451 		        glDisable(GL_DEPTH_TEST);
452 		        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
453 			    glViewport(0, 0, _dx, _dy);
454 				Log.d("glClearColor");
455 				glClearColor(0.2f, 0.4f, 0.9f, 1.0f);
456 				Log.d("glClear");
457             	glClear(GL_COLOR_BUFFER_BIT);
458 
459 				import dlangui.graphics.gldrawbuf;
460 				GLDrawBuf buf = new GLDrawBuf(_dx, _dy);
461 				buf.beforeDrawing();
462 				onDraw(buf);
463 				buf.fillRect(Rect(0, 0, 100, 100), 0x805010);
464 				buf.afterDrawing();
465 				destroy(buf);
466 				Log.d("Calling glx swap buffers for drawable ", _drawable);
467 				glXSwapBuffers(_display, _drawable);
468 				//glXMakeContextCurrent(_display, _drawable, _drawable, null);
469 				xcb_flush(_xcbconnection);
470 				glXMakeContextCurrent (_display, None, None, null);
471 				glXWaitGL();
472             }
473 		} else {
474 			if (!_drawbuf)
475 				_drawbuf = new ColorDrawBuf(_dx, _dy);
476 			_drawbuf.resize(_dx, _dy);
477 			_drawbuf.fill(_backgroundColor);
478 			//Log.d("calling createImage");
479 			createImage();
480 			//Log.d("done createImage");
481 			onDraw(_drawbuf);
482 			draw(_drawbuf);
483 				/*
484 			xcb_rectangle_t r = { 20, 20, 60, 60 };
485 			xcb_poly_fill_rectangle(_xcbconnection, _w, _g,  1, &r);
486 			r = xcb_rectangle_t(cast(short)(_dx - 20 - 60), cast(short)(_dy - 20 - 60), 60, 60);
487 			xcb_poly_fill_rectangle(_xcbconnection, _w, _g,  1, &r);
488 					*/
489 			xcb_flush(_xcbconnection);
490 		}
491 	}
492 
493 	ColorDrawBuf _drawbuf;
494 	bool _exposeSent;
495 	void processExpose(xcb_expose_event_t * event) {
496 		redraw();
497 		_exposeSent = false;
498 	}
499 
500 	/// request window redraw
501 	override void invalidate() {
502 		if (_exposeSent)
503 			return;
504 		_exposeSent = true;
505 		xcb_expose_event_t * event = cast(xcb_expose_event_t*)std.c.stdlib.malloc(xcb_expose_event_t.sizeof);
506 		event.response_type = XCB_EXPOSE; /* The type of the event, here it is XCB_EXPOSE */
507 		event.sequence = 0;
508 		event.window = _w;        /* The Id of the window that receives the event (in case */
509 		                                /* our application registered for events on several windows */
510 		event.x = 0;             /* The x coordinate of the top-left part of the window that needs to be redrawn */
511 		event.y = 0;             /* The y coordinate of the top-left part of the window that needs to be redrawn */
512 		event.width = cast(ushort)_dx;         /* The width of the part of the window that needs to be redrawn */
513 		event.height = cast(ushort)_dy;        /* The height of the part of the window that needs to be redrawn */
514 		event.count = 1;
515 
516 		xcb_void_cookie_t res = xcb_send_event(_xcbconnection, false, _w, XCB_EVENT_MASK_EXPOSURE, cast(char *)event);
517 		xcb_flush(_xcbconnection);
518 	}
519 
520 	protected ButtonDetails _lbutton;
521 	protected ButtonDetails _mbutton;
522 	protected ButtonDetails _rbutton;
523 	void processMouseEvent(MouseAction action, ubyte detail, ushort state, short x, short y) {
524 		MouseButton button = MouseButton.None;
525 		short wheelDelta = 0;
526         ButtonDetails * pbuttonDetails = null;
527 		ushort flags = 0;
528 		if (state & XCB_BUTTON_MASK_1)
529 			flags |= MouseFlag.LButton;
530 		if (state & XCB_BUTTON_MASK_2)
531 			flags |= MouseFlag.MButton;
532 		if (state & XCB_BUTTON_MASK_3)
533 			flags |= MouseFlag.RButton;
534 		if (state & XCB_MOD_MASK_SHIFT)
535 			flags |= MouseFlag.Shift;
536 		if (state & XCB_MOD_MASK_CONTROL)
537 			flags |= MouseFlag.Control;
538 		if (state & XCB_MOD_MASK_LOCK)
539 			flags |= MouseFlag.Alt;
540 		if (action == MouseAction.ButtonDown || action == MouseAction.ButtonUp) {
541 			switch (detail) {
542 				case 1:
543 					button = MouseButton.Left;
544 		            pbuttonDetails = &_lbutton;
545 					if (action == MouseAction.ButtonDown)
546 						flags |= MouseFlag.LButton;
547 					else if (action == MouseAction.ButtonUp)
548 						flags &= ~MouseFlag.LButton;
549 					break;
550 				case 2:
551 					button = MouseButton.Middle;
552 		            pbuttonDetails = &_mbutton;
553 					if (action == MouseAction.ButtonDown)
554 						flags |= MouseFlag.MButton;
555 					else if (action == MouseAction.ButtonUp)
556 						flags &= ~MouseFlag.MButton;
557 					break;
558 				case 3:
559 					button = MouseButton.Right;
560 		            pbuttonDetails = &_rbutton;
561 					if (action == MouseAction.ButtonDown)
562 						flags |= MouseFlag.RButton;
563 					else if (action == MouseAction.ButtonUp)
564 						flags &= ~MouseFlag.RButton;
565 					break;
566 				case 4:
567 					if (action == MouseAction.ButtonUp)
568 						return;
569 					wheelDelta = -1;
570 					action = MouseAction.Wheel;
571 					break;
572 				case 5:
573 					if (action == MouseAction.ButtonUp)
574 						return;
575 					wheelDelta = 1;
576 					action = MouseAction.Wheel;
577 					break;
578 				default:
579 					// unknown button
580 					return;
581 			}
582 		}
583 		//Log.d("processMouseEvent ", action, " detail=", detail, " state=", state, " at coords ", x, ", ", y);
584 	    if (action == MouseAction.ButtonDown) {
585 	        pbuttonDetails.down(x, y, cast(ushort)flags);
586 	    } else if (action == MouseAction.ButtonUp) {
587 	        pbuttonDetails.up(x, y, cast(ushort)flags);
588 	    }
589 	    MouseEvent event = new MouseEvent(action, button, cast(ushort)flags, x, y, wheelDelta);
590 	    event.lbutton = _lbutton;
591 	    event.rbutton = _rbutton;
592 	    event.mbutton = _mbutton;
593 		bool res = dispatchMouseEvent(event);
594 	    if (res) {
595 	        Log.d("Calling update() after mouse event");
596 	        invalidate();
597 	    }
598 	}
599 
600 	uint convertKeyCode(uint keyCode) {
601 		switch (keyCode) {
602 			case XK_BackSpace:
603 		    	return KeyCode.BACK;
604 			case XK_Tab:
605 				return KeyCode.TAB;
606 			case XK_Linefeed:
607 			case XK_Return:
608 				return KeyCode.RETURN;
609 			case XK_Shift_L:
610 				return KeyCode.LSHIFT;
611 			case XK_Shift_R:
612 				return KeyCode.RSHIFT;
613 			case XK_Control_L:
614 				return KeyCode.LCONTROL;
615 			case XK_Control_R:
616 				return KeyCode.RCONTROL;
617 			case XK_Alt_L:
618 				return KeyCode.LALT;
619 			case XK_Alt_R:
620 				return KeyCode.RALT;
621 			case XK_Pause:
622 				return KeyCode.PAUSE;
623 			case XK_Caps_Lock:
624 				return KeyCode.CAPS;
625 			case XK_Escape:
626 				return KeyCode.ESCAPE;
627 			case XK_KP_Space:
628 				return KeyCode.SPACE;
629 			case XK_KP_Page_Up:
630 			case XK_Page_Up:
631 				return KeyCode.PAGEUP;
632 			case XK_KP_Page_Down:
633 			case XK_Page_Down:
634 				return KeyCode.PAGEDOWN;
635 			case XK_End:
636 			case XK_KP_End:
637 				return KeyCode.END;
638 			case XK_Home:
639 			case XK_KP_Home:
640 				return KeyCode.HOME;
641 			case XK_KP_Left:
642 			case XK_Left:
643 				return KeyCode.LEFT;
644 			case XK_KP_Up:
645 			case XK_Up:
646 				return KeyCode.UP;
647 			case XK_KP_Right:
648 			case XK_Right:
649 				return KeyCode.RIGHT;
650 			case XK_KP_Down:
651 			case XK_Down:
652 				return KeyCode.DOWN;
653 			case XK_Insert:
654 			case XK_KP_Insert:
655 				return KeyCode.INS;
656 			case XK_KP_Delete:
657 			case XK_Delete:
658 				return KeyCode.DEL;
659 			case XK_0:
660 				return KeyCode.KEY_0;
661 			case XK_1:
662 				return KeyCode.KEY_1;
663 			case XK_2:
664 				return KeyCode.KEY_2;
665 			case XK_3:
666 				return KeyCode.KEY_3;
667 			case XK_4:
668 				return KeyCode.KEY_4;
669 			case XK_5:
670 				return KeyCode.KEY_5;
671 			case XK_6:
672 				return KeyCode.KEY_6;
673 			case XK_7:
674 				return KeyCode.KEY_7;
675 			case XK_8:
676 				return KeyCode.KEY_8;
677 			case XK_9:
678 				return KeyCode.KEY_9;
679 			case XK_A:
680 				return KeyCode.KEY_A;
681 			case XK_B:
682 				return KeyCode.KEY_B;
683 			case XK_C:
684 				return KeyCode.KEY_C;
685 			case XK_D:
686 				return KeyCode.KEY_D;
687 			case XK_E:
688 				return KeyCode.KEY_E;
689 			case XK_F:
690 				return KeyCode.KEY_F;
691 			case XK_G:
692 				return KeyCode.KEY_G;
693 			case XK_H:
694 				return KeyCode.KEY_H;
695 			case XK_I:
696 				return KeyCode.KEY_I;
697 			case XK_J:
698 				return KeyCode.KEY_J;
699 			case XK_K:
700 				return KeyCode.KEY_K;
701 			case XK_L:
702 				return KeyCode.KEY_L;
703 			case XK_M:
704 				return KeyCode.KEY_M;
705 			case XK_N:
706 				return KeyCode.KEY_N;
707 			case XK_O:
708 				return KeyCode.KEY_O;
709 			case XK_P:
710 				return KeyCode.KEY_P;
711 			case XK_Q:
712 				return KeyCode.KEY_Q;
713 			case XK_R:
714 				return KeyCode.KEY_R;
715 			case XK_S:
716 				return KeyCode.KEY_S;
717 			case XK_T:
718 				return KeyCode.KEY_T;
719 			case XK_U:
720 				return KeyCode.KEY_U;
721 			case XK_V:
722 				return KeyCode.KEY_V;
723 			case XK_W:
724 				return KeyCode.KEY_W;
725 			case XK_X:
726 				return KeyCode.KEY_X;
727 			case XK_Y:
728 				return KeyCode.KEY_Y;
729 			case XK_Z:
730 				return KeyCode.KEY_Z;
731 				//return KeyCode.LWIN = 0x5b,
732 				//return KeyCode.RWIN = 0x5c,
733 			case XK_KP_0:
734 				return KeyCode.NUM_0;
735 			case XK_KP_1:
736 				return KeyCode.NUM_1;
737 			case XK_KP_2:
738 				return KeyCode.NUM_2;
739 			case XK_KP_3:
740 				return KeyCode.NUM_3;
741 			case XK_KP_4:
742 				return KeyCode.NUM_4;
743 			case XK_KP_5:
744 				return KeyCode.NUM_5;
745 			case XK_KP_6:
746 				return KeyCode.NUM_6;
747 			case XK_KP_7:
748 				return KeyCode.NUM_7;
749 			case XK_KP_8:
750 				return KeyCode.NUM_8;
751 			case XK_KP_9:
752 				return KeyCode.NUM_9;
753 			case XK_asterisk:
754 				return KeyCode.MUL;
755 			case XK_plus:
756 				return KeyCode.ADD;
757 			case XK_slash:
758 				return KeyCode.DIV;
759 			case XK_minus:
760 				return KeyCode.SUB;
761 			case XK_period:
762 				return KeyCode.DECIMAL;
763 			case XK_F1:
764 				return KeyCode.F1;
765 			case XK_F2:
766 				return KeyCode.F2;
767 			case XK_F3:
768 				return KeyCode.F3;
769 			case XK_F4:
770 				return KeyCode.F4;
771 			case XK_F5:
772 				return KeyCode.F5;
773 			case XK_F6:
774 				return KeyCode.F6;
775 			case XK_F7:
776 				return KeyCode.F7;
777 			case XK_F8:
778 				return KeyCode.F8;
779 			case XK_F9:
780 				return KeyCode.F9;
781 			case XK_F10:
782 				return KeyCode.F10;
783 			case XK_F11:
784 				return KeyCode.F11;
785 			case XK_F12:
786 				return KeyCode.F12;
787 			case XK_Num_Lock:
788 				return KeyCode.NUMLOCK;
789 			case XK_Scroll_Lock:
790 				return KeyCode.SCROLL;
791 			default:
792 				return 0x10000 | keyCode;
793 		}
794 	}
795 
796 	uint convertKeyFlags(uint flags) {
797 		return 0;
798 	}
799 
800 	bool processKeyEvent(KeyAction action, uint keyCode, uint flags) {
801 		Log.d("processKeyEvent ", action, " x11 key=", keyCode, " x11 flags=", flags);
802 		keyCode = convertKeyCode(keyCode);
803 		flags = convertKeyFlags(flags);
804 		Log.d("processKeyEvent ", action, " converted key=", keyCode, " converted flags=", flags);
805 		bool res = dispatchKeyEvent(new KeyEvent(action, keyCode, flags));
806 		if (keyCode & 0x10000 && (keyCode & 0xF000) != 0xF000) {
807 			dchar[1] text;
808 			text[0] = keyCode & 0xFFFF;
809 			res = dispatchKeyEvent(new KeyEvent(KeyAction.Text, keyCode, flags, cast(dstring)text)) || res;
810 		}
811 	    if (res) {
812 	        Log.d("Calling update() after key event");
813 	        invalidate();
814 	    }
815 		return res;
816 	}
817 }
818 
819 private __gshared xcb_connection_t * _xcbconnection;
820 private __gshared xcb_screen_t     * _xcbscreen;
821 private __gshared ubyte _xcbscreendepth;
822 private __gshared bool _enableOpengl;
823 private __gshared std.c.linux.X11.Xlib.Display * _display;
824 
825 class XCBPlatform : Platform {
826 	this() {
827 
828 	}
829 	~this() {
830 		foreach(ref XCBWindow wnd; _windowMap) {
831 			destroy(wnd);
832 			wnd = null;
833 		}
834 		_windowMap.clear();
835 		disconnect();
836 	}
837 	void disconnect() {
838 		/* Cleanup */
839 		if (_display) {
840 			Log.d("Closing X display");
841         	std.c.linux.X11.Xlib.XCloseDisplay(_display);
842 			_display = null;
843 		} else if (_xcbconnection) {
844 			Log.d("Closing XCB connection");
845 			/* close connection to server */
846 	  		xcb_disconnect(_xcbconnection);
847 			_xcbconnection = null;
848 		}
849 	}
850 	bool connect() {
851 
852 		try {
853 			DerelictGL3.load();
854 			_enableOpengl = true;
855 		} catch (Exception e) {
856 			Log.e("Cannot load opengl library", e);
857 		}
858 		//_enableOpengl = false; // test
859 		// X
860 		import std.c.linux.X11.Xlib;
861         int default_screen;
862 
863 		if (_enableOpengl) {
864 			Log.d("Opening display via XLib");
865 	        /* Open Xlib Display */
866 	        _display = XOpenDisplay(null);
867 			if (!_display)
868 	        {
869 		        Log.e("Failed to open display using Xlib");
870 				_enableOpengl = false;
871 			} else {
872 				// display is opened
873 				default_screen = DefaultScreen(_display);
874 				Log.d("Opened display =");
875 				/* Get the XCB connection from the display */
876 			    _xcbconnection = XGetXCBConnection(_display);
877 			    if (!_xcbconnection)
878 			    {
879 			        XCloseDisplay(_display);
880 					_display = null;
881 			        Log.e("Failed to get XCB connection from Xlib display");
882 					_enableOpengl = false;
883 				} else {
884 					/* Acquire event queue ownership */
885 				    XSetEventQueueOwner(_display, XEventQueueOwner.XCBOwnsEventQueue);
886 				}
887 			}
888 		}
889 
890 		if (_xcbconnection is null) {
891 			Log.d("Opening XCB connection");
892 			/* open connection with the server */
893 			_xcbconnection = xcb_connect(null,null);
894 		}
895 		if (xcb_connection_has_error(_xcbconnection)) {
896 		    Log.e("Cannot open display");
897 			_xcbconnection = null;
898 		    return false;
899 		}
900 		//XSetEventQueueOwner(display, XCBOwnsEventQueue);
901 		Log.d("Getting first screen");
902 		/* get the first screen */
903 
904 		if (_enableOpengl) {
905 		    /* Find XCB screen */
906 		    //xcb_screen_t *screen = null;
907 		    xcb_screen_iterator_t screen_iter =
908 		        xcb_setup_roots_iterator(xcb_get_setup(_xcbconnection));
909 		    for(int screen_num = default_screen;
910 			        screen_iter.rem && screen_num > 0;
911 					--screen_num, xcb_screen_next(&screen_iter)) {
912 			}
913 		    _xcbscreen = screen_iter.data;
914 		} else {
915 			_xcbscreen = xcb_setup_roots_iterator( xcb_get_setup(_xcbconnection) ).data;
916 		}
917 		_xcbscreendepth = xcb_aux_get_depth(_xcbconnection, _xcbscreen);
918 
919 		if (_enableOpengl) {
920 
921 			int versionMajor;
922 			int versionMinor;
923 			if (!glXQueryVersion(_display,
924                     &versionMajor,
925 				&versionMinor)) {
926 				Log.e("Cannot get GLX version");
927 			} else {
928 				Log.e("GLX version: ", versionMajor, ".", versionMinor);
929 			}
930 
931 		}
932 
933 
934 		return true;
935 	}
936 	XCBWindow getWindow(xcb_window_t w) {
937 		if (w in _windowMap)
938 			return _windowMap[w];
939 		return null;
940 	}
941 	override Window createWindow(string windowCaption, Window parent) {
942 		XCBWindow res = new XCBWindow(windowCaption, parent);
943 		_windowMap[res.windowId] = res;
944 		return res;
945 	}
946 	override int enterMessageLoop() {
947 		Log.i("entering message loop");
948 		int done = 0;
949 		xcb_generic_event_t *e;
950 		xcb_key_symbols_t * keysyms;// = xcb_key_symbols_alloc(_xcbconnection);
951 		keysyms = xcb_key_symbols_alloc(_xcbconnection);
952 		if (!keysyms)
953 			Log.e("xcb_key_symbols_alloc returned null");
954 		/* event loop */
955 		do {
956 		  	e = xcb_wait_for_event(_xcbconnection);
957 			if (e is null) {
958 				Log.w("NULL event received. Exiting message loop");
959 				break;
960 			}
961 		    switch (e.response_type & ~0x80) {
962 				case XCB_CREATE_NOTIFY: {
963 						xcb_create_notify_event_t *event = cast(xcb_create_notify_event_t *)e;
964 						Log.i("XCB_CREATE_NOTIFY");
965 						XCBWindow window = getWindow(event.window);
966 						if (window !is null) {
967 							//
968 						} else {
969 							Log.w("Received message for unknown window", event.window);
970 						}
971 						break;
972 					}
973 				case XCB_DESTROY_NOTIFY: {
974 						xcb_destroy_notify_event_t *event = cast(xcb_destroy_notify_event_t *)e;
975 						Log.i("XCB_DESTROY_NOTIFY");
976 						XCBWindow window = getWindow(event.window);
977 						if (window !is null) {
978 							//
979 						} else {
980 							Log.w("Received message for unknown window", event.window);
981 						}
982 						break;
983 					}
984 				case XCB_MAP_NOTIFY: {
985 						xcb_map_notify_event_t *event = cast(xcb_map_notify_event_t *)e;
986 						Log.i("XCB_MAP_NOTIFY");
987 						XCBWindow window = getWindow(event.window);
988 						if (window !is null) {
989 							//
990 						} else {
991 							Log.w("Received message for unknown window", event.window);
992 						}
993 						break;
994 					}
995 				case XCB_UNMAP_NOTIFY: {
996 						xcb_unmap_notify_event_t *event = cast(xcb_unmap_notify_event_t *)e;
997 						Log.i("XCB_UNMAP_NOTIFY");
998 						XCBWindow window = getWindow(event.window);
999 						if (window !is null) {
1000 							//
1001 						} else {
1002 							Log.w("Received message for unknown window", event.window);
1003 						}
1004 						break;
1005 					}
1006 				case XCB_VISIBILITY_NOTIFY: {
1007 						xcb_visibility_notify_event_t *event = cast(xcb_visibility_notify_event_t *)e;
1008 						Log.i("XCB_VISIBILITY_NOTIFY ", event.state);
1009 						XCBWindow window = getWindow(event.window);
1010 						if (window !is null) {
1011 							//
1012 						} else {
1013 							Log.w("Received message for unknown window", event.window);
1014 						}
1015 						break;
1016 					}
1017 				case XCB_REPARENT_NOTIFY: {
1018 						xcb_reparent_notify_event_t *event = cast(xcb_reparent_notify_event_t *)e;
1019 						Log.i("XCB_REPARENT_NOTIFY");
1020 						break;
1021 					}
1022 				case XCB_CONFIGURE_NOTIFY: {
1023 						xcb_configure_notify_event_t *event = cast(xcb_configure_notify_event_t *)e;
1024 						Log.i("XCB_CONFIGURE_NOTIFY ", event.width, "x", event.height);
1025 						XCBWindow window = getWindow(event.window);
1026 						if (window !is null) {
1027 							//
1028 							window.onResize(event.width, event.height);
1029 						} else {
1030 							Log.w("Received message for unknown window", event.window);
1031 						}
1032 						break;
1033 					}
1034 				case XCB_EXPOSE: {   /* draw or redraw the window */
1035 						xcb_expose_event_t *expose = cast(xcb_expose_event_t *)e;
1036 						Log.i("XCB_EXPOSE");
1037 						XCBWindow window = getWindow(expose.window);
1038 						if (window !is null) {
1039 							window.processExpose(expose);
1040 						} else {
1041 							Log.w("Received message for unknown window", expose.window);
1042 						}
1043 				      	break;
1044 					}
1045 				case XCB_BUTTON_PRESS: {
1046 						xcb_button_press_event_t *bp = cast(xcb_button_press_event_t *)e;
1047 						Log.d("XCB_BUTTON_PRESS");
1048 						XCBWindow window = getWindow(bp.event);
1049 						if (window !is null) {
1050 							//
1051 							window.processMouseEvent(MouseAction.ButtonDown, bp.detail, bp.state, bp.event_x, bp.event_y);
1052 						} else {
1053 							Log.w("Received message for unknown window", bp.event);
1054 						}
1055 						break;
1056 					}
1057 				case XCB_BUTTON_RELEASE: {
1058 						Log.d("XCB_BUTTON_RELEASE");
1059 						xcb_button_release_event_t *br = cast(xcb_button_release_event_t *)e;
1060 						XCBWindow window = getWindow(br.event);
1061 						if (window !is null) {
1062 							//
1063 							window.processMouseEvent(MouseAction.ButtonUp, br.detail, br.state, br.event_x, br.event_y);
1064 						} else {
1065 							Log.w("Received message for unknown window", br.event);
1066 						}
1067 						break;
1068 					}
1069 		        case XCB_MOTION_NOTIFY: {
1070 			            xcb_motion_notify_event_t *motion = cast(xcb_motion_notify_event_t *)e;
1071 			            //Log.d("XCB_MOTION_NOTIFY ", motion.event, " at coords ", motion.event_x, ", ", motion.event_y);
1072 						XCBWindow window = getWindow(motion.event);
1073 						if (window !is null) {
1074 							//
1075 							window.processMouseEvent(MouseAction.Move, 0, motion.state, motion.event_x, motion.event_y);
1076 						} else {
1077 							Log.w("Received message for unknown window", motion.event);
1078 						}
1079 			            break;
1080 			        }
1081 		        case XCB_ENTER_NOTIFY: {
1082 			            xcb_enter_notify_event_t *enter = cast(xcb_enter_notify_event_t *)e;
1083 			            Log.d("XCB_ENTER_NOTIFY ", enter.event, " at coords ", enter.event_x, ", ", enter.event_y);
1084 			            break;
1085 			        }
1086 		        case XCB_LEAVE_NOTIFY: {
1087 		            xcb_leave_notify_event_t *leave = cast(xcb_leave_notify_event_t *)e;
1088 		            Log.d("XCB_LEAVE_NOTIFY ", leave.event, " at coords ", leave.event_x, ", ", leave.event_y);
1089 					XCBWindow window = getWindow(leave.event);
1090 					if (window !is null) {
1091 						//
1092 						window.processMouseEvent(MouseAction.Leave, 0, leave.state, leave.event_x, leave.event_y);
1093 					} else {
1094 						Log.w("Received message for unknown window", leave.event);
1095 					}
1096 		            break;
1097 		        }
1098 		        case XCB_KEY_PRESS: {
1099 			            xcb_key_press_event_t *kp = cast(xcb_key_press_event_t *)e;
1100 			            //print_modifiers(kp.state);
1101 			            Log.d("XCB_KEY_PRESS ", kp.event, " key=", kp.detail);
1102 						XCBWindow window = getWindow(kp.event);
1103 						if (window !is null) {
1104 							//xcb_keysym_t keysym = xcb_key_press_lookup_keysym(keysyms, kp, xcb_lookup_t.xcb_lookup_key_sym_t);
1105 							xcb_keysym_t keysym = xcb_key_press_lookup_keysym(keysyms, kp, 2);
1106 							Log.d("xcb_key_press_lookup_keysym - key=", kp.detail, " keysym=", keysym);
1107 							//xcb_keysym_t keysym = xcb_key_symbols_get_keysym(keysyms, kp.detail, xcb_lookup_t.xcb_lookup_key_sym_t);
1108 							window.processKeyEvent(KeyAction.KeyDown, keysym, kp.state);
1109 						}
1110 						//if (kp.detail == XK_space) // exist by space
1111 		      			//	done = 1;
1112 			            break;
1113 			        }
1114 		        case XCB_KEY_RELEASE: {
1115 			            xcb_key_release_event_t *kr = cast(xcb_key_release_event_t *)e;
1116 			            //print_modifiers(kr.state);
1117 				        Log.d("XCB_KEY_RELEASE ", kr.event, " key=", kr.detail);
1118 						XCBWindow window = getWindow(kr.event);
1119 						if (window !is null) {
1120 							//xcb_keysym_t keysym = xcb_key_symbols_get_keysym(keysyms, kr.detail, xcb_lookup_t.xcb_lookup_key_sym_t);
1121 							xcb_keysym_t keysym = xcb_key_release_lookup_keysym(keysyms, kr, 2);
1122 							window.processKeyEvent(KeyAction.KeyUp, keysym, kr.state);
1123 						}
1124 			            break;
1125 			        }
1126 				default:
1127 					Log.v("unknown event: ", e.response_type & ~0x80);
1128 					break;
1129 		    }
1130 		    free(e);
1131 		} while(!done);
1132 		if (keysyms)
1133 			xcb_key_symbols_free(keysyms);
1134 		Log.i("exiting message loop");
1135 		return 0;
1136 	}
1137 
1138     /// retrieves text from clipboard (when mouseBuffer == true, use mouse selection clipboard - under linux)
1139 	override dstring getClipboardText(bool mouseBuffer = false) {
1140 		return ""d;
1141 	}
1142 
1143     /// sets text to clipboard (when mouseBuffer == true, use mouse selection clipboard - under linux)
1144 	override void setClipboardText(dstring text, bool mouseBuffer = false) {
1145 	}
1146 
1147 	protected XCBWindow[xcb_window_t] _windowMap;
1148 }
1149 
1150 // entry point
1151 extern(C) int UIAppMain(string[] args);
1152 
1153 int main(string[] args)
1154 {
1155 
1156 	setStderrLogger();
1157 	Log.setLogLevel(LogLevel.Trace);
1158 
1159 	FreeTypeFontManager ft = new FreeTypeFontManager();
1160 	ft.registerFont("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", FontFamily.SansSerif, "DejaVu", false, FontWeight.Normal);
1161 	FontManager.instance = ft;
1162 
1163 	currentTheme = createDefaultTheme();
1164 
1165 	XCBPlatform xcb = new XCBPlatform();
1166 	if (!xcb.connect()) {
1167 		return 1;
1168 	}
1169 	Platform.setInstance(xcb);
1170 
1171 	int res = 0;
1172 
1173 		static if (true) {
1174 			res = UIAppMain(args);
1175 		} else {
1176 			Window window = xcb.createWindow("Window Caption", null);
1177 			window.show();
1178 
1179 			res = xcb.enterMessageLoop();
1180 		}
1181 
1182 	Platform.setInstance(null);
1183 	Log.d("Destroying XCB platform");
1184 	destroy(xcb);
1185 
1186 	currentTheme = null;
1187 	drawableCache = null;
1188 	imageCache = null;
1189 	FontManager.instance = null;
1190 
1191 	Log.d("Exiting main");
1192 
1193 	return res;
1194 }