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