1 /** 2 * X11 window implementation. 3 * 4 * Copyright: Copyright (C) 2017 Richard Andrew Cattermole 5 * Copyright (C) 2017 Ethan Reker 6 * Copyright (C) 2017 Lukasz Pelszynski 7 * Copyright (C) 2019-2020 Guillaume Piolat 8 * 9 * Bugs: 10 * - X11 does not support double clicks, it is sometimes emulated https://github.com/glfw/glfw/issues/462 11 * 12 * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 13 * Authors: Richard (Rikki) Andrew Cattermole 14 */ 15 module dplug.window.x11window; 16 17 version(linux): // because of static linking with the X11 library 18 19 import core.atomic; 20 import core.stdc.string; 21 import core.sys.posix.unistd; 22 import core.sys.posix.time; 23 24 import dplug.math.box; 25 26 import derelict.x11.X; 27 import derelict.x11.Xlib; 28 import derelict.x11.keysym; 29 import derelict.x11.keysymdef; 30 import derelict.x11.Xutil; 31 32 import dplug.core.runtime; 33 import dplug.core.nogc; 34 import dplug.core.thread; 35 import dplug.core.sync; 36 import dplug.core.map; 37 38 import dplug.graphics.image; 39 import dplug.window.window; 40 41 nothrow: 42 @nogc: 43 44 //debug = logX11Window; 45 46 debug(logX11Window) 47 { 48 import core.stdc.stdio; 49 } 50 51 52 // This is an extension to X11, almost always should exist on modern systems 53 // If it becomes a problem, version out its usage, it'll work just won't be as nice event wise 54 extern(C) bool XkbSetDetectableAutoRepeat(Display*, bool, bool*); 55 56 final class X11Window : IWindow 57 { 58 nothrow: 59 @nogc: 60 public: 61 62 this(WindowUsage usage, void* parentWindow, IWindowListener listener, int width, int height) 63 { 64 debug(logX11Window) printf(">X11Window.this()\n"); 65 66 // Detect clock 67 { 68 timespec ts; 69 _monotonicClock = false; 70 if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) 71 _monotonicClock = true; 72 } 73 74 _eventMutex = makeMutex(); 75 _dirtyAreaMutex = makeMutex(); 76 _isHostWindow = (usage == WindowUsage.host); 77 78 assert(listener !is null); 79 _listener = listener; 80 81 bool isChildWindow = parentWindow !is null; 82 acquireX11(isChildWindow); 83 84 lockX11(); 85 86 //_parentID = cast(Window)parentWindow; 87 _screen = DefaultScreen(_display); 88 _visual = XDefaultVisual(_display, _screen); 89 90 XkbSetDetectableAutoRepeat(_display, true, null); 91 92 version(VST3) 93 _useXEmbed = true; 94 else 95 _useXEmbed = false; 96 97 createX11Window(parentWindow, width, height); 98 createHiddenCursor(); 99 100 _timeAtCreationInUs = getTimeUs(); 101 _lastMeasturedTimeInUs = _timeAtCreationInUs; 102 _lastClickTimeUs = _timeAtCreationInUs; 103 104 _timerLoop = makeThread(&timerLoopFunc); 105 _timerLoop.start(); 106 _eventLoop = makeThread(&eventLoopFunc); 107 _eventLoop.start(); 108 109 unlockX11(); 110 debug(logX11Window) printf("<X11Window.this()\n"); 111 } 112 113 ~this() 114 { 115 debug(logX11Window) printf(">X11Window.~this()\n"); 116 atomicStore(_terminateThreads, true); 117 118 // Terminate event loop thread 119 _eventLoop.join(); 120 121 // Terminate time thread 122 _timerLoop.join(); 123 124 releaseX11Window(); 125 126 _eventMutex.destroy(); 127 128 releaseX11(); 129 debug(logX11Window) printf("<X11Window.~this()\n"); 130 } 131 132 // <Implements IWindow> 133 override void waitEventAndDispatch() 134 { 135 XEvent event; 136 lockX11(); 137 XWindowEvent(_display, _windowID, windowEventMask(), &event); 138 unlockX11(); 139 processEvent(&event); 140 } 141 142 override bool terminated() 143 { 144 return atomicLoad(_userRequestedTermination); 145 } 146 147 override uint getTimeMs() 148 { 149 // TODO: remove getTimeMs from the IWindow interface. 150 151 static uint perform() 152 { 153 import core.sys.posix.sys.time; 154 timeval tv; 155 gettimeofday(&tv, null); 156 return cast(uint)((tv.tv_sec) * 1000 + (tv.tv_usec) / 1000 ) ; 157 } 158 return assumeNothrowNoGC(&perform)(); 159 } 160 161 // For more precise animation 162 ulong getTimeUs() 163 { 164 static ulong perform(bool monotonic) 165 { 166 import core.sys.posix.sys.time; 167 168 if (monotonic) 169 { 170 timespec ts; 171 clock_gettime(CLOCK_MONOTONIC, &ts); // gives a time in ns 172 return ( ts.tv_sec * cast(ulong)1_000_000 ) + (cast(ulong)ts.tv_nsec) / 1000; 173 } 174 else 175 { 176 timeval tv; 177 gettimeofday(&tv, null); 178 return cast(ulong)(tv.tv_sec) * 1_000_000 + tv.tv_usec; 179 } 180 } 181 return assumeNothrowNoGC(&perform)(_monotonicClock); 182 } 183 184 override void* systemHandle() 185 { 186 return cast(void*)_windowID; 187 } 188 189 override bool requestResize(int widthLogicalPixels, int heightLogicalPixels, bool alsoResizeParentWindow) 190 { 191 assert(!alsoResizeParentWindow); // unsupported here 192 lockX11(); 193 XResizeWindow(_display, _windowID, widthLogicalPixels, heightLogicalPixels); 194 unlockX11(); 195 return true; 196 } 197 198 // </Implements IWindow> 199 200 private: 201 202 enum int BIT_DEPTH = 24; 203 enum int XEMBED_VERSION = 0; 204 enum int XEMBED_MAPPED = (1 << 0); 205 206 /// Usage of this window (host or plugin) 207 bool _isHostWindow; 208 209 /// window listener. 210 IWindowListener _listener = null; 211 212 /// Current width of window. 213 int _width = -1; 214 215 /// Current height of window. 216 int _height = -1; 217 218 /// Framebuffer reference 219 ImageRef!RGBA _wfb; 220 221 /// Because timer events can arrive after the first Expose (first seen in JUCE's AudioPluginHost see Issue #572) 222 bool _recomputeDirtyAreasWasCalled; 223 224 /// Flag to tell threads to terminate. Used in thread finalization only. 225 shared(bool) _terminateThreads = false; 226 227 /// Did the user closed the window? (when used as host window). 228 shared(bool) _userRequestedTermination = false; 229 230 // Time when creating this window, in microseconds. 231 ulong _timeAtCreationInUs; 232 233 /// Last time in microseconds. 234 ulong _lastMeasturedTimeInUs; 235 236 /// Last click time (for double click emulation). 237 ulong _lastClickTimeUs; 238 239 /// This thread pump event. 240 Thread _eventLoop; 241 242 /// This thread acts like a timer. 243 Thread _timerLoop; 244 245 /// Are we using XEmbed? 246 bool _useXEmbed; 247 248 /// Last mouse position. 249 bool _firstMouseMoveAfterEntering = true; 250 int _lastMouseX; 251 int _lastMouseY; 252 253 /// Prevent onAnimate and all other events from going on at the same time, 254 /// notably including onDraw. 255 /// That way, `onAnimate`, `onDraw` and input callbacks are not concurrent, 256 /// like in Windows and macOS. 257 UncheckedMutex _eventMutex; 258 259 /// Prevent recomputeDirtyAreas() and onDraw() to be called simulatneously. 260 /// This is masking a race in dplug:gui. See Dplug issue #572. 261 /// Other window systems (Windows and Mac) also have this kind of shenanigans. 262 UncheckedMutex _dirtyAreaMutex; 263 264 // 265 // <X11 resources> 266 // 267 268 /// X11 ID of this window. 269 Window _windowID; 270 271 /// The default X11 screen of _display. 272 int _screen; 273 274 /// The default Visual of _display. 275 Visual* _visual; 276 277 /// Colormap associated with this window. 278 Colormap _cmap; 279 280 // XEmbed stuff 281 Atom _XEMBED; 282 Atom _XEMBED_INFO; 283 284 Atom _closeAtom; 285 286 /// X11 Graphics Context 287 derelict.x11.Xlib.GC _graphicGC; 288 XImage* _graphicImage; 289 290 // Last MouseCursor used. This is to avoid updating the cursor 291 // more often than necessary 292 // Default value of pointer 293 MouseCursor _lastMouseCursor = MouseCursor.pointer; 294 295 // empty pixmap for creating an invisible cursor 296 Pixmap _bitmapNoData; 297 298 // custom defined cursor that has empty data to appear invisible 299 Cursor _hiddenCursor; 300 301 // If we have access to CLOCK_MONOTONIC. 302 bool _monotonicClock; 303 304 // 305 // </X11 resources> 306 // 307 308 void eventLoopFunc() nothrow @nogc 309 { 310 // Pump events until told to terminate. 311 uint pauseTimeUs = 0; 312 313 while (true) 314 { 315 if (atomicLoad(_terminateThreads)) 316 break; 317 318 XEvent event; 319 lockX11(); 320 Bool found = XCheckWindowEvent(_display, _windowID, windowEventMask(), &event); 321 unlockX11(); 322 if (found == False) 323 { 324 pauseTimeUs = pauseTimeUs * 2 + 1000; // exponential pause 325 if (pauseTimeUs > 100_000) 326 pauseTimeUs = 100_000; // max 100ms of pause } 327 usleep(pauseTimeUs); 328 } 329 else 330 { 331 processEvent(&event); 332 pauseTimeUs = 0; 333 } 334 } 335 } 336 337 void timerLoopFunc() nothrow @nogc 338 { 339 // Send repaints until told to terminate. 340 while(true) 341 { 342 if (atomicLoad(_terminateThreads)) 343 break; 344 345 doAnimation(); 346 sendRepaintIfUIDirty(); 347 setCursor(); 348 349 if (atomicLoad(_terminateThreads)) 350 break; 351 352 // Sleep 1 / 60th of a second 353 enum long durationInNanosecs = 1_000_000_000 / 60; 354 timespec time; 355 timespec rem; 356 time.tv_nsec = durationInNanosecs; 357 time.tv_sec = 0; 358 nanosleep(&time, &rem); 359 } 360 } 361 362 void doAnimation() 363 { 364 ulong now = getTimeUs(); 365 double dt = (now - _lastMeasturedTimeInUs) * 0.000001; 366 double time = (now - _timeAtCreationInUs) * 0.000001; // hopefully no plug-in will be open more than 49 days 367 _lastMeasturedTimeInUs = now; 368 369 _eventMutex.lock(); 370 _listener.onAnimate(dt, time); 371 _eventMutex.unlock(); 372 } 373 374 void setCursor() 375 { 376 version(legacyMouseCursor) 377 {} 378 else 379 { 380 MouseCursor cursor = _listener.getMouseCursor(); 381 382 if(cursor != _lastMouseCursor) 383 { 384 lockX11(); 385 immutable int x11CursorFont = convertCursorToX11CursorFont(cursor); 386 auto c = cursor == MouseCursor.hidden ? _hiddenCursor : XCreateFontCursor(_display, x11CursorFont); 387 XDefineCursor(_display, _windowID, c); 388 unlockX11(); 389 } 390 _lastMouseCursor = cursor; 391 } 392 } 393 394 void processEvent(XEvent* event) 395 { 396 switch(event.type) 397 { 398 case ConfigureNotify: 399 notifySize(event.xconfigure.width, event.xconfigure.height); 400 break; 401 402 case EnterNotify: 403 _firstMouseMoveAfterEntering = true; 404 return; // nothing to do 405 406 case LeaveNotify: 407 _listener.onMouseExitedWindow(); 408 return; 409 410 case Expose: 411 412 // Same thing that under Cocoa and Windows: the first Expose could happen before the timer is called. 413 // See Issue #523 and #572. 414 _dirtyAreaMutex.lock(); 415 if (!_recomputeDirtyAreasWasCalled) 416 { 417 _listener.recomputeDirtyAreas(); 418 _recomputeDirtyAreasWasCalled = true; 419 } 420 _dirtyAreaMutex.unlock(); 421 422 // Draw UI 423 _eventMutex.lock(); 424 _dirtyAreaMutex.lock(); 425 _listener.onDraw(WindowPixelFormat.BGRA8); 426 _dirtyAreaMutex.unlock(); 427 _eventMutex.unlock(); 428 429 XExposeEvent* xpose = cast(XExposeEvent*)event; 430 431 // Find dirty rect from event 432 int x = xpose.x; 433 int y = xpose.y; 434 int width = xpose.width; 435 int height = xpose.height; 436 437 lockX11(); 438 XPutImage(_display, 439 _windowID, 440 _graphicGC, 441 _graphicImage, 442 x, y, // source position 443 x, y, // dest position 444 width, 445 height); 446 unlockX11(); 447 break; 448 449 case ReparentNotify: 450 break; // do nothing 451 452 case KeyPress: 453 KeySym symbol; 454 lockX11(); 455 XLookupString(&event.xkey, null, 0, &symbol, null); 456 unlockX11(); 457 _eventMutex.lock(); 458 _listener.onKeyDown(convertKeyFromX11(symbol)); 459 _eventMutex.unlock(); 460 break; 461 462 case KeyRelease: 463 KeySym symbol; 464 lockX11(); 465 XLookupString(&event.xkey, null, 0, &symbol, null); 466 unlockX11(); 467 _eventMutex.lock(); 468 _listener.onKeyUp(convertKeyFromX11(symbol)); 469 _eventMutex.unlock(); 470 break; 471 472 case MotionNotify: 473 int newMouseX = event.xmotion.x; 474 int newMouseY = event.xmotion.y; 475 476 if (_firstMouseMoveAfterEntering) 477 { 478 _lastMouseX = newMouseX; 479 _lastMouseY = newMouseY; 480 _firstMouseMoveAfterEntering = false; 481 } 482 483 int dx = newMouseX - _lastMouseX; 484 int dy = newMouseY - _lastMouseY; 485 _eventMutex.lock(); 486 _listener.onMouseMove(newMouseX, newMouseY, dx, dy, mouseStateFromX11(event.xbutton.state)); 487 _eventMutex.unlock(); 488 _lastMouseX = newMouseX; 489 _lastMouseY = newMouseY; 490 break; 491 492 case ButtonPress: 493 int newMouseX = event.xbutton.x; 494 int newMouseY = event.xbutton.y; 495 MouseButton button; 496 if (event.xbutton.button == Button1) 497 button = MouseButton.left; 498 else if (event.xbutton.button == Button3) 499 button = MouseButton.right; 500 else if (event.xbutton.button == Button2) 501 button = MouseButton.middle; 502 else if (event.xbutton.button == Button4) 503 button = MouseButton.x1; 504 else if (event.xbutton.button == Button5) 505 button = MouseButton.x2; 506 507 ulong now = getTimeUs(); 508 bool isDoubleClick = now - _lastClickTimeUs <= 500_000; // 500 ms 509 _lastClickTimeUs = now; 510 511 _lastMouseX = newMouseX; 512 _lastMouseY = newMouseY; 513 514 _eventMutex.lock(); 515 if (event.xbutton.button == Button4 || event.xbutton.button == Button5) 516 { 517 _listener.onMouseWheel(newMouseX, newMouseY, 0, event.xbutton.button == Button4 ? 1 : -1, 518 mouseStateFromX11(event.xbutton.state)); 519 } 520 else 521 { 522 _listener.onMouseClick(newMouseX, newMouseY, button, isDoubleClick, mouseStateFromX11(event.xbutton.state)); 523 } 524 _eventMutex.unlock(); 525 break; 526 527 case ButtonRelease: 528 int newMouseX = event.xbutton.x; 529 int newMouseY = event.xbutton.y; 530 531 MouseButton button; 532 533 _lastMouseX = newMouseX; 534 _lastMouseY = newMouseY; 535 536 if (event.xbutton.button == Button1) 537 button = MouseButton.left; 538 else if (event.xbutton.button == Button3) 539 button = MouseButton.right; 540 else if (event.xbutton.button == Button2) 541 button = MouseButton.middle; 542 else if (event.xbutton.button == Button4 || event.xbutton.button == Button5) 543 break; 544 545 _eventMutex.lock(); 546 _listener.onMouseRelease(newMouseX, newMouseY, button, mouseStateFromX11(event.xbutton.state)); 547 _eventMutex.unlock(); 548 break; 549 550 case DestroyNotify: 551 atomicStore(_userRequestedTermination, true); 552 break; 553 554 default: 555 string s = X11EventTypeString(event.type); 556 debug(logX11Window) printf("Unhandled event %d (%s)\n", event.type, s.ptr); 557 } 558 } 559 560 void notifySize(int width, int height) 561 { 562 if (width != _width || height != _height) 563 { 564 _width = width; 565 _height = height; 566 567 _eventMutex.lock(); 568 _dirtyAreaMutex.lock(); 569 _wfb = _listener.onResized(width, height); 570 _dirtyAreaMutex.unlock(); 571 _eventMutex.unlock(); 572 573 // reallocates backbuffer (if any) 574 freeBackbuffer(); 575 576 // and recreates it at the right size 577 assert(_visual); 578 lockX11(); 579 int bytes_per_line = cast(int)(_wfb.pitch); 580 _graphicImage = XCreateImage(_display, 581 _visual, 582 BIT_DEPTH, 583 ZPixmap, 584 0, 585 cast(char*)_wfb.pixels, 586 _width, 587 _height, 588 32, 589 bytes_per_line); 590 unlockX11(); 591 } 592 } 593 594 void sendRepaintIfUIDirty() nothrow @nogc 595 { 596 box2i dirtyRect; 597 _dirtyAreaMutex.lock(); 598 { 599 _listener.recomputeDirtyAreas(); 600 _recomputeDirtyAreasWasCalled = true; 601 dirtyRect = _listener.getDirtyRectangle(); 602 } 603 _dirtyAreaMutex.unlock(); 604 605 if (!dirtyRect.empty()) 606 { 607 XEvent evt; 608 memset(&evt, 0, XEvent.sizeof); 609 evt.type = Expose; 610 evt.xexpose.window = _windowID; 611 evt.xexpose.display = _display; 612 evt.xexpose.x = dirtyRect.min.x; 613 evt.xexpose.y = dirtyRect.min.y; 614 evt.xexpose.width = dirtyRect.width; 615 evt.xexpose.height = dirtyRect.height; 616 lockX11(); 617 XSendEvent(_display, _windowID, False, ExposureMask, &evt); 618 XFlush(_display); 619 unlockX11(); 620 } 621 } 622 623 void createX11Window(void* parentWindow, int width, int height) 624 { 625 // Note: this is already locked by constructor 626 627 // Find the parent Window ID if none provided 628 Window parentWindowID; 629 if (parentWindow is null) 630 parentWindowID = RootWindow(_display, _screen); 631 else 632 parentWindowID = cast(Window)parentWindow; 633 634 _cmap = XCreateColormap(_display, parentWindowID, _visual, AllocNone); 635 636 XSetWindowAttributes attr; 637 memset(&attr, 0, XSetWindowAttributes.sizeof); 638 attr.border_pixel = BlackPixel(_display, _screen); 639 attr.colormap = _cmap; 640 attr.event_mask = windowEventMask(); 641 642 int left = 0; 643 int top = 0; 644 int border_width = 0; 645 _windowID = XCreateWindow(_display, 646 parentWindowID, 647 left, top, 648 width, height, border_width, 649 BIT_DEPTH, 650 InputOutput, 651 _visual, 652 CWBorderPixel | CWColormap | CWEventMask, // TODO we need all this? 653 &attr); 654 655 656 // in case ConfigureNotiy isn't sent 657 // this create t 658 notifySize(width, height); 659 660 // MAYDO: Is it necessary? seems not. 661 // XResizeWindow(_display, _windowID, width, height); 662 663 _closeAtom = XInternAtom(_display, "WM_DELETE_WINDOW", False); 664 XSetWMProtocols(_display, _windowID, &_closeAtom , 1); 665 666 if(_useXEmbed) 667 { 668 //Setup XEMBED atoms 669 _XEMBED = XInternAtom(_display, "_XEMBED", false); 670 _XEMBED_INFO = XInternAtom(_display, "_XEMBED_INFO", false); 671 uint[2] data = [XEMBED_VERSION, XEMBED_MAPPED]; 672 enum XA_CARDINAL = 6; 673 XChangeProperty(_display, _windowID, _XEMBED_INFO, 674 XA_CARDINAL, 32, PropModeReplace, 675 cast(ubyte*) data, 2); 676 677 if(parentWindow) 678 { 679 XReparentWindow(_display, _windowID, parentWindowID, 0, 0); 680 } 681 } 682 683 // MAYDO possible could be XMapWindow I guess 684 XMapRaised(_display, _windowID); 685 XSelectInput(_display, _windowID, windowEventMask()); 686 687 _graphicGC = XCreateGC(_display, _windowID, 0, null); 688 } 689 690 void releaseX11Window() 691 { 692 // release all X11 resource allocated by createX11Window 693 lockX11(); 694 XFreeCursor(_display, _hiddenCursor); 695 XFreePixmap(_display, _bitmapNoData); 696 XFreeColormap(_display, _cmap); 697 XFreeGC(_display, _graphicGC); 698 freeBackbuffer(); 699 XDestroyWindow(_display, _windowID); 700 701 // We need to flush all window events from the queue that are related to this window. 702 // Else another open instance may read message from this window and crash. 703 XSync(_display, false); 704 XEvent event; 705 while(true) 706 { 707 Bool found = XCheckWindowEvent(_display, _windowID, windowEventMask(), &event); 708 if (!found) break; 709 } 710 711 unlockX11(); 712 } 713 714 // this frees _graphicImage, but not the data it points to since it is owned elsewhere 715 void freeBackbuffer() 716 { 717 if (_graphicImage) 718 { 719 lockX11(); 720 XFree(_graphicImage); 721 unlockX11(); 722 _graphicImage = null; 723 } 724 } 725 726 // which X11 events we are interested in 727 728 uint windowEventMask() 729 { 730 return ExposureMask 731 | StructureNotifyMask 732 | KeyReleaseMask 733 | KeyPressMask 734 | ButtonReleaseMask 735 | ButtonPressMask 736 | PointerMotionMask 737 | EnterWindowMask 738 | LeaveWindowMask; 739 } 740 741 void createHiddenCursor() 742 { 743 XColor black; 744 static char[] noData = [0,0,0,0,0,0,0,0]; 745 black.red = black.green = black.blue = 0; 746 747 _bitmapNoData = XCreateBitmapFromData(_display, _windowID, noData.ptr, 8, 8); 748 _hiddenCursor = XCreatePixmapCursor(_display, _bitmapNoData, _bitmapNoData, &black, &black, 0, 0); 749 } 750 } 751 752 private: 753 754 debug(logX11Window) 755 { 756 extern(C) int X11ErrorHandler(Display* display, XErrorEvent* event) 757 { 758 char[128] buf; 759 lockX11(); 760 XGetErrorText(display, event.error_code, buf.ptr, 128); 761 unlockX11(); 762 printf("Error = %s\n", buf.ptr); 763 assert(false); 764 } 765 } 766 767 Key convertKeyFromX11(KeySym symbol) 768 { 769 switch(symbol) 770 { 771 case XK_space: 772 return Key.space; 773 774 case XK_Up: 775 return Key.upArrow; 776 777 case XK_Down: 778 return Key.downArrow; 779 780 case XK_Left: 781 return Key.leftArrow; 782 783 case XK_Right: 784 return Key.rightArrow; 785 786 case XK_0: .. case XK_9: 787 return cast(Key)(Key.digit0 + (symbol - XK_0)); 788 789 case XK_KP_0: .. case XK_KP_9: 790 return cast(Key)(Key.digit0 + (symbol - XK_KP_0)); 791 792 case XK_A: .. case XK_Z: 793 return cast(Key)(Key.A + (symbol - XK_A)); 794 795 case XK_a: .. case XK_z: 796 return cast(Key)(Key.a + (symbol - XK_a)); 797 798 case XK_Return: 799 case XK_KP_Enter: 800 return Key.enter; 801 802 case XK_Escape: 803 return Key.escape; 804 805 case XK_Delete: 806 return Key.suppr; 807 808 case XK_BackSpace: 809 return Key.backspace; 810 811 default: 812 return Key.unsupported; 813 } 814 } 815 816 MouseState mouseStateFromX11(uint state) 817 { 818 return MouseState( 819 (state & Button1Mask) == Button1Mask, 820 (state & Button3Mask) == Button3Mask, 821 (state & Button2Mask) == Button2Mask, 822 false, false, 823 (state & ControlMask) == ControlMask, 824 (state & ShiftMask) == ShiftMask, 825 (state & Mod1Mask) == Mod1Mask); 826 } 827 828 // Check if the X11 operation has completed correctly 829 void checkX11Status(Status err) 830 { 831 if (err == 0) 832 assert(false); // There was an error 833 } 834 835 836 // <X11 initialization> 837 838 shared(int) _x11Counter = 0; 839 __gshared Display* _display; 840 841 /// Protects every X11 call. This is because as a plugin we cannot call XInitThreads() 842 /// to ensure thread safety. 843 /// Note that like the connection, this is shared across plugin instances... 844 __gshared UncheckedMutex _x11Mutex; 845 846 void lockX11() 847 { 848 _x11Mutex.lock(); 849 } 850 851 void unlockX11() 852 { 853 _x11Mutex.unlock(); 854 } 855 856 void acquireX11(bool isAChildwindow) 857 { 858 // Note: this is racey, if you open two plugins exactly at once, it might race 859 if (atomicOp!"+="(_x11Counter, 1) == 1) 860 { 861 _x11Mutex = makeMutex(); 862 debug(logX11Window) 863 { 864 XSetErrorHandler(&X11ErrorHandler); 865 } 866 867 // "On a POSIX-conformant system, if the display_name is NULL, 868 // it defaults to the value of the DISPLAY environment variable." 869 _display = XOpenDisplay(null); 870 871 if(_display == null) 872 assert(false); 873 874 debug(logX11Window) 875 XSynchronize(_display, False); 876 } 877 else 878 { 879 usleep(20); // Dumb protection against X11 initialization race. 880 } 881 } 882 883 void releaseX11() 884 { 885 if (atomicOp!"-="(_x11Counter, 1) == 0) 886 { 887 XCloseDisplay(_display); 888 _x11Mutex.destroy(); 889 } 890 } 891 892 893 string X11EventTypeString(int type) 894 { 895 string s = "Unknown event"; 896 if (type == 2) s = "KeyPress"; 897 if (type == 3) s = "KeyRelease"; 898 if (type == 4) s = "ButtonPress"; 899 if (type == 5) s = "ButtonRelease"; 900 if (type == 6) s = "MotionNotify"; 901 if (type == 7) s = "EnterNotify"; 902 if (type == 8) s = "LeaveNotify"; 903 if (type == 9) s = "FocusIn"; 904 if (type == 10) s = "FocusOut"; 905 if (type == 11) s = "KeymapNotify"; 906 if (type == 12) s = "Expose"; 907 if (type == 13) s = "GraphicsExpose"; 908 if (type == 14) s = "NoExpose"; 909 if (type == 15) s = "VisibilityNotify"; 910 if (type == 16) s = "CreateNotify"; 911 if (type == 17) s = "DestroyNotify"; 912 if (type == 18) s = "UnmapNotify"; 913 if (type == 19) s = "MapNotify"; 914 if (type == 20) s = "MapRequest"; 915 if (type == 21) s = "ReparentNotify"; 916 if (type == 22) s = "ConfigureNotify"; 917 if (type == 23) s = "ConfigureRequest"; 918 if (type == 24) s = "GravityNotify"; 919 if (type == 25) s = "ResizeRequest"; 920 if (type == 26) s = "CirculateNotify"; 921 if (type == 27) s = "CirculateRequest"; 922 if (type == 28) s = "PropertyNotify"; 923 if (type == 29) s = "SelectionClear"; 924 if (type == 30) s = "SelectionRequest"; 925 if (type == 31) s = "SelectionNotify"; 926 if (type == 32) s = "ColormapNotify"; 927 if (type == 33) s = "ClientMessage"; 928 if (type == 34) s = "MappingNotify"; 929 if (type == 35) s = "GenericEvent"; 930 if (type == 36) s = "LASTEvent"; 931 return s; 932 } 933 934 int convertCursorToX11CursorFont(MouseCursor cursor) 935 { 936 switch(cursor) 937 { 938 939 case cursor.linkSelect: 940 return 60; 941 case cursor.drag: 942 return 58; 943 case cursor.move: 944 return 34; 945 case cursor.horizontalResize: 946 return 108; 947 case cursor.verticalResize: 948 return 116; 949 case cursor.diagonalResize: 950 return 14; 951 case cursor.pointer: 952 default: 953 return 2; 954 } 955 }