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 | PropertyChangeMask; 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 _windowState = WindowState.hidden; 316 317 _children.reserve(20); 318 _parent = cast(X11Window) parent; 319 if (_parent) 320 _parent._children ~= this; 321 322 if (!(flags & WindowFlag.Resizable)) { 323 XSizeHints sizeHints; 324 sizeHints.min_width = width; 325 sizeHints.min_height = height; 326 sizeHints.max_width = width; 327 sizeHints.max_height = height; 328 sizeHints.flags = PMaxSize | PMinSize; 329 XSetWMNormalHints(x11display, _win, &sizeHints); 330 } 331 if (flags & WindowFlag.Fullscreen) { 332 if (atom_NET_WM_STATE_FULLSCREEN != None) { 333 changeWindowState(_NET_WM_STATE_ADD, atom_NET_WM_STATE_FULLSCREEN); 334 } 335 else 336 Log.w("Missing _NET_WM_STATE_FULLSCREEN atom"); 337 } 338 if (flags & WindowFlag.Modal) { 339 if (_parent) { 340 XSetTransientForHint(x11display, _win, _parent._win); 341 } else { 342 Log.w("Top-level modal window"); 343 } 344 if (atom_NET_WM_STATE_MODAL != None) { 345 changeWindowState(_NET_WM_STATE_ADD, atom_NET_WM_STATE_MODAL); 346 } else { 347 Log.w("Missing _NET_WM_STATE_MODAL atom"); 348 } 349 } 350 /* this routine determines which types of input are allowed in 351 the input. see the appropriate section for details... 352 */ 353 // XSelectInput(x11display, _win, KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | 354 // EnterWindowMask | LeaveWindowMask | PointerMotionMask | ButtonMotionMask | ExposureMask | VisibilityChangeMask | 355 // FocusChangeMask | KeymapStateMask | StructureNotifyMask); 356 357 /* create the Graphics Context */ 358 _gc = createGC(x11display, _win); 359 //_gc = XCreateGC(x11display, _win, 0, cast(XGCValues*)null); 360 //Log.d("X11Window: windowId=", _win, " gc=", _gc); 361 362 363 364 365 /* here is another routine to set the foreground and background 366 colors _currently_ in use in the window. 367 */ 368 //XSetBackground(x11display, _gc, white); 369 //XSetForeground(x11display, _gc, black); 370 371 /* clear the window and bring it on top of the other windows */ 372 //XClearWindow(x11display, _win); 373 //XFlush(x11display); 374 375 handleWindowStateChange(WindowState.unspecified, Rect(0, 0, _dx, _dy)); 376 377 if (platform.defaultWindowIcon.length != 0) 378 windowIcon = drawableCache.getImage(platform.defaultWindowIcon); 379 } 380 381 ~this() { 382 debug Log.d("Destroying X11 window"); 383 if (timer) { 384 timer.stop(); 385 } 386 if (_parent) { 387 import std.algorithm : countUntil, remove; 388 ptrdiff_t index = countUntil(_parent._children,this); 389 if (index > -1 ) { 390 _parent._children = _parent._children.remove(index); 391 } 392 _parent = null; 393 } 394 static if (ENABLE_OPENGL) { 395 if (_glc) { 396 glXDestroyContext(x11display, _glc); 397 _glc = null; 398 } 399 } 400 if (_drawbuf) 401 destroy(_drawbuf); 402 if (_gc) { 403 XFreeGC(x11display, _gc); 404 _gc = null; 405 } 406 if (_win) { 407 XDestroyWindow(x11display, _win); 408 _win = 0; 409 } 410 } 411 412 /// show window 413 override void show() { 414 Log.d("X11Window.show"); 415 XMapRaised(x11display, _win); 416 XFlush(x11display); 417 static if (ENABLE_OPENGL) { 418 if (_enableOpengl) { 419 _glc = glXCreateContext(x11display, x11visual, null, GL_TRUE); 420 if (!_glc) { 421 _enableOpengl = false; 422 } else { 423 glXMakeCurrent(x11display, cast(uint)_win, _glc); 424 _enableOpengl = initGLSupport(_platform.GLVersionMajor < 3); 425 if (!_enableOpengl && _glc) { 426 glXDestroyContext(x11display, _glc); 427 _glc = null; 428 } 429 } 430 } 431 if (_enableOpengl) { 432 Log.d("Open GL support is enabled"); 433 } else { 434 Log.d("Open GL support is disabled"); 435 } 436 } 437 if (_mainWidget) { 438 _mainWidget.measure(SIZE_UNSPECIFIED, SIZE_UNSPECIFIED); 439 if (flags & WindowFlag.MeasureSize) 440 resizeWindow(Point(_mainWidget.measuredWidth, _mainWidget.measuredHeight)); 441 else 442 adjustWindowOrContentSize(_mainWidget.measuredWidth, _mainWidget.measuredHeight); 443 444 adjustPositionDuringShow(); 445 446 _mainWidget.setFocus(); 447 } 448 } 449 450 override protected void handleWindowStateChange(WindowState newState, Rect newWindowRect = RECT_VALUE_IS_NOT_SET) { 451 super.handleWindowStateChange(newState, newWindowRect); 452 } 453 454 protected final void changeWindowState(int action, Atom firstProperty, Atom secondProperty = None) nothrow 455 { 456 XEvent ev; 457 memset(&ev, 0, ev.sizeof); 458 ev.xany.type = ClientMessage; 459 ev.xclient.window = _win; 460 ev.xclient.message_type = atom_NET_WM_STATE; 461 ev.xclient.format = 32; 462 ev.xclient.data.l[0] = action; 463 ev.xclient.data.l[1] = firstProperty; 464 if (secondProperty != None) 465 ev.xclient.data.l[2] = secondProperty; 466 ev.xclient.data.l[3] = 0; 467 XSendEvent(x11display, RootWindow(x11display, x11screen), false, SubstructureNotifyMask|SubstructureRedirectMask, &ev); 468 } 469 470 protected enum { 471 _NET_WM_STATE_REMOVE = 0, 472 _NET_WM_STATE_ADD, 473 _NET_WM_STATE_TOGGLE 474 } 475 476 override bool setWindowState(WindowState newState, bool activate = false, Rect newWindowRect = RECT_VALUE_IS_NOT_SET) { 477 if (_win == None) { 478 return false; 479 } 480 bool result = false; 481 switch(newState) { 482 case WindowState.maximized: 483 if (atom_NET_WM_STATE != None && atom_NET_WM_STATE_MAXIMIZED_HORZ != None && atom_NET_WM_STATE_MAXIMIZED_VERT != None) { 484 changeWindowState(_NET_WM_STATE_ADD, atom_NET_WM_STATE_MAXIMIZED_HORZ, atom_NET_WM_STATE_MAXIMIZED_VERT); 485 result = true; 486 } 487 break; 488 case WindowState.minimized: 489 if (atom_NET_WM_STATE != None && atom_NET_WM_STATE_HIDDEN != None) { 490 changeWindowState(_NET_WM_STATE_ADD, atom_NET_WM_STATE_HIDDEN); 491 result = true; 492 } 493 break; 494 case WindowState.hidden: 495 XUnmapWindow(x11display, _win); 496 result = true; 497 break; 498 case WindowState.normal: 499 if (atom_NET_WM_STATE != None && 500 atom_NET_WM_STATE_MAXIMIZED_HORZ != None && 501 atom_NET_WM_STATE_MAXIMIZED_VERT != None && 502 atom_NET_WM_STATE_HIDDEN != None) 503 { 504 changeWindowState(_NET_WM_STATE_REMOVE, atom_NET_WM_STATE_MAXIMIZED_HORZ, atom_NET_WM_STATE_MAXIMIZED_VERT); 505 changeWindowState(_NET_WM_STATE_REMOVE, atom_NET_WM_STATE_HIDDEN); 506 changeWindowState(_NET_WM_STATE_REMOVE, atom_NET_WM_STATE_FULLSCREEN); 507 result = true; 508 } 509 break; 510 case WindowState.fullscreen: 511 if (atom_NET_WM_STATE != None && atom_NET_WM_STATE_FULLSCREEN != None) { 512 changeWindowState(_NET_WM_STATE_ADD, atom_NET_WM_STATE_FULLSCREEN); 513 result = true; 514 } 515 break; 516 default: 517 break; 518 } 519 520 // change size and/or position 521 bool rectChanged = false; 522 if (newWindowRect != RECT_VALUE_IS_NOT_SET && (newState == WindowState.normal || newState == WindowState.unspecified)) { 523 // change position 524 if (newWindowRect.top != int.min && newWindowRect.left != int.min) { 525 XMoveWindow(x11display, _win, newWindowRect.left, newWindowRect.top); 526 rectChanged = true; 527 result = true; 528 } 529 530 // change size 531 if (newWindowRect.bottom != int.min && newWindowRect.right != int.min) { 532 if (!(flags & WindowFlag.Resizable)) { 533 XSizeHints sizeHints; 534 sizeHints.min_width = newWindowRect.right; 535 sizeHints.min_height = newWindowRect.bottom; 536 sizeHints.max_width = newWindowRect.right; 537 sizeHints.max_height = newWindowRect.bottom; 538 sizeHints.flags = PMaxSize | PMinSize; 539 XSetWMNormalHints(x11display, _win, &sizeHints); 540 } 541 XResizeWindow(x11display, _win, newWindowRect.right, newWindowRect.bottom); 542 rectChanged = true; 543 result = true; 544 } 545 } 546 547 if (activate) { 548 XMapRaised(x11display, _win); 549 result = true; 550 } 551 XFlush(x11display); 552 553 //needed here to make _windowRect and _windowState valid 554 //example: change size by resizeWindow() and make some calculations using windowRect 555 if (rectChanged) { 556 handleWindowStateChange(newState, Rect(newWindowRect.left == int.min ? _windowRect.left : newWindowRect.left, 557 newWindowRect.top == int.min ? _windowRect.top : newWindowRect.top, newWindowRect.right == int.min ? _windowRect.right : newWindowRect.right, 558 newWindowRect.bottom == int.min ? _windowRect.bottom : newWindowRect.bottom)); 559 } 560 else 561 handleWindowStateChange(newState, RECT_VALUE_IS_NOT_SET); 562 563 564 return result; 565 } 566 567 override @property DWindow parentWindow() { 568 return _parent; 569 } 570 571 private bool _isActive; 572 override protected void handleWindowActivityChange(bool isWindowActive) { 573 _isActive = isWindowActive; 574 super.handleWindowActivityChange(isWindowActive); 575 } 576 577 override @property bool isActive() { 578 return _isActive; 579 } 580 581 override @property dstring windowCaption() { 582 return _caption; 583 } 584 585 override @property void windowCaption(dstring caption) { 586 _caption = caption; 587 auto captionc = _caption.toUTF8; 588 auto captionz = cast(ubyte*)captionc.toStringz; 589 XTextProperty nameProperty; 590 nameProperty.value = captionz; 591 nameProperty.encoding = atom_UTF8_STRING; 592 nameProperty.format = 8; 593 nameProperty.nitems = cast(uint)captionc.length; 594 XStoreName(x11display, _win, cast(char*)captionz); // this may not support unicode 595 XSetWMName(x11display, _win, &nameProperty); 596 XChangeProperty(x11display, _win, atom_NET_WM_NAME, atom_UTF8_STRING, 8, PropModeReplace, captionz, cast(int)captionc.length); 597 //XFlush(x11display); //TODO: not sure if XFlush is required 598 } 599 600 /// sets window icon 601 override @property void windowIcon(DrawBufRef buf) { 602 ColorDrawBuf icon = cast(ColorDrawBuf)buf.get; 603 if (!icon) { 604 Log.e("Trying to set null icon for window"); 605 return; 606 } 607 immutable int iconw = 32; 608 immutable int iconh = 32; 609 ColorDrawBuf iconDraw = new ColorDrawBuf(iconw, iconh); 610 scope(exit) destroy(iconDraw); 611 iconDraw.fill(0xFF000000); 612 iconDraw.drawRescaled(Rect(0, 0, iconw, iconh), icon, Rect(0, 0, icon.width, icon.height)); 613 iconDraw.invertAndPreMultiplyAlpha(); 614 c_long[] propData = new c_long[2 + iconw * iconh]; 615 propData[0] = iconw; 616 propData[1] = iconh; 617 auto iconData = iconDraw.scanLine(0); 618 foreach(i; 0..iconw*iconh) { 619 propData[i+2] = iconData[i]; 620 } 621 XChangeProperty(x11display, _win, atom_NET_WM_ICON, XA_CARDINAL, 32, PropModeReplace, cast(ubyte*)propData.ptr, cast(int)propData.length); 622 } 623 624 /// request window redraw 625 override void invalidate() { 626 if (!_needRedraw) { 627 debug(x11) Log.d("Window.invalidate()"); 628 _needRedraw = true; 629 } 630 } 631 632 /// close window 633 override void close() { 634 Log.d("X11Window.close()"); 635 _platform.closeWindow(this); 636 } 637 638 ColorDrawBuf _drawbuf; 639 protected void drawUsingBitmap() { 640 if (_dx > 0 && _dy > 0) { 641 //Log.d("drawUsingBitmap()"); 642 // prepare drawbuf 643 if (_drawbuf is null) 644 _drawbuf = new ColorDrawBuf(_dx, _dy); 645 else 646 _drawbuf.resize(_dx, _dy); 647 _drawbuf.resetClipping(); 648 // draw widgets into buffer 649 _drawbuf.fill(backgroundColor); 650 onDraw(_drawbuf); 651 // draw buffer on X11 window 652 XImage img; 653 img.width = _drawbuf.width; 654 img.height = _drawbuf.height; 655 img.xoffset = 0; 656 img.format = ZPixmap; 657 img.data = cast(char*)_drawbuf.scanLine(0); 658 img.bitmap_unit = 32; 659 img.bitmap_pad = 32; 660 img.bitmap_bit_order = LSBFirst; 661 img.depth = 24; 662 img.chars_per_line = _drawbuf.width * 4; 663 img.bits_per_pixel = 32; 664 img.red_mask = 0xFF0000; 665 img.green_mask = 0x00FF00; 666 img.blue_mask = 0x0000FF; 667 XInitImage(&img); 668 //XSetClipOrigin(x11display, _gc, 0, 0); 669 XPutImage(x11display, _win, 670 _gc, //DefaultGC(x11display, DefaultScreen(x11display)), 671 &img, 672 0, 0, 0, 0, 673 _drawbuf.width, 674 _drawbuf.height); 675 //XFlush(x11display); // no need to XFlush since it will be called in event loop 676 } 677 } 678 679 protected void drawUsingOpengl() { 680 static if (ENABLE_OPENGL) { 681 //Log.d("drawUsingOpengl()"); 682 glXMakeCurrent(x11display, cast(uint)_win, _glc); 683 glDisable(GL_DEPTH_TEST); 684 glViewport(0, 0, _dx, _dy); 685 float a = 1.0f; 686 float r = ((_backgroundColor >> 16) & 255) / 255.0f; 687 float g = ((_backgroundColor >> 8) & 255) / 255.0f; 688 float b = ((_backgroundColor >> 0) & 255) / 255.0f; 689 glClearColor(r, g, b, a); 690 glClear(GL_COLOR_BUFFER_BIT); 691 GLDrawBuf buf = new GLDrawBuf(_dx, _dy, false); 692 buf.beforeDrawing(); 693 onDraw(buf); 694 buf.afterDrawing(); 695 glXSwapBuffers(x11display, cast(uint)_win); 696 destroy(buf); 697 } 698 } 699 700 void redraw() { 701 _needRedraw = false; 702 //Use values cached by ConfigureNotify to avoid XGetWindowAttributes call. 703 //XWindowAttributes window_attributes_return; 704 //XGetWindowAttributes(x11display, _win, &window_attributes_return); 705 //Log.d(format("XGetWindowAttributes reported size %d, %d", window_attributes_return.width, window_attributes_return.height)); 706 immutable width = _cachedWidth; 707 immutable height = _cachedHeight; 708 if (width > 0 && height > 0) 709 onResize(width, height); 710 debug(x11) Log.d(format("redraw(%d, %d)", width, height)); 711 if (_enableOpengl) 712 drawUsingOpengl(); 713 else 714 drawUsingBitmap(); 715 } 716 717 protected ButtonDetails _lbutton; 718 protected ButtonDetails _mbutton; 719 protected ButtonDetails _rbutton; 720 721 ushort convertMouseFlags(uint flags) { 722 ushort res = 0; 723 if (flags & Button1Mask) 724 res |= MouseFlag.LButton; 725 if (flags & Button2Mask) 726 res |= MouseFlag.RButton; 727 if (flags & Button3Mask) 728 res |= MouseFlag.MButton; 729 return res; 730 } 731 732 MouseButton convertMouseButton(uint button) { 733 if (button == Button1) 734 return MouseButton.Left; 735 if (button == Button2) 736 return MouseButton.Right; 737 if (button == Button3) 738 return MouseButton.Middle; 739 return MouseButton.None; 740 } 741 742 ushort lastFlags; 743 short lastx; 744 short lasty; 745 uint _keyFlags; 746 void processMouseEvent(MouseAction action, uint button, uint state, int x, int y) { 747 MouseEvent event = null; 748 if (action == MouseAction.Wheel) { 749 // handle wheel 750 short wheelDelta = cast(short)y; 751 if (_keyFlags & KeyFlag.Shift) 752 lastFlags |= MouseFlag.Shift; 753 else 754 lastFlags &= ~MouseFlag.Shift; 755 if (_keyFlags & KeyFlag.Control) 756 lastFlags |= MouseFlag.Control; 757 else 758 lastFlags &= ~MouseFlag.Control; 759 if (_keyFlags & KeyFlag.Alt) 760 lastFlags |= MouseFlag.Alt; 761 else 762 lastFlags &= ~MouseFlag.Alt; 763 if (wheelDelta) 764 event = new MouseEvent(action, MouseButton.None, lastFlags, lastx, lasty, wheelDelta); 765 } else { 766 lastFlags = convertMouseFlags(state); 767 if (_keyFlags & KeyFlag.Shift) 768 lastFlags |= MouseFlag.Shift; 769 if (_keyFlags & KeyFlag.Control) 770 lastFlags |= MouseFlag.Control; 771 if (_keyFlags & KeyFlag.Alt) 772 lastFlags |= MouseFlag.Alt; 773 lastx = cast(short)x; 774 lasty = cast(short)y; 775 MouseButton btn = convertMouseButton(button); 776 event = new MouseEvent(action, btn, lastFlags, lastx, lasty); 777 } 778 if (event) { 779 ButtonDetails * pbuttonDetails = null; 780 if (button == MouseButton.Left) 781 pbuttonDetails = &_lbutton; 782 else if (button == MouseButton.Right) 783 pbuttonDetails = &_rbutton; 784 else if (button == MouseButton.Middle) 785 pbuttonDetails = &_mbutton; 786 if (pbuttonDetails) { 787 if (action == MouseAction.ButtonDown) { 788 pbuttonDetails.down(cast(short)x, cast(short)y, lastFlags); 789 } else if (action == MouseAction.ButtonUp) { 790 pbuttonDetails.up(cast(short)x, cast(short)y, lastFlags); 791 } 792 } 793 event.lbutton = _lbutton; 794 event.rbutton = _rbutton; 795 event.mbutton = _mbutton; 796 bool res = dispatchMouseEvent(event); 797 if (res) { 798 debug(mouse) Log.d("Calling update() after mouse event"); 799 update(); 800 //invalidate(); 801 } 802 } 803 } 804 805 uint convertKeyCode(uint keyCode) { 806 import x11.keysymdef; 807 alias KeyCode = dlangui.core.events.KeyCode; 808 switch(keyCode) { 809 case XK_0: 810 return KeyCode.KEY_0; 811 case XK_1: 812 return KeyCode.KEY_1; 813 case XK_2: 814 return KeyCode.KEY_2; 815 case XK_3: 816 return KeyCode.KEY_3; 817 case XK_4: 818 return KeyCode.KEY_4; 819 case XK_5: 820 return KeyCode.KEY_5; 821 case XK_6: 822 return KeyCode.KEY_6; 823 case XK_7: 824 return KeyCode.KEY_7; 825 case XK_8: 826 return KeyCode.KEY_8; 827 case XK_9: 828 return KeyCode.KEY_9; 829 case XK_A: 830 case XK_a: 831 return KeyCode.KEY_A; 832 case XK_B: 833 case XK_b: 834 return KeyCode.KEY_B; 835 case XK_C: 836 case XK_c: 837 return KeyCode.KEY_C; 838 case XK_D: 839 case XK_d: 840 return KeyCode.KEY_D; 841 case XK_E: 842 case XK_e: 843 return KeyCode.KEY_E; 844 case XK_F: 845 case XK_f: 846 return KeyCode.KEY_F; 847 case XK_G: 848 case XK_g: 849 return KeyCode.KEY_G; 850 case XK_H: 851 case XK_h: 852 return KeyCode.KEY_H; 853 case XK_I: 854 case XK_i: 855 return KeyCode.KEY_I; 856 case XK_J: 857 case XK_j: 858 return KeyCode.KEY_J; 859 case XK_K: 860 case XK_k: 861 return KeyCode.KEY_K; 862 case XK_L: 863 case XK_l: 864 return KeyCode.KEY_L; 865 case XK_M: 866 case XK_m: 867 return KeyCode.KEY_M; 868 case XK_N: 869 case XK_n: 870 return KeyCode.KEY_N; 871 case XK_O: 872 case XK_o: 873 return KeyCode.KEY_O; 874 case XK_P: 875 case XK_p: 876 return KeyCode.KEY_P; 877 case XK_Q: 878 case XK_q: 879 return KeyCode.KEY_Q; 880 case XK_R: 881 case XK_r: 882 return KeyCode.KEY_R; 883 case XK_S: 884 case XK_s: 885 return KeyCode.KEY_S; 886 case XK_T: 887 case XK_t: 888 return KeyCode.KEY_T; 889 case XK_U: 890 case XK_u: 891 return KeyCode.KEY_U; 892 case XK_V: 893 case XK_v: 894 return KeyCode.KEY_V; 895 case XK_W: 896 case XK_w: 897 return KeyCode.KEY_W; 898 case XK_X: 899 case XK_x: 900 return KeyCode.KEY_X; 901 case XK_Y: 902 case XK_y: 903 return KeyCode.KEY_Y; 904 case XK_Z: 905 case XK_z: 906 return KeyCode.KEY_Z; 907 case XK_F1: 908 return KeyCode.F1; 909 case XK_F2: 910 return KeyCode.F2; 911 case XK_F3: 912 return KeyCode.F3; 913 case XK_F4: 914 return KeyCode.F4; 915 case XK_F5: 916 return KeyCode.F5; 917 case XK_F6: 918 return KeyCode.F6; 919 case XK_F7: 920 return KeyCode.F7; 921 case XK_F8: 922 return KeyCode.F8; 923 case XK_F9: 924 return KeyCode.F9; 925 case XK_F10: 926 return KeyCode.F10; 927 case XK_F11: 928 return KeyCode.F11; 929 case XK_F12: 930 return KeyCode.F12; 931 case XK_F13: 932 return KeyCode.F13; 933 case XK_F14: 934 return KeyCode.F14; 935 case XK_F15: 936 return KeyCode.F15; 937 case XK_F16: 938 return KeyCode.F16; 939 case XK_F17: 940 return KeyCode.F17; 941 case XK_F18: 942 return KeyCode.F18; 943 case XK_F19: 944 return KeyCode.F19; 945 case XK_F20: 946 return KeyCode.F20; 947 case XK_F21: 948 return KeyCode.F21; 949 case XK_F22: 950 return KeyCode.F22; 951 case XK_F23: 952 return KeyCode.F23; 953 case XK_F24: 954 return KeyCode.F24; 955 case XK_BackSpace: 956 return KeyCode.BACK; 957 case XK_space: 958 return KeyCode.SPACE; 959 case XK_Tab: 960 return KeyCode.TAB; 961 case XK_Return: 962 case XK_KP_Enter: 963 return KeyCode.RETURN; 964 case XK_Escape: 965 return KeyCode.ESCAPE; 966 case XK_KP_Delete: 967 case XK_Delete: 968 //case 0x40000063: // dirty hack for Linux - key on keypad 969 return KeyCode.DEL; 970 case XK_Insert: 971 case XK_KP_Insert: 972 //case 0x40000062: // dirty hack for Linux - key on keypad 973 return KeyCode.INS; 974 case XK_KP_Home: 975 case XK_Home: 976 //case 0x4000005f: // dirty hack for Linux - key on keypad 977 return KeyCode.HOME; 978 case XK_KP_Page_Up: 979 case XK_Page_Up: 980 //case 0x40000061: // dirty hack for Linux - key on keypad 981 return KeyCode.PAGEUP; 982 case XK_KP_End: 983 case XK_End: 984 //case 0x40000059: // dirty hack for Linux - key on keypad 985 return KeyCode.END; 986 case XK_KP_Page_Down: 987 case XK_Page_Down: 988 //case 0x4000005b: // dirty hack for Linux - key on keypad 989 return KeyCode.PAGEDOWN; 990 case XK_KP_Left: 991 case XK_Left: 992 //case 0x4000005c: // dirty hack for Linux - key on keypad 993 return KeyCode.LEFT; 994 case XK_KP_Right: 995 case XK_Right: 996 //case 0x4000005e: // dirty hack for Linux - key on keypad 997 return KeyCode.RIGHT; 998 case XK_KP_Up: 999 case XK_Up: 1000 //case 0x40000060: // dirty hack for Linux - key on keypad 1001 return KeyCode.UP; 1002 case XK_KP_Down: 1003 case XK_Down: 1004 //case 0x4000005a: // dirty hack for Linux - key on keypad 1005 return KeyCode.DOWN; 1006 case XK_Control_L: 1007 return KeyCode.LCONTROL; 1008 case XK_Shift_L: 1009 return KeyCode.LSHIFT; 1010 case XK_Alt_L: 1011 return KeyCode.LALT; 1012 case XK_Control_R: 1013 return KeyCode.RCONTROL; 1014 case XK_Shift_R: 1015 return KeyCode.RSHIFT; 1016 case XK_Alt_R: 1017 return KeyCode.RALT; 1018 case XK_slash: 1019 case XK_KP_Divide: 1020 return KeyCode.KEY_DIVIDE; 1021 default: 1022 return 0x10000 | keyCode; 1023 } 1024 } 1025 1026 uint convertKeyFlags(uint flags) { 1027 uint res; 1028 if (flags & ControlMask) 1029 res |= KeyFlag.Control; 1030 if (flags & ShiftMask) 1031 res |= KeyFlag.Shift; 1032 if (flags & LockMask) 1033 res |= KeyFlag.Alt; 1034 // if (flags & KMOD_RCTRL) 1035 // res |= KeyFlag.RControl | KeyFlag.Control; 1036 // if (flags & KMOD_RSHIFT) 1037 // res |= KeyFlag.RShift | KeyFlag.Shift; 1038 // if (flags & KMOD_RALT) 1039 // res |= KeyFlag.RAlt | KeyFlag.Alt; 1040 // if (flags & KMOD_LCTRL) 1041 // res |= KeyFlag.LControl | KeyFlag.Control; 1042 // if (flags & KMOD_LSHIFT) 1043 // res |= KeyFlag.LShift | KeyFlag.Shift; 1044 // if (flags & KMOD_LALT) 1045 // res |= KeyFlag.LAlt | KeyFlag.Alt; 1046 return res; 1047 } 1048 1049 1050 bool processKeyEvent(KeyAction action, uint keyCode, uint flags) { 1051 //debug(DebugSDL) 1052 Log.d("processKeyEvent ", action, " X11 key=0x", format("%08x", keyCode), " X11 flags=0x", format("%08x", flags)); 1053 keyCode = convertKeyCode(keyCode); 1054 flags = convertKeyFlags(flags); 1055 Log.d("processKeyEvent ", action, " converted key=0x", format("%08x", keyCode), " flags=0x", format("%08x", flags)); 1056 1057 alias KeyCode = dlangui.core.events.KeyCode; 1058 if (action == KeyAction.KeyDown) { 1059 switch(keyCode) { 1060 case KeyCode.ALT: 1061 flags |= KeyFlag.Alt; 1062 break; 1063 case KeyCode.RALT: 1064 flags |= KeyFlag.Alt | KeyFlag.RAlt; 1065 break; 1066 case KeyCode.LALT: 1067 flags |= KeyFlag.Alt | KeyFlag.LAlt; 1068 break; 1069 case KeyCode.CONTROL: 1070 flags |= KeyFlag.Control; 1071 break; 1072 case KeyCode.RCONTROL: 1073 flags |= KeyFlag.Control | KeyFlag.RControl; 1074 break; 1075 case KeyCode.LCONTROL: 1076 flags |= KeyFlag.Control | KeyFlag.LControl; 1077 break; 1078 case KeyCode.SHIFT: 1079 flags |= KeyFlag.Shift; 1080 break; 1081 case KeyCode.RSHIFT: 1082 flags |= KeyFlag.Shift | KeyFlag.RShift; 1083 break; 1084 case KeyCode.LSHIFT: 1085 flags |= KeyFlag.Shift | KeyFlag.LShift; 1086 break; 1087 default: 1088 break; 1089 } 1090 } 1091 _keyFlags = flags; 1092 1093 debug(DebugSDL) Log.d("processKeyEvent ", action, " converted key=0x", format("%08x", keyCode), " converted flags=0x", format("%08x", flags)); 1094 bool res = dispatchKeyEvent(new KeyEvent(action, keyCode, flags)); 1095 // if ((keyCode & 0x10000) && (keyCode & 0xF000) != 0xF000) { 1096 // dchar[1] text; 1097 // text[0] = keyCode & 0xFFFF; 1098 // res = dispatchKeyEvent(new KeyEvent(KeyAction.Text, keyCode, flags, cast(dstring)text)) || res; 1099 // } 1100 if (res) { 1101 debug(keys) Log.d("Calling update() after key event"); 1102 //invalidate(); 1103 update(); 1104 } 1105 return res; 1106 } 1107 1108 bool processTextInput(dstring ds, uint flags) { 1109 flags = convertKeyFlags(flags); 1110 bool res = dispatchKeyEvent(new KeyEvent(KeyAction.Text, 0, flags, ds)); 1111 if (res) { 1112 debug(keys) Log.d("Calling update() after text event"); 1113 invalidate(); 1114 } 1115 return res; 1116 } 1117 1118 /// after drawing, call to schedule redraw if animation is active 1119 override void scheduleAnimation() { 1120 invalidate(); 1121 } 1122 1123 TimerThread timer; 1124 private long _nextExpectedTimerTs; 1125 1126 /// schedule timer for interval in milliseconds - call window.onTimer when finished 1127 override protected void scheduleSystemTimer(long intervalMillis) { 1128 if (!timer) { 1129 timer = new TimerThread(delegate() { 1130 XEvent ev; 1131 memset(&ev, 0, ev.sizeof); 1132 //ev.xclient = XClientMessageEvent.init; 1133 ev.xclient.type = ClientMessage; 1134 ev.xclient.message_type = atom_DLANGUI_TIMER_EVENT; 1135 ev.xclient.window = _win; 1136 ev.xclient.display = x11display2; 1137 ev.xclient.format = 32; 1138 //Log.d("Sending timer event"); 1139 XLockDisplay(x11display2); 1140 XSendEvent(x11display2, _win, false, StructureNotifyMask, &ev); 1141 XFlush(x11display2); 1142 XUnlockDisplay(x11display2); 1143 }); 1144 } 1145 if (intervalMillis < 10) 1146 intervalMillis = 10; 1147 long nextts = currentTimeMillis + intervalMillis; 1148 if (_nextExpectedTimerTs == 0 || _nextExpectedTimerTs > nextts) { 1149 _nextExpectedTimerTs = nextts; 1150 timer.set(nextts); 1151 } 1152 } 1153 1154 bool handleTimer() { 1155 if (!_nextExpectedTimerTs) 1156 return false; 1157 long ts = currentTimeMillis; 1158 if (ts >= _nextExpectedTimerTs) { 1159 _nextExpectedTimerTs = 0; 1160 onTimer(); 1161 return true; 1162 } 1163 return false; 1164 } 1165 1166 /// post event to handle in UI thread (this method can be used from background thread) 1167 override void postEvent(CustomEvent event) { 1168 super.postEvent(event); 1169 XEvent ev; 1170 memset(&ev, 0, ev.sizeof); 1171 ev.xclient.type = ClientMessage; 1172 ev.xclient.window = _win; 1173 ev.xclient.display = x11display2; 1174 ev.xclient.message_type = atom_DLANGUI_TASK_EVENT; 1175 ev.xclient.format = 32; 1176 ev.xclient.data.l[0] = event.uniqueId; 1177 XLockDisplay(x11display2); 1178 XSendEvent(x11display2, _win, false, StructureNotifyMask, &ev); 1179 XFlush(x11display2); 1180 XUnlockDisplay(x11display2); 1181 // SDL_Event sdlevent; 1182 // sdlevent.user.type = USER_EVENT_ID; 1183 // sdlevent.user.code = cast(int)event.uniqueId; 1184 // sdlevent.user.windowID = windowId; 1185 // SDL_PushEvent(&sdlevent); 1186 } 1187 1188 protected uint _lastCursorType = CursorType.None; 1189 /// sets cursor type for window 1190 override protected void setCursorType(uint cursorType) { 1191 if (_lastCursorType != cursorType) { 1192 Log.d("setCursorType(", cursorType, ")"); 1193 _lastCursorType = cursorType; 1194 XDefineCursor(x11display, _win, x11cursors[cursorType]); 1195 XFlush(x11display); 1196 } 1197 } 1198 } 1199 1200 private immutable int CUSTOM_EVENT = 32; 1201 private immutable int TIMER_EVENT = 8; 1202 1203 class X11Platform : Platform { 1204 1205 this() { 1206 } 1207 1208 private X11Window[XWindow] _windowMap; 1209 private X11Window[] _windowList; 1210 1211 /** 1212 * create window 1213 * Args: 1214 * windowCaption = window caption text 1215 * parent = parent Window, or null if no parent 1216 * flags = WindowFlag bit set, combination of Resizable, Modal, Fullscreen 1217 * width = window width 1218 * height = window height 1219 * 1220 * Window w/o Resizable nor Fullscreen will be created with size based on measurement of its content widget 1221 */ 1222 override DWindow createWindow(dstring windowCaption, DWindow parent, uint flags = WindowFlag.Resizable, uint width = 0, uint height = 0) { 1223 int newwidth = width; 1224 int newheight = height; 1225 X11Window window = new X11Window(this, windowCaption, parent, flags, newwidth, newheight); 1226 _windowMap[window._win] = window; 1227 _windowList ~= window; 1228 return window; 1229 } 1230 1231 X11Window findWindow(XWindow windowId) { 1232 if (windowId in _windowMap) 1233 return _windowMap[windowId]; 1234 return null; 1235 } 1236 1237 /** 1238 * close window 1239 * 1240 * Closes window earlier created with createWindow() 1241 */ 1242 override void closeWindow(DWindow w) { 1243 X11Window window = cast(X11Window)w; 1244 XEvent ev; 1245 memset(&ev, 0, ev.sizeof); 1246 ev.xclient.type = ClientMessage; 1247 ev.xclient.message_type = atom_DLANGUI_CLOSE_WINDOW_EVENT; 1248 ev.xclient.window = window._win; 1249 ev.xclient.display = x11display2; 1250 ev.xclient.format = 32; 1251 Log.d("Sending close window event"); 1252 XLockDisplay(x11display2); 1253 XSendEvent(x11display2, window._win, false, StructureNotifyMask, &ev); 1254 XFlush(x11display2); 1255 XUnlockDisplay(x11display2); 1256 1257 for (uint i = 0; i < _windowList.length; i++) { 1258 if (w is _windowList[i]) { 1259 for (uint j = i; j + 1 < _windowList.length; j++) 1260 _windowList[j] = _windowList[j + 1]; 1261 _windowList[$ - 1] = null; 1262 _windowList.length--; 1263 break; 1264 } 1265 } 1266 1267 } 1268 1269 bool handleTimers() { 1270 bool handled = false; 1271 foreach(w; _windowMap) { 1272 if (w.handleTimer()) { 1273 handled = true; 1274 break; 1275 } 1276 } 1277 return handled; 1278 } 1279 1280 final bool allWindowsClosed() { 1281 return _windowMap.length == 0; 1282 } 1283 1284 /** 1285 * Starts application message loop. 1286 * 1287 * When returned from this method, application is shutting down. 1288 */ 1289 override int enterMessageLoop() { 1290 import core.thread; 1291 XEvent event; /* the XEvent declaration !!! */ 1292 KeySym key; /* a dealie-bob to handle KeyPress Events */ 1293 char[255] text; /* a char buffer for KeyPress Events */ 1294 1295 Log.d("enterMessageLoop()"); 1296 XComposeStatus compose; 1297 1298 import core.sys.posix.sys.select; 1299 int x11displayFd = ConnectionNumber(x11display); 1300 fd_set fdSet; 1301 FD_ZERO(&fdSet); 1302 FD_SET(x11displayFd, &fdSet); 1303 scope(exit) FD_ZERO(&fdSet); 1304 while(!allWindowsClosed()) { 1305 // Note: only events we set the mask for are detected! 1306 foreach(win; _windowMap) { 1307 if (win._needRedraw) { 1308 win.redraw(); 1309 } 1310 } 1311 XFlush(x11display); 1312 int eventsInQueue = XEventsQueued(x11display, QueuedAlready); 1313 if (!eventsInQueue) { 1314 import core.stdc.errno; 1315 int selectResult; 1316 do { 1317 timeval zeroTime; 1318 selectResult = select(x11displayFd + 1, &fdSet, null, null, &zeroTime); 1319 } while(selectResult == -1 && errno == EINTR); 1320 if (selectResult < 0) { 1321 Log.e("X11: display fd select error"); 1322 } else if (selectResult == 1) { 1323 Log.d("X11: XPending"); 1324 eventsInQueue = XPending(x11display); 1325 } 1326 } 1327 if (!eventsInQueue) { 1328 debug(x11) Log.d("X11: Sleeping"); 1329 Thread.sleep(dur!("msecs")(10)); 1330 } 1331 foreach(eventIndex; 0..eventsInQueue) 1332 { 1333 if (allWindowsClosed()) 1334 break; 1335 XNextEvent(x11display, &event); 1336 switch (event.type) { 1337 case ConfigureNotify: 1338 X11Window w = findWindow(event.xconfigure.window); 1339 if (w) { 1340 w._cachedWidth = event.xconfigure.width; 1341 w._cachedHeight = event.xconfigure.height; 1342 w.handleWindowStateChange(WindowState.unspecified, Rect(event.xconfigure.x, event.xconfigure.y, event.xconfigure.width, event.xconfigure.height)); 1343 } else { 1344 Log.e("ConfigureNotify: Window not found"); 1345 } 1346 break; 1347 case PropertyNotify: 1348 if (event.xproperty.atom == atom_NET_WM_STATE && event.xproperty.state == PropertyNewValue) { 1349 X11Window w = findWindow(event.xproperty.window); 1350 if (w) { 1351 Atom type; 1352 int format; 1353 ubyte* properties; 1354 c_ulong dataLength, overflow; 1355 if (XGetWindowProperty(x11display, event.xproperty.window, atom_NET_WM_STATE, 1356 0, int.max/4, False, AnyPropertyType, &type, &format, &dataLength, &overflow, &properties) == 0) { 1357 scope(exit) XFree(properties); 1358 // check for minimized 1359 bool minimized = false; 1360 for (int i=0; i < dataLength ; i++) { 1361 if (((cast(c_ulong*)properties)[i]) == atom_NET_WM_STATE_HIDDEN) { 1362 w.handleWindowStateChange(WindowState.minimized); 1363 minimized = true; 1364 } 1365 } 1366 if (!minimized) { 1367 bool maximizedH = false; 1368 bool maximizedV = false; 1369 for (int i=0; i < dataLength ; i++) { 1370 if (((cast(c_ulong*)properties)[i]) == atom_NET_WM_STATE_MAXIMIZED_VERT) 1371 maximizedV = true; 1372 if (((cast(c_ulong*)properties)[i]) == atom_NET_WM_STATE_MAXIMIZED_HORZ) 1373 maximizedH = true; 1374 } 1375 1376 if (maximizedV && maximizedH) 1377 w.handleWindowStateChange(WindowState.maximized); 1378 else 1379 w.handleWindowStateChange(WindowState.normal); 1380 1381 } 1382 } 1383 } 1384 } 1385 break; 1386 case MapNotify: 1387 X11Window w = findWindow(event.xmap.window); 1388 if (w) { 1389 w.handleWindowStateChange(WindowState.normal); 1390 } 1391 break; 1392 case UnmapNotify: 1393 X11Window w = findWindow(event.xunmap.window); 1394 if (w) { 1395 w.handleWindowStateChange(WindowState.hidden); 1396 } 1397 break; 1398 case Expose: 1399 if (event.xexpose.count == 0) { 1400 X11Window w = findWindow(event.xexpose.window); 1401 if (w) { 1402 w.invalidate(); 1403 } else { 1404 Log.e("Expose: Window not found"); 1405 } 1406 } else { 1407 Log.d("Expose: non-0 count"); 1408 } 1409 break; 1410 case KeyPress: 1411 Log.d("X11: KeyPress event"); 1412 X11Window w = findWindow(event.xkey.window); 1413 if (w) { 1414 char[100] buf; 1415 KeySym ks; 1416 Status s; 1417 if (!w.xic) { 1418 w.xic = XCreateIC(xim, 1419 XNInputStyle, XIMPreeditNothing | XIMStatusNothing, 1420 XNClientWindow, w._win, 0); 1421 if (!w.xic) { 1422 Log.e("Cannot create input context"); 1423 } 1424 } 1425 1426 if (!w.xic) 1427 XLookupString(&event.xkey, buf.ptr, buf.length - 1, &ks, &compose); 1428 else { 1429 Xutf8LookupString(w.xic, &event.xkey, buf.ptr, cast(int)buf.length - 1, &ks, &s); 1430 if (s != XLookupChars && s != XLookupBoth) 1431 XLookupString(&event.xkey, buf.ptr, buf.length - 1, &ks, &compose); 1432 } 1433 foreach(ref ch; buf) { 1434 if (ch == 255 || ch < 32 || ch == 127) 1435 ch = 0; 1436 } 1437 string txt = fromStringz(buf.ptr).dup; 1438 import std.utf; 1439 dstring dtext; 1440 try { 1441 if (txt.length) 1442 dtext = toUTF32(txt); 1443 } catch (UTFException e) { 1444 // ignore, invalid text 1445 } 1446 debug(x11) Log.d("X11: KeyPress event bytes=", txt.length, " text=", txt, " dtext=", dtext); 1447 if (dtext.length) { 1448 w.processTextInput(dtext, event.xkey.state); 1449 } else { 1450 w.processKeyEvent(KeyAction.KeyDown, cast(uint)ks, 1451 //event.xkey.keycode, 1452 event.xkey.state); 1453 } 1454 } else { 1455 Log.e("Window not found"); 1456 } 1457 break; 1458 case KeyRelease: 1459 Log.d("X11: KeyRelease event"); 1460 X11Window w = findWindow(event.xkey.window); 1461 if (w) { 1462 char[100] buf; 1463 KeySym ks; 1464 XLookupString(&event.xkey, buf.ptr, buf.length - 1, &ks, &compose); 1465 w.processKeyEvent(KeyAction.KeyUp, cast(uint)ks, 1466 //event.xkey.keycode, 1467 event.xkey.state); 1468 } else { 1469 Log.e("Window not found"); 1470 } 1471 break; 1472 case ButtonPress: 1473 Log.d("X11: ButtonPress event"); 1474 X11Window w = findWindow(event.xbutton.window); 1475 if (w) { 1476 w.processMouseEvent(MouseAction.ButtonDown, event.xbutton.button, event.xbutton.state, event.xbutton.x, event.xbutton.y); 1477 } else { 1478 Log.e("Window not found"); 1479 } 1480 break; 1481 case ButtonRelease: 1482 Log.d("X11: ButtonRelease event"); 1483 X11Window w = findWindow(event.xbutton.window); 1484 if (w) { 1485 w.processMouseEvent(MouseAction.ButtonUp, event.xbutton.button, event.xbutton.state, event.xbutton.x, event.xbutton.y); 1486 } else { 1487 Log.e("Window not found"); 1488 } 1489 break; 1490 case MotionNotify: 1491 debug(x11) Log.d("X11: MotionNotify event"); 1492 X11Window w = findWindow(event.xmotion.window); 1493 if (w) { 1494 w.processMouseEvent(MouseAction.Move, 0, event.xmotion.state, event.xmotion.x, event.xmotion.y); 1495 } else { 1496 Log.e("Window not found"); 1497 } 1498 break; 1499 case EnterNotify: 1500 Log.d("X11: EnterNotify event"); 1501 X11Window w = findWindow(event.xcrossing.window); 1502 if (!w) { 1503 Log.e("Window not found"); 1504 } 1505 break; 1506 case LeaveNotify: 1507 Log.d("X11: LeaveNotify event"); 1508 X11Window w = findWindow(event.xcrossing.window); 1509 if (w) { 1510 w.processMouseEvent(MouseAction.Leave, 0, event.xcrossing.state, event.xcrossing.x, event.xcrossing.y); 1511 } else { 1512 Log.e("Window not found"); 1513 } 1514 break; 1515 case CreateNotify: 1516 Log.d("X11: CreateNotify event"); 1517 X11Window w = findWindow(event.xcreatewindow.window); 1518 if (!w) { 1519 Log.e("Window not found"); 1520 } 1521 break; 1522 case DestroyNotify: 1523 Log.d("X11: DestroyNotify event"); 1524 break; 1525 case ResizeRequest: 1526 Log.d("X11: ResizeRequest event"); 1527 X11Window w = findWindow(event.xresizerequest.window); 1528 if (!w) { 1529 Log.e("Window not found"); 1530 } 1531 break; 1532 case FocusIn: 1533 Log.d("X11: FocusIn event"); 1534 X11Window w = findWindow(event.xfocus.window); 1535 if (w) 1536 w.handleWindowActivityChange(true); 1537 else 1538 Log.e("Window not found"); 1539 break; 1540 case FocusOut: 1541 Log.d("X11: FocusOut event"); 1542 X11Window w = findWindow(event.xfocus.window); 1543 if (w) 1544 w.handleWindowActivityChange(false); 1545 else 1546 Log.e("Window not found"); 1547 break; 1548 case KeymapNotify: 1549 Log.d("X11: KeymapNotify event"); 1550 X11Window w = findWindow(event.xkeymap.window); 1551 break; 1552 case SelectionClear: 1553 Log.d("X11: SelectionClear event"); 1554 break; 1555 case SelectionRequest: 1556 debug(x11) Log.d("X11: SelectionRequest event"); 1557 if (event.xselectionrequest.owner in _windowMap) { 1558 XSelectionRequestEvent *selectionRequest = &event.xselectionrequest; 1559 1560 XEvent selectionEvent; 1561 memset(&selectionEvent, 0, selectionEvent.sizeof); 1562 selectionEvent.xany.type = SelectionNotify; 1563 selectionEvent.xselection.selection = selectionRequest.selection; 1564 selectionEvent.xselection.target = selectionRequest.target; 1565 selectionEvent.xselection.property = None; 1566 selectionEvent.xselection.requestor = selectionRequest.requestor; 1567 selectionEvent.xselection.time = selectionRequest.time; 1568 1569 if (selectionRequest.target == XA_STRING || selectionRequest.target == atom_UTF8_STRING) { 1570 static if (false) { 1571 int currentSelectionFormat; 1572 Atom currentSelectionType; 1573 c_ulong selectionDataLength, overflow; 1574 ubyte* selectionDataPtr; 1575 if (XGetWindowProperty(x11display, DefaultRootWindow(x11display), atom_DLANGUI_CLIPBOARD_BUFFER, 1576 0, int.max/4, False, selectionRequest.target, 1577 ¤tSelectionType, ¤tSelectionFormat, &selectionDataLength, 1578 &overflow, &selectionDataPtr) == 0) 1579 { 1580 scope(exit) XFree(selectionDataPtr); 1581 XChangeProperty(x11display, selectionRequest.requestor, selectionRequest.property, 1582 selectionRequest.target, 8, PropModeReplace, 1583 selectionDataPtr, cast(int)selectionDataLength); 1584 } 1585 } else { 1586 XChangeProperty(x11display, selectionRequest.requestor, selectionRequest.property, 1587 selectionRequest.target, 8, PropModeReplace, 1588 cast(ubyte*)localClipboardContent, cast(int)localClipboardContent.length); 1589 } 1590 selectionEvent.xselection.property = selectionRequest.property; 1591 } else if (selectionRequest.target == atom_TARGETS) { 1592 Atom[3] supportedFormats = [atom_UTF8_STRING, XA_STRING, atom_TARGETS]; 1593 XChangeProperty(x11display, selectionRequest.requestor, selectionRequest.property, 1594 XA_ATOM, 32, PropModeReplace, 1595 cast(ubyte*)supportedFormats.ptr, cast(int)supportedFormats.length); 1596 selectionEvent.xselection.property = selectionRequest.property; 1597 } 1598 XSendEvent(x11display, selectionRequest.requestor, False, 0, &selectionEvent); 1599 } 1600 break; 1601 case SelectionNotify: 1602 Log.d("X11: SelectionNotify event"); 1603 X11Window w = findWindow(event.xselection.requestor); 1604 break; 1605 case ClientMessage: 1606 debug(x11) Log.d("X11: ClientMessage event"); 1607 X11Window w = findWindow(event.xclient.window); 1608 if (w) { 1609 if (event.xclient.message_type == atom_DLANGUI_TASK_EVENT) { 1610 w.handlePostedEvent(cast(uint)event.xclient.data.l[0]); 1611 } else if (event.xclient.message_type == atom_DLANGUI_TIMER_EVENT) { 1612 w.handleTimer(); 1613 } else if (event.xclient.message_type == atom_WM_PROTOCOLS) { 1614 Log.d("Handling WM_PROTOCOLS"); 1615 if ((event.xclient.format == 32) && (event.xclient.data.l[0]) == atom_WM_DELETE_WINDOW) { 1616 Log.d("Handling WM_DELETE_WINDOW"); 1617 _windowMap.remove(w._win); 1618 destroy(w); 1619 } 1620 } else if (event.xclient.message_type == atom_DLANGUI_CLOSE_WINDOW_EVENT) { 1621 _windowMap.remove(w._win); 1622 destroy(w); 1623 } 1624 } else { 1625 Log.e("Window not found"); 1626 } 1627 break; 1628 default: 1629 break; 1630 } 1631 } 1632 } 1633 return 0; 1634 } 1635 1636 /// check has clipboard text 1637 override bool hasClipboardText(bool mouseBuffer = false) { 1638 return (localClipboardContent.length != 0); 1639 } 1640 1641 /// retrieves text from clipboard (when mouseBuffer == true, use mouse selection clipboard - under linux) 1642 override dstring getClipboardText(bool mouseBuffer = false) { 1643 return toUTF32(localClipboardContent); 1644 } 1645 1646 /// sets text to clipboard (when mouseBuffer == true, use mouse selection clipboard - under linux) 1647 override void setClipboardText(dstring text, bool mouseBuffer = false) { 1648 localClipboardContent = toUTF8(text); 1649 if (!mouseBuffer && atom_CLIPBOARD == None) { 1650 Log.e("No CLIPBOARD atom available"); 1651 return; 1652 } 1653 XWindow xwindow = None; 1654 // Find any top-level window 1655 foreach(w; _windowMap) { 1656 if (w._parent is null && w._win != None) { 1657 xwindow = w._win; 1658 } 1659 } 1660 if (xwindow == None) { 1661 Log.e("Could not find window to save clipboard text"); 1662 return; 1663 } 1664 static if (false) { 1665 // This is example of how setting clipboard contents can be implemented without global variable 1666 auto textc = text.toUTF8; 1667 XChangeProperty(x11display, DefaultRootWindow(x11display), atom_DLANGUI_CLIPBOARD_BUFFER, XA_STRING, 8, PropModeReplace, cast(ubyte*)textc.ptr, cast(int)textc.length); 1668 } 1669 1670 if (mouseBuffer && XGetSelectionOwner(x11display, XA_PRIMARY) != xwindow) { 1671 XSetSelectionOwner(x11display, XA_PRIMARY, xwindow, CurrentTime); 1672 } else if (XGetSelectionOwner(x11display, atom_CLIPBOARD != xwindow)) { 1673 XSetSelectionOwner(x11display, atom_CLIPBOARD, xwindow, CurrentTime); 1674 } 1675 } 1676 1677 /// calls request layout for all windows 1678 override void requestLayout() { 1679 foreach(w; _windowMap) { 1680 w.requestLayout(); 1681 } 1682 } 1683 1684 /// returns true if there is some modal window opened above this window, and this window should not process mouse/key input and should not allow closing 1685 override bool hasModalWindowsAbove(DWindow w) { 1686 // override in platform specific class 1687 for (uint i = 0; i + 1 < _windowList.length; i++) { 1688 if (_windowList[i] is w) { 1689 for (uint j = i + 1; j < _windowList.length; j++) { 1690 if (_windowList[j].flags & WindowFlag.Modal && _windowList[j].windowState != WindowState.hidden) 1691 return true; 1692 } 1693 return false; 1694 } 1695 } 1696 return false; 1697 } 1698 1699 /// handle theme change: e.g. reload some themed resources 1700 override void onThemeChanged() { 1701 foreach(w; _windowMap) 1702 w.dispatchThemeChanged(); 1703 } 1704 1705 } 1706 1707 import core.thread; 1708 import core.sync.mutex; 1709 import core.sync.condition; 1710 class TimerThread : Thread { 1711 Mutex mutex; 1712 Condition condition; 1713 bool stopped; 1714 long nextEventTs; 1715 void delegate() callback; 1716 1717 this(void delegate() timerCallback) { 1718 callback = timerCallback; 1719 mutex = new Mutex(); 1720 condition = new Condition(mutex); 1721 super(&run); 1722 start(); 1723 } 1724 1725 ~this() { 1726 stop(); 1727 destroy(condition); 1728 destroy(mutex); 1729 } 1730 1731 void set(long nextTs) { 1732 mutex.lock(); 1733 if (nextEventTs == 0 || nextEventTs > nextTs) { 1734 nextEventTs = nextTs; 1735 condition.notify(); 1736 } 1737 mutex.unlock(); 1738 } 1739 void run() { 1740 1741 while (!stopped) { 1742 bool expired = false; 1743 1744 mutex.lock(); 1745 1746 long ts = currentTimeMillis; 1747 long timeToWait = nextEventTs == 0 ? 1000000 : nextEventTs - ts; 1748 if (timeToWait < 10) 1749 timeToWait = 10; 1750 1751 if (nextEventTs == 0) 1752 condition.wait(); 1753 else 1754 condition.wait(dur!"msecs"(timeToWait)); 1755 1756 if (stopped) { 1757 mutex.unlock(); 1758 break; 1759 } 1760 ts = currentTimeMillis; 1761 if (nextEventTs && nextEventTs < ts && !stopped) { 1762 expired = true; 1763 nextEventTs = 0; 1764 } 1765 1766 mutex.unlock(); 1767 1768 if (expired) 1769 callback(); 1770 } 1771 } 1772 void stop() { 1773 if (stopped) 1774 return; 1775 stopped = true; 1776 mutex.lock(); 1777 condition.notify(); 1778 mutex.unlock(); 1779 join(); 1780 } 1781 } 1782 1783 1784 extern(C) int DLANGUImain(string[] args) 1785 { 1786 initLogs(); 1787 1788 if (!initFontManager()) { 1789 Log.e("******************************************************************"); 1790 Log.e("No font files found!!!"); 1791 Log.e("Currently, only hardcoded font paths implemented."); 1792 Log.e("Probably you can modify sdlapp.d to add some fonts for your system."); 1793 Log.e("TODO: use fontconfig"); 1794 Log.e("******************************************************************"); 1795 assert(false); 1796 } 1797 initResourceManagers(); 1798 1799 currentTheme = createDefaultTheme(); 1800 1801 XInitThreads(); 1802 1803 /* use the information from the environment variable DISPLAY 1804 to create the X connection: 1805 */ 1806 x11display = XOpenDisplay(null); 1807 if (!x11display) { 1808 Log.e("Cannot open X11 display"); 1809 return 1; 1810 } 1811 x11display2 = XOpenDisplay(null); 1812 if (!x11display2) { 1813 Log.e("Cannot open secondary connection for X11 display"); 1814 return 1; 1815 } 1816 1817 x11screen = DefaultScreen(x11display); 1818 1819 static if (ENABLE_OPENGL) { 1820 try { 1821 DerelictGL3.missingSymbolCallback = &gl3MissingSymFunc; 1822 DerelictGL3.load(); 1823 DerelictGL.missingSymbolCallback = &gl3MissingSymFunc; 1824 DerelictGL.load(); 1825 Log.d("OpenGL library loaded ok"); 1826 GLint[] att = [ GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None ]; 1827 XWindow root; 1828 root = DefaultRootWindow(x11display); 1829 x11visual = glXChooseVisual(x11display, 0, cast(int*)att.ptr); 1830 if (x11visual) { 1831 x11cmap = XCreateColormap(x11display, root, cast(Visual*)x11visual.visual, AllocNone); 1832 _enableOpengl = true; 1833 } else { 1834 Log.e("Cannot find suitable Visual for using of OpenGL"); 1835 } 1836 } catch (Exception e) { 1837 Log.e("Cannot load OpenGL library", e); 1838 } 1839 } 1840 1841 1842 setupX11Atoms(); 1843 1844 x11cursors[CursorType.None] = XCreateFontCursor(x11display, XC_arrow); 1845 x11cursors[CursorType.Parent] = XCreateFontCursor(x11display, XC_arrow); 1846 x11cursors[CursorType.Arrow] = XCreateFontCursor(x11display, XC_left_ptr); 1847 x11cursors[CursorType.IBeam] = XCreateFontCursor(x11display, XC_xterm); 1848 x11cursors[CursorType.Wait] = XCreateFontCursor(x11display, XC_watch); 1849 x11cursors[CursorType.Crosshair] = XCreateFontCursor(x11display, XC_tcross); 1850 x11cursors[CursorType.WaitArrow] = XCreateFontCursor(x11display, XC_watch); 1851 x11cursors[CursorType.SizeNWSE] = XCreateFontCursor(x11display, XC_fleur); 1852 x11cursors[CursorType.SizeNESW] = XCreateFontCursor(x11display, XC_fleur); 1853 x11cursors[CursorType.SizeWE] = XCreateFontCursor(x11display, XC_sb_h_double_arrow); 1854 x11cursors[CursorType.SizeNS] = XCreateFontCursor(x11display, XC_sb_v_double_arrow); 1855 x11cursors[CursorType.SizeAll] = XCreateFontCursor(x11display, XC_fleur); 1856 x11cursors[CursorType.No] = XCreateFontCursor(x11display, XC_pirate); 1857 x11cursors[CursorType.Hand] = XCreateFontCursor(x11display, XC_hand2); 1858 1859 xim = XOpenIM(x11display, null, null, null); 1860 if (!xim) { 1861 Log.e("Cannot open input method"); 1862 } 1863 1864 Log.d("X11 display=", x11display, " screen=", x11screen); 1865 1866 1867 1868 X11Platform x11platform = new X11Platform(); 1869 1870 Platform.setInstance(x11platform); 1871 1872 int res = 0; 1873 1874 version (unittest) { 1875 } else { 1876 res = UIAppMain(args); 1877 } 1878 1879 //Log.e("Widget instance count after UIAppMain: ", Widget.instanceCount()); 1880 1881 Log.d("Destroying X11 platform"); 1882 Platform.setInstance(null); 1883 1884 releaseResourcesOnAppExit(); 1885 1886 1887 XCloseDisplay(x11display); 1888 XCloseDisplay(x11display2); 1889 1890 Log.d("Exiting main width result=", res); 1891 1892 return res; 1893 }