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 ¤tSelectionType, ¤tSelectionFormat, &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 }