1 /* 2 Cockos WDL License 3 4 Copyright (C) 2005 - 2015 Cockos Incorporated 5 Copyright (C) 2015 - 2016 Auburn Sounds 6 7 Portions copyright other contributors, see each source file for more information 8 9 This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. 10 11 Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 12 13 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 14 1. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 15 1. This notice may not be removed or altered from any source distribution. 16 */ 17 /** 18 Win32 window implementation. 19 */ 20 module dplug.window.win32window; 21 22 import std.process, 23 std.string, 24 std.conv; 25 26 import dplug.math.vector; 27 import dplug.math.box; 28 29 import dplug.core.runtime; 30 import dplug.core.nogc; 31 import dplug.core.vec; 32 33 import dplug.graphics.image; 34 35 import dplug.window.window; 36 37 // Note: can be annoying while debugging, as it is a global mouse hook. 38 // But the only way to drag outside of window with mouse wheel working. 39 // It is amazing that Windows even allow that. 40 debug 41 { 42 } 43 else 44 { 45 version = hookMouseForWheelEvents; 46 } 47 48 nothrow: 49 @nogc: 50 51 52 // Disabled, remaining issues in #536 53 // - very few hosts actually give the needed DPI 54 // - resizing that window is in some cases a challenge 55 // - using "mixed" DPI support to force Hi-DPI looks like a bad idea 56 // - not always easy to resize the child window 57 // - need testing in Windows 8 or something where some calls are missing 58 enum DPISupportWin32 = false; 59 60 version(Windows) 61 { 62 import std.uuid; 63 import dplug.core.random; 64 65 import core.sys.windows.windef; 66 import core.sys.windows.winuser; 67 import core.sys.windows.winbase; 68 import core.sys.windows.wingdi; 69 70 HINSTANCE getModuleHandle() nothrow @nogc 71 { 72 return GetModuleHandleA(null); 73 } 74 75 final class Win32Window : IWindow 76 { 77 public: 78 nothrow: 79 @nogc: 80 81 this(HWND parentWindow, IWindowListener listener, int width, int height) 82 { 83 _wndClass.style = CS_DBLCLKS | CS_OWNDC | CS_VREDRAW | CS_HREDRAW; 84 85 _wndClass.lpfnWndProc = &windowProcCallback; 86 87 _wndClass.cbClsExtra = 0; 88 _wndClass.cbWndExtra = 0; 89 _wndClass.hInstance = getModuleHandle(); 90 _wndClass.hIcon = null; 91 _wndClass.hCursor = LoadCursor(null, IDC_ARROW); 92 _wndClass.hbrBackground = null; 93 _wndClass.lpszMenuName = null; 94 95 // Generates an unique class name 96 generateClassName(); 97 _wndClass.lpszClassName = _className.ptr; 98 99 if (!RegisterClassW(&_wndClass)) 100 { 101 assert(false, "Couldn't register Win32 class"); 102 } 103 104 DWORD flags = 0; 105 if (parentWindow != null) 106 flags |= WS_CHILD; 107 else 108 parentWindow = GetDesktopWindow(); 109 110 _hwnd = CreateWindowW(_className.ptr, null, flags, CW_USEDEFAULT, CW_USEDEFAULT, 111 width, height, 112 parentWindow, null, 113 getModuleHandle(), 114 cast(void*)this); 115 116 if (_hwnd is null) 117 { 118 assert(false, "Couldn't create a Win32 window"); 119 } 120 121 // Create update region 122 _updateRegion = CreateRectRgn(0, 0, 0, 0); 123 _clipRegion = CreateRectRgn(0, 0, 0, 0); 124 _updateRgbBuf = makeVec!ubyte(); 125 _updateRects = makeVec!box2i(); 126 127 _listener = listener; 128 // Sets this as user data 129 SetWindowLongPtrA(_hwnd, GWLP_USERDATA, cast(LONG_PTR)( cast(void*)this )); 130 131 if (_listener !is null) // we are interested in custom behavior 132 { 133 134 int mSec = 15; // refresh at 60 hz if possible 135 SetTimer(_hwnd, TIMER_ID, mSec, null); 136 } 137 138 // Resize if DPI is supported. 139 if (DPISupportWin32) 140 { 141 _dpiSupport.initialize(); 142 _currentDPI = _dpiSupport.GetDpiForWindow(_hwnd) / 96.0f; 143 //MAYDO resize for DPI 144 //resizeWindowForDPI(); 145 } 146 147 ShowWindow(_hwnd, SW_SHOW); 148 SetFocus(_hwnd); 149 150 // Get performance counter frequency 151 LARGE_INTEGER performanceFrequency; 152 BOOL res = QueryPerformanceFrequency(&performanceFrequency); 153 assert(res != 0); // since XP it is always supported 154 _performanceCounterDivider = performanceFrequency.QuadPart; 155 156 // Get reference time 157 _timeAtCreationInMs = getTimeMs(); 158 _lastMeasturedTimeInMs = _timeAtCreationInMs; 159 160 // Do we need the FLStudio bridge work-around? 161 // Detect if we are under FLStudio's bridge. 162 _useFLStudioBridgeWorkaround = false; 163 HMODULE hmodule = GetModuleHandle(NULL); 164 if (hmodule !is NULL) 165 { 166 char[256] path; 167 int len = GetModuleFileNameA(hmodule, path.ptr, 256); 168 if (len >= 12) 169 { 170 _useFLStudioBridgeWorkaround = path[len - 12 .. len] == "ilbridge.exe"; 171 } 172 } 173 version(hookMouseForWheelEvents) 174 installMouseHook(); 175 } 176 177 ~this() 178 { 179 version(hookMouseForWheelEvents) 180 removeMouseHook(); 181 182 if (_hwnd != null) 183 { 184 DestroyWindow(_hwnd); 185 _hwnd = null; 186 187 // Unregister the window class, which was unique 188 UnregisterClassW(_wndClass.lpszClassName, getModuleHandle()); 189 } 190 191 if (_updateRegion != null) 192 { 193 DeleteObject(_updateRegion); 194 _updateRegion = null; 195 } 196 197 if (_clipRegion != null) 198 { 199 DeleteObject(_clipRegion); 200 _clipRegion = null; 201 } 202 } 203 204 /// Returns: true if window size changed. 205 /// Update internal buffers, if needed. 206 bool updateInternalSize() 207 { 208 RECT winsize; 209 BOOL res = GetClientRect(_hwnd, &winsize); 210 if (res == 0) 211 { 212 assert(false, "GetClientRect failed"); 213 } 214 215 int newWidth = winsize.right - winsize.left; 216 int newHeight = winsize.bottom - winsize.top; 217 218 // only do something if the client size has changed 219 if (newWidth != _width || newHeight != _height) 220 { 221 _width = newWidth; 222 _height = newHeight; 223 224 _sizeSet = true; 225 _wfb = _listener.onResized(_width, _height); 226 return true; 227 } 228 else 229 return false; 230 } 231 232 override bool requestResize(int widthLogicalPixels, int heightLogicalPixels, bool alsoResizeParentWindow) 233 { 234 UINT flags = SWP_NOACTIVATE 235 | SWP_NOZORDER 236 | SWP_NOOWNERZORDER 237 | SWP_NOMOVE 238 | SWP_NOCOPYBITS; // discard entire content of the client area 239 240 RECT formerClient; 241 GetClientRect(_hwnd, &formerClient); 242 243 int dw = widthLogicalPixels - (formerClient.right - formerClient.left); 244 int dh = heightLogicalPixels - (formerClient.bottom - formerClient.top); 245 246 HWND parent = null; 247 RECT parentClient; 248 HWND gparent = null; 249 RECT gparentClient; 250 if (alsoResizeParentWindow) 251 { 252 if (IsChildWindow(_hwnd)) 253 { 254 parent = GetParent(_hwnd); 255 GetClientRect(parent, &parentClient); 256 if (IsChildWindow(parent)) 257 { 258 gparent = GetParent(parent); 259 GetClientRect(gparent, &gparentClient); 260 } 261 } 262 } 263 264 BOOL r = SetWindowPos(_hwnd, null, 265 0, 0, 0 + widthLogicalPixels, 0 + heightLogicalPixels, 266 flags); 267 bool result = r != 0; 268 269 if (parent !is null) 270 { 271 int parentWidth = (parentClient.right - parentClient.left) + dw; 272 int parentHeight = (parentClient.bottom - parentClient.top) + dh; 273 SetWindowPos(parent, null, 274 0, 0, 0 + parentWidth, 0 + parentHeight, 275 SWP_NOZORDER | SWP_NOMOVE | SWP_NOACTIVATE); 276 } 277 if (gparent !is null) 278 { 279 int gparentWidth = (gparentClient.right - gparentClient.left) + dw; 280 int gparentHeight = (gparentClient.bottom - gparentClient.top) + dh; 281 SetWindowPos(gparent, null, 282 0, 0, 0 + gparentWidth, 0 + gparentHeight, 283 SWP_NOZORDER | SWP_NOMOVE | SWP_NOACTIVATE); 284 } 285 return result; 286 } 287 288 LRESULT windowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) 289 { 290 // because DispatchMessage is called by host, we don't know which thread comes here 291 ScopedForeignCallback!(true, true) scopedCallback; 292 scopedCallback.enter(); 293 294 if (_listener is null) 295 return DefWindowProc(hwnd, uMsg, wParam, lParam); 296 297 switch (uMsg) 298 { 299 case WM_KEYDOWN: 300 case WM_KEYUP: 301 { 302 bool handled = false; 303 304 shiftPressed = GetKeyState(VK_SHIFT) < 0; 305 306 Key key = vkToKey(wParam, shiftPressed); 307 if (uMsg == WM_KEYDOWN) 308 { 309 if (_listener.onKeyDown(key)) 310 { 311 handled = true; 312 } 313 } 314 else 315 { 316 if (_listener.onKeyUp(key)) 317 { 318 handled = true; 319 } 320 } 321 322 if (!handled) 323 { 324 // TODO: looks fishy, why GA_ROOT instead of GA_PARENT? 325 // key is passed to the most-parent window 326 HWND rootHWnd = GetAncestor(hwnd, GA_ROOT); 327 if (rootHWnd != hwnd) // send message up the chain if not self (not hosting) 328 { 329 SendMessage(rootHWnd, uMsg, wParam, lParam); 330 return DefWindowProc(hwnd, uMsg, wParam, lParam); 331 } 332 else 333 return DefWindowProc(hwnd, uMsg, wParam, lParam); 334 } 335 else 336 return 0; 337 } 338 339 340 case WM_SETCURSOR: 341 { 342 // In case the cursor was modified by another window, restore the cursor. 343 // This is done differently depending on whether we want a modifed cursor ourselves. 344 // Easily tested in REAPER by hovering over the left border of 345 // the plugin: this turn the custor into a resize arrow. 346 version(legacyMouseCursor) 347 { 348 goto default; 349 } 350 else 351 return setMouseCursor(true); 352 } 353 354 case WM_MOUSEMOVE: 355 { 356 // See Dplug #378, important to sign-extend for multiple monitors 357 int newMouseX = cast(int)(lParam << 16) >> 16; 358 int newMouseY = ( cast(int)lParam ) >> 16; 359 int dx = newMouseX - _mouseX; 360 int dy = newMouseY - _mouseY; 361 _listener.onMouseMove(newMouseX, newMouseY, dx, dy, getMouseState(wParam)); 362 _mouseX = newMouseX; 363 _mouseY = newMouseY; 364 setMouseCursor(false); 365 366 // Track when mouse exit the window 367 if (!_mouseTracking) 368 { 369 // Enable mouse tracking. 370 TRACKMOUSEEVENT tme; 371 tme.cbSize = tme.sizeof; 372 tme.hwndTrack = hwnd; 373 tme.dwFlags = TME_LEAVE; 374 tme.dwHoverTime = HOVER_DEFAULT; 375 TrackMouseEvent(&tme); 376 _mouseTracking = true; 377 } 378 379 return 0; 380 } 381 382 case WM_SETFOCUS: 383 { 384 return 0; 385 } 386 387 case WM_KILLFOCUS: 388 { 389 return 0; 390 } 391 392 case WM_MOUSEWHEEL: 393 { 394 // See Dplug #378, important to sign-extend for multiple monitors 395 int mouseX = cast(int)(lParam << 16) >> 16; 396 int mouseY = ( cast(int)lParam ) >> 16; 397 398 // Mouse positions we are getting are not relative to the client area 399 RECT r; 400 GetWindowRect(hwnd, &r); 401 mouseX -= r.left; 402 mouseY -= r.top; 403 404 int wheelDeltaY = cast(short)((wParam & 0xffff0000) >> 16) / WHEEL_DELTA; 405 406 if (_listener.onMouseWheel(mouseX, mouseY, 0, wheelDeltaY, getMouseState(wParam))) 407 { 408 return 0; // handled 409 } 410 goto default; 411 } 412 413 case WM_RBUTTONDOWN: 414 case WM_RBUTTONDBLCLK: 415 { 416 if (mouseClick(_mouseX, _mouseY, MouseButton.right, uMsg == WM_RBUTTONDBLCLK, wParam)) 417 return 0; // handled 418 goto default; 419 } 420 421 case WM_LBUTTONDOWN: 422 case WM_LBUTTONDBLCLK: 423 { 424 if (mouseClick(_mouseX, _mouseY, MouseButton.left, uMsg == WM_LBUTTONDBLCLK, wParam)) 425 return 0; // handled 426 goto default; 427 } 428 429 case WM_MBUTTONDOWN: 430 case WM_MBUTTONDBLCLK: 431 { 432 if (mouseClick(_mouseX, _mouseY, MouseButton.middle, uMsg == WM_MBUTTONDBLCLK, wParam)) 433 return 0; // handled 434 goto default; 435 } 436 437 // X1/X2 buttons 438 case WM_XBUTTONDOWN: 439 case WM_XBUTTONDBLCLK: 440 { 441 auto mb = (wParam >> 16) == 1 ? MouseButton.x1 : MouseButton.x2; 442 if (mouseClick(_mouseX, _mouseY, mb, uMsg == WM_XBUTTONDBLCLK, wParam)) 443 return 0; 444 goto default; 445 } 446 447 case WM_RBUTTONUP: 448 if (mouseRelease(_mouseX, _mouseY, MouseButton.right, wParam)) 449 return 0; 450 goto default; 451 452 case WM_LBUTTONUP: 453 if (mouseRelease(_mouseX, _mouseY, MouseButton.left, wParam)) 454 return 0; 455 goto default; 456 case WM_MBUTTONUP: 457 if (mouseRelease(_mouseX, _mouseY, MouseButton.middle, wParam)) 458 return 0; 459 goto default; 460 461 case WM_XBUTTONUP: 462 { 463 auto mb = (wParam >> 16) == 1 ? MouseButton.x1 : MouseButton.x2; 464 if (mouseRelease(_mouseX, _mouseY, mb, wParam)) 465 return 0; 466 goto default; 467 } 468 469 case WM_CAPTURECHANGED: 470 _listener.onMouseCaptureCancelled(); 471 setMouseCursor(true); 472 goto default; 473 474 case WM_MOUSELEAVE: 475 _listener.onMouseExitedWindow(); 476 _mouseTracking = false; 477 return 0; 478 479 case WM_SIZE: 480 case WM_SHOWWINDOW: 481 { 482 // This get the size of the client area, and then tells the listener about the new size. 483 updateInternalSize(); 484 goto default; 485 } 486 487 case WM_PAINT: 488 { 489 // Internal size must have been set. 490 assert(_sizeSet); 491 492 // Same thing that under Cocoa and Linux, the first WM_PAINT could happen before WM_TIMER is ever called. 493 // In this case, call `recomputeDirtyAreas()` so that onDraw draw something and uninitialized pixels are not displayed. 494 // See Issue #523 and #572. 495 if (_dirtyAreasAreNotYetComputed) 496 { 497 _dirtyAreasAreNotYetComputed = false; 498 _listener.recomputeDirtyAreas(); 499 } 500 501 // Renders UI. 502 // FUTURE: This could be done in a separate thread? 503 // For efficiency purpose, render in BGRA for Windows 504 // We do it here, but note that redrawing has nothing to do with WM_PAINT specifically, 505 // we just need to wait for it here. 506 _listener.onDraw(WindowPixelFormat.BGRA8); 507 508 // Get the update region 509 int type = GetUpdateRgn(hwnd, _updateRegion, FALSE); 510 assert (type != ERROR); 511 512 // Begin painting 513 PAINTSTRUCT paintStruct; 514 HDC hdc = BeginPaint(_hwnd, &paintStruct); 515 516 HRGN regionToUpdate = _updateRegion; 517 518 // FLStudio compatibility 519 // Try to get the DC's clipping region, which may be larger in the case of FLStudio's bridge. 520 if (_useFLStudioBridgeWorkaround) 521 { 522 if ( GetClipRgn(hdc, _clipRegion) == 1) 523 regionToUpdate = _clipRegion; 524 } 525 526 // Get needed number of bytes 527 DWORD bytes = GetRegionData(regionToUpdate, 0, null); 528 _updateRgbBuf.resize(bytes); 529 530 if (bytes == GetRegionData(regionToUpdate, bytes, cast(RGNDATA*)(_updateRgbBuf.ptr))) 531 { 532 // Get rectangles to update visually from the update region 533 ubyte* buf = _updateRgbBuf.ptr; 534 RGNDATAHEADER* header = cast(RGNDATAHEADER*)buf; 535 assert(header.iType == RDH_RECTANGLES); 536 _updateRects.clearContents(); 537 RECT* pRect = cast(RECT*)(buf + RGNDATAHEADER.sizeof); 538 539 alias wfb = _wfb; 540 for (int r = 0; r < header.nCount; ++r) 541 { 542 int left = pRect[r].left; 543 int top = pRect[r].top; 544 int right = pRect[r].right; 545 int bottom = pRect[r].bottom; 546 _updateRects.pushBack(box2i(left, top, right, bottom)); 547 } 548 549 static int byteStride(int width) pure nothrow @nogc @safe 550 { 551 enum scanLineAlignment = 4; 552 int widthInBytes = width * 4; 553 return (widthInBytes + (scanLineAlignment - 1)) & ~(scanLineAlignment-1); 554 } 555 // Note: the pitch of the displayed image _has_ to follow `byteStride`. 556 // SetDIBitsToDevice doesn't seem to take arbitrary pitch 557 assert(wfb.pitch == byteStride(wfb.w)); 558 559 BITMAPINFOHEADER bmi = BITMAPINFOHEADER.init; // fill with zeroes 560 with (bmi) 561 { 562 biSize = BITMAPINFOHEADER.sizeof; 563 biWidth = wfb.w; 564 biHeight = -wfb.h; 565 biPlanes = 1; 566 biCompression = BI_RGB; 567 biXPelsPerMeter = 72; 568 biYPelsPerMeter = 72; 569 biBitCount = 32; 570 biSizeImage = cast(int)(wfb.pitch) * wfb.h; 571 } 572 573 foreach(box2i area; _updateRects) 574 { 575 if (area.width() <= 0 || area.height() <= 0) 576 continue; // nothing to update 577 578 SetDIBitsToDevice(hdc, area.min.x, area.min.y, area.width, area.height, 579 area.min.x, -area.min.y - area.height + wfb.h, 580 0, wfb.h, wfb.pixels, cast(BITMAPINFO *)&bmi, DIB_RGB_COLORS); 581 } 582 } 583 else 584 assert(false); 585 586 EndPaint(_hwnd, &paintStruct); 587 return 0; 588 } 589 590 case WM_ERASEBKGND: 591 { 592 // This fails, so cause this window's WM_PAINT to be responsible for erasing background, 593 // hence saving a bit of performance. 594 return 1; 595 } 596 597 case WM_CLOSE: 598 { 599 this.destroyNoGC(); 600 return 0; 601 } 602 603 case WM_TIMER: 604 { 605 if (wParam == TIMER_ID) 606 { 607 uint now = getTimeMs(); 608 double dt = (now - _lastMeasturedTimeInMs) * 0.001; 609 double time = (now - _timeAtCreationInMs) * 0.001; // hopefully no plug-in will be open more than 49 days 610 _lastMeasturedTimeInMs = now; 611 _listener.onAnimate(dt, time); 612 613 _listener.recomputeDirtyAreas(); 614 _dirtyAreasAreNotYetComputed = false; 615 box2i dirtyRect = _listener.getDirtyRectangle(); 616 if (!dirtyRect.empty()) 617 { 618 RECT r = RECT(dirtyRect.min.x, dirtyRect.min.y, dirtyRect.max.x, dirtyRect.max.y); 619 InvalidateRect(_hwnd, &r, FALSE); // FUTURE: invalidate rects one by one 620 621 // See issue #432 and #269 622 // To avoid blocking WM_TIMER with expensive WM_PAINT, it's important NOT to enqueue manually a 623 // WM_PAINT here. Let Windows do its job of sending WM_PAINT when needed. 624 } 625 } 626 return 0; 627 } 628 629 default: 630 return DefWindowProcA(hwnd, uMsg, wParam, lParam); 631 } 632 } 633 634 // Implements IWindow 635 override void waitEventAndDispatch() 636 { 637 MSG msg; 638 int ret = GetMessageW(&msg, _hwnd, 0, 0); // no range filtering 639 if (ret == -1) 640 assert(false, "Error while in GetMessage"); 641 TranslateMessage(&msg); 642 DispatchMessageW(&msg); 643 } 644 645 override bool terminated() 646 { 647 return _terminated; 648 } 649 650 override uint getTimeMs() 651 { 652 LARGE_INTEGER perfCounter; 653 BOOL err = QueryPerformanceCounter(&perfCounter); 654 assert(err != 0); // always supported since XP 655 double time = (perfCounter.QuadPart * 1000 + (_performanceCounterDivider >> 1)) / cast(double)_performanceCounterDivider; 656 return cast(uint)(time); 657 } 658 659 override void* systemHandle() 660 { 661 return cast(void*)( cast(size_t)_hwnd ); 662 } 663 664 private: 665 enum TIMER_ID = 144; 666 667 HWND _hwnd; 668 669 WNDCLASSW _wndClass; 670 671 HRGN _updateRegion; 672 HRGN _clipRegion; 673 bool _useFLStudioBridgeWorkaround; 674 675 Vec!ubyte _updateRgbBuf; 676 Vec!box2i _updateRects; 677 678 long _performanceCounterDivider; 679 uint _timeAtCreationInMs; 680 uint _lastMeasturedTimeInMs; 681 682 bool _sizeSet = false; 683 IWindowListener _listener; // contract: _listener must only be used in the message callback 684 685 ImageRef!RGBA _wfb; // framebuffer reference 686 687 bool _terminated = false; 688 int _width = 0; 689 int _height = 0; 690 691 int _mouseX = 0; 692 int _mouseY = 0; 693 bool _mouseTracking = false; 694 695 bool shiftPressed = false; 696 bool _dirtyAreasAreNotYetComputed = true; 697 698 // Helper for getting DPI information. 699 DPIHelper _dpiSupport; 700 701 // Current UI scale, 1.0f means default DPI (96). 702 // See_also: https://www.enlyze.com/blog/writing-win32-apps-like-its-2020/part-3/ 703 float _currentDPI = 1.0f; 704 705 // Last MouseCursor used. This is to avoid updating the cursor 706 // more often than necessary 707 // Default value of pointer 708 MouseCursor _lastMouseCursor = MouseCursor.pointer; 709 710 /// Propagates mouse events. 711 /// Returns: true if event handled. 712 bool mouseClick(int mouseX, int mouseY, MouseButton mb, bool isDoubleClick, WPARAM wParam) 713 { 714 SetFocus(_hwnd); // get keyboard focus 715 716 // Start mouse capture, if not already captures (See Issue #694) 717 if (GetCapture() != _hwnd) 718 SetCapture(_hwnd); 719 720 bool consumed = _listener.onMouseClick(mouseX, mouseY, mb, isDoubleClick, getMouseState(wParam)); 721 return consumed; 722 } 723 724 /// ditto 725 bool mouseRelease(int mouseX, int mouseY, MouseButton mb, WPARAM wParam) 726 { 727 // Note: the first button release from mouse interrupts the dragging operation. 728 // This need not be the case, but it makes things easier. 729 // Not sure how cross platform we are on that specific point. 730 if (GetCapture() == _hwnd) 731 ReleaseCapture(); 732 733 bool consumed = _listener.onMouseRelease(mouseX, mouseY, mb, getMouseState(wParam)); 734 return consumed; 735 } 736 737 wchar[43] _className; // Zero-terminated class name 738 739 void generateClassName() nothrow @nogc 740 { 741 generateNullTerminatedRandomUUID!wchar(_className, "dplug_"w); 742 } 743 744 int setMouseCursor(bool forceUpdate) 745 { 746 version(legacyMouseCursor) 747 {} 748 else 749 { 750 MouseCursor cursor = _listener.getMouseCursor(); 751 752 if((cursor != _lastMouseCursor) || forceUpdate ) 753 { 754 CURSORINFO pci; 755 pci.cbSize = CURSORINFO.sizeof; 756 GetCursorInfo(&pci); 757 758 // If the cursor we want to display is "hidden" and the cursor is being shown 759 // then we will hide the cursor. 760 // If the cursor we want to display is anything other than "hidden" and the 761 // cursor is being hidden already, we will set it to show 762 // (this triggers a WM_SETCURSOR which will call this to set the cursor) 763 // lastly if the above conditions are false then we will set the cursor 764 if(cursor == MouseCursor.hidden && pci.flags == CURSOR_SHOWING) 765 { 766 ShowCursor(false); 767 } 768 else if(cursor != MouseCursor.hidden && pci.flags == 0) 769 { 770 ShowCursor(true); 771 } 772 else 773 { 774 auto cursorId = mouseCursorToCursorId(cursor); 775 HCURSOR hc = LoadCursorA(NULL, cast(const(char)*)cursorId); 776 SetCursor(hc); 777 } 778 _lastMouseCursor = cursor; 779 return 1; 780 } 781 } 782 783 return 0; 784 } 785 786 version(hookMouseForWheelEvents) 787 { 788 789 // This prop says that this is a Dplug window, that has hooked the mouse. 790 // useful to identiy things when you just have a HWND. 791 // having does NOT mean that you can cast to Win32Window or anything, just that 792 // you expect the mouse wheel events to come to you in a drag outside your bounds. 793 enum string HOOK_PROPERTY = "i-am-a-dplug-window-and-have-hooked-the-mouse"; 794 795 // true if installed 796 bool installMouseHook() 797 { 798 _mouseHook = SetWindowsHookExA(WH_MOUSE_LL, &LowLevelMouseProc, null, 0); 799 800 if (_mouseHook) 801 { 802 BOOL res = SetPropA(_hwnd, HOOK_PROPERTY.ptr, _mouseHook); 803 if (res == 0) 804 { 805 removeMouseHook(); 806 return false; 807 } 808 } 809 return (_mouseHook !is null); 810 } 811 812 // true if removed 813 void removeMouseHook() 814 { 815 // Remove property, if any 816 if (null != GetPropA(_hwnd, HOOK_PROPERTY.ptr)) 817 { 818 RemovePropA(_hwnd, HOOK_PROPERTY.ptr); 819 } 820 821 // Note: nothing prevent the hook to be currently called there. 822 if (_mouseHook) 823 { 824 UnhookWindowsHookEx(_mouseHook); 825 _mouseHook = null; 826 } 827 } 828 829 HHOOK _mouseHook = null; 830 } 831 } 832 833 834 extern(Windows) nothrow 835 { 836 LRESULT windowProcCallback(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) 837 { 838 Win32Window window = cast(Win32Window)( cast(void*)(GetWindowLongPtrA(hwnd, GWLP_USERDATA)) ); 839 if (window !is null) 840 return window.windowProc(hwnd, uMsg, wParam, lParam); 841 else 842 return DefWindowProcA(hwnd, uMsg, wParam, lParam); 843 } 844 } 845 846 Key vkToKey(WPARAM vk, bool shiftPressed)pure nothrow @nogc 847 { 848 switch (vk) 849 { 850 case VK_SPACE: return Key.space; 851 852 case VK_UP: return Key.upArrow; 853 case VK_DOWN: return Key.downArrow; 854 case VK_LEFT: return Key.leftArrow; 855 case VK_RIGHT: return Key.rightArrow; 856 857 case VK_NUMPAD0: return Key.digit0; 858 case VK_NUMPAD1: return Key.digit1; 859 case VK_NUMPAD2: return Key.digit2; 860 case VK_NUMPAD3: return Key.digit3; 861 case VK_NUMPAD4: return Key.digit4; 862 case VK_NUMPAD5: return Key.digit5; 863 case VK_NUMPAD6: return Key.digit6; 864 case VK_NUMPAD7: return Key.digit7; 865 case VK_NUMPAD8: return Key.digit8; 866 case VK_NUMPAD9: return Key.digit9; 867 case 0x30: return Key.digit0; 868 case 0x31: return Key.digit1; 869 case 0x32: return Key.digit2; 870 case 0x33: return Key.digit3; 871 case 0x34: return Key.digit4; 872 case 0x35: return Key.digit5; 873 case 0x36: return Key.digit6; 874 case 0x37: return Key.digit7; 875 case 0x38: return Key.digit8; 876 case 0x39: return Key.digit9; 877 case 0x41: return shiftPressed ? Key.A : Key.a; 878 case 0x42: return shiftPressed ? Key.B : Key.b; 879 case 0x43: return shiftPressed ? Key.C : Key.c; 880 case 0x44: return shiftPressed ? Key.D : Key.d; 881 case 0x45: return shiftPressed ? Key.E : Key.e; 882 case 0x46: return shiftPressed ? Key.F : Key.f; 883 case 0x47: return shiftPressed ? Key.G : Key.g; 884 case 0x48: return shiftPressed ? Key.H : Key.h; 885 case 0x49: return shiftPressed ? Key.I : Key.i; 886 case 0x4A: return shiftPressed ? Key.J : Key.j; 887 case 0x4B: return shiftPressed ? Key.K : Key.k; 888 case 0x4C: return shiftPressed ? Key.L : Key.l; 889 case 0x4D: return shiftPressed ? Key.M : Key.m; 890 case 0x4E: return shiftPressed ? Key.N : Key.n; 891 case 0x4F: return shiftPressed ? Key.O : Key.o; 892 case 0x50: return shiftPressed ? Key.P : Key.p; 893 case 0x51: return shiftPressed ? Key.Q : Key.q; 894 case 0x52: return shiftPressed ? Key.R : Key.r; 895 case 0x53: return shiftPressed ? Key.S : Key.s; 896 case 0x54: return shiftPressed ? Key.T : Key.t; 897 case 0x55: return shiftPressed ? Key.U : Key.u; 898 case 0x56: return shiftPressed ? Key.V : Key.v; 899 case 0x57: return shiftPressed ? Key.W : Key.w; 900 case 0x58: return shiftPressed ? Key.X : Key.x; 901 case 0x59: return shiftPressed ? Key.Y : Key.y; 902 case 0x5A: return shiftPressed ? Key.Z : Key.z; 903 case VK_BACK: return Key.backspace; 904 case VK_RETURN: return Key.enter; 905 case VK_DELETE: return Key.suppr; 906 case VK_ESCAPE: return Key.escape; 907 default: return Key.unsupported; 908 } 909 } 910 911 SHORT keyState(int vk) 912 { 913 version(AAX) 914 return GetAsyncKeyState(VK_MENU); 915 else 916 return GetKeyState(VK_MENU); 917 } 918 919 static MouseState getMouseState(WPARAM wParam) 920 { 921 return MouseState( (wParam & MK_LBUTTON) != 0, 922 (wParam & MK_RBUTTON) != 0, 923 (wParam & MK_MBUTTON) != 0, 924 (wParam & MK_XBUTTON1) != 0, 925 (wParam & MK_XBUTTON2) != 0, 926 (wParam & MK_CONTROL) != 0, 927 (wParam & MK_SHIFT) != 0, 928 keyState(VK_MENU) < 0 ); 929 } 930 931 HCURSOR mouseCursorToCursorId(MouseCursor cursor) 932 { 933 switch(cursor) 934 { 935 case cursor.linkSelect: 936 return IDC_HAND; 937 938 case cursor.drag: 939 return IDC_CROSS; 940 941 case cursor.move: 942 return IDC_HAND; 943 944 case cursor.horizontalResize: 945 return IDC_SIZEWE; 946 947 case cursor.verticalResize: 948 return IDC_SIZENS; 949 950 case cursor.diagonalResize: 951 return IDC_SIZENWSE; 952 953 case cursor.pointer: 954 955 default: 956 return IDC_ARROW; 957 } 958 } 959 960 961 // Copyright 2013 The Flutter Authors. All rights reserved. 962 // 963 // Redistribution and use in source and binary forms, with or without modification, 964 // are permitted provided that the following conditions are met: 965 // 966 // * Redistributions of source code must retain the above copyright 967 // notice, this list of conditions and the following disclaimer. 968 // * Redistributions in binary form must reproduce the above 969 // copyright notice, this list of conditions and the following 970 // disclaimer in the documentation and/or other materials provided 971 // with the distribution. 972 // * Neither the name of Google Inc. nor the names of its 973 // contributors may be used to endorse or promote products derived 974 // from this software without specific prior written permission. 975 // 976 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 977 // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 978 // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 979 // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 980 // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 981 // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 982 // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 983 // ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 984 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 985 // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 986 987 /// A helper class for abstracting various Windows DPI related functions across 988 /// Windows OS versions. 989 private struct DPIHelper 990 { 991 public: 992 nothrow: 993 @nogc: 994 void initialize() 995 { 996 if ((user32_module_ = LoadLibraryA("User32.dll")) != null) 997 { 998 get_dpi_for_window_ = cast(GetDpiForWindow_t) GetProcAddress(user32_module_, "GetDpiForWindow".ptr); 999 dpi_for_window_supported_ = get_dpi_for_window_ !is null; 1000 1001 set_thread_dpi_awareness_context = cast(SetThreadDpiAwarenessContext_t) GetProcAddress(user32_module_, "SetThreadDpiAwarenessContext".ptr); 1002 set_thread_dpi_hosting_behavior = cast(SetThreadDpiHostingBehavior_t) GetProcAddress(user32_module_, "SetThreadDpiHostingBehavior".ptr); 1003 } 1004 if ((shlib_module_ = LoadLibraryA("Shcore.dll")) != null) 1005 { 1006 get_dpi_for_monitor_ = cast(GetDpiForMonitor_t) GetProcAddress(shlib_module_, "GetDpiForMonitor".ptr); 1007 dpi_for_monitor_supported_ = get_dpi_for_monitor_ !is null; 1008 } 1009 } 1010 1011 ~this() 1012 { 1013 if (user32_module_ != null) 1014 { 1015 FreeLibrary(user32_module_); 1016 user32_module_ = null; 1017 } 1018 if (shlib_module_ != null) 1019 { 1020 FreeLibrary(shlib_module_); 1021 shlib_module_ = null; 1022 } 1023 } 1024 1025 private: 1026 extern(Windows) nothrow @nogc 1027 { 1028 alias GetDpiForWindow_t = UINT function(HWND); 1029 alias GetDpiForMonitor_t = HRESULT function(HMONITOR hmonitor, 1030 UINT dpiType, 1031 UINT* dpiX, 1032 UINT* dpiY); 1033 alias EnableNonClientDpiScaling_t = BOOL function(HWND); 1034 1035 alias SetThreadDpiAwarenessContext_t = DPI_AWARENESS_CONTEXT function(DPI_AWARENESS_CONTEXT); 1036 alias SetThreadDpiHostingBehavior_t = DPI_HOSTING_BEHAVIOR function(DPI_HOSTING_BEHAVIOR); 1037 } 1038 1039 GetDpiForWindow_t get_dpi_for_window_; 1040 GetDpiForMonitor_t get_dpi_for_monitor_; 1041 1042 SetThreadDpiAwarenessContext_t set_thread_dpi_awareness_context; 1043 SetThreadDpiHostingBehavior_t set_thread_dpi_hosting_behavior; 1044 1045 EnableNonClientDpiScaling_t enable_non_client_dpi_scaling_; 1046 HMODULE user32_module_ = null; 1047 HMODULE shlib_module_ = null; 1048 bool dpi_for_window_supported_ = false; 1049 bool dpi_for_monitor_supported_ = false; 1050 1051 1052 /// Returns the DPI for |hwnd|. Supports all DPI awareness modes, and is 1053 /// backward compatible down to Windows Vista. If |hwnd| is nullptr, returns 1054 /// the DPI for the primary monitor. If Per-Monitor DPI awareness is not 1055 /// available, returns the system's DPI. 1056 UINT GetDpiForWindow(HWND hwnd) 1057 { 1058 // GetDpiForWindow returns the DPI for any awareness mode. If not available, 1059 // or no |hwnd| is provided, fallback to a per monitor, system, or default 1060 // DPI. 1061 if (dpi_for_window_supported_ && hwnd != null) { 1062 return get_dpi_for_window_(hwnd); 1063 } 1064 1065 if (dpi_for_monitor_supported_) 1066 { 1067 HMONITOR monitor = null; 1068 if (hwnd != null) { 1069 monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY); 1070 } 1071 return GetDpiForMonitor(monitor); 1072 } 1073 HDC hdc = GetDC(hwnd); 1074 UINT dpi = GetDeviceCaps(hdc, LOGPIXELSX); 1075 ReleaseDC(hwnd, hdc); 1076 return dpi; 1077 } 1078 1079 enum kDefaultDpi = 96; 1080 1081 /// Returns the DPI of a given monitor. Defaults to 96 if the API is not 1082 /// available. 1083 UINT GetDpiForMonitor(HMONITOR monitor) 1084 { 1085 if (dpi_for_monitor_supported_) 1086 { 1087 if (monitor == null) 1088 { 1089 POINT target_point; 1090 target_point.x = 0; 1091 target_point.y = 0; 1092 monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTOPRIMARY); 1093 } 1094 UINT dpi_x = 0, 1095 dpi_y = 0; 1096 1097 HRESULT result = get_dpi_for_monitor_(monitor, 0 /* kEffectiveDpiMonitorType */, &dpi_x, &dpi_y); 1098 if (SUCCEEDED(result)) 1099 { 1100 return dpi_x; 1101 } 1102 } 1103 return kDefaultDpi; 1104 } 1105 1106 DPI_AWARENESS_CONTEXT SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT context) 1107 { 1108 // Do not set DPI to unsupported if asked so. 1109 if (context == DPI_AWARENESS_CONTEXT_UNSUPPORTED) 1110 return context; 1111 1112 if (set_thread_dpi_awareness_context !is null) 1113 { 1114 return set_thread_dpi_awareness_context(context); 1115 } 1116 else 1117 return DPI_AWARENESS_CONTEXT_UNSUPPORTED; 1118 } 1119 1120 DPI_HOSTING_BEHAVIOR SetThreadDpiHostingBehavior(DPI_HOSTING_BEHAVIOR value) 1121 { 1122 if (value == DPI_HOSTING_UNSUPPORTED) 1123 return value; 1124 1125 if (set_thread_dpi_hosting_behavior !is null) 1126 return set_thread_dpi_hosting_behavior(value); 1127 else 1128 return value; // do nothing but pretend it went OK 1129 } 1130 } 1131 1132 alias DPI_AWARENESS_CONTEXT = void*; 1133 enum : DPI_AWARENESS_CONTEXT 1134 { 1135 DPI_AWARENESS_CONTEXT_UNSUPPORTED = cast(void*)0, // This API not supported 1136 DPI_AWARENESS_CONTEXT_UNAWARE = cast(void*)(-1), 1137 DPI_AWARENESS_CONTEXT_SYSTEM_AWARE = cast(void*)(-2), 1138 DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE = cast(void*)(-3), 1139 DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = cast(void*)(-4), 1140 DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED = cast(void*)(-5) // since Windows 10 1141 } 1142 1143 alias DPI_HOSTING_BEHAVIOR = int; 1144 enum : DPI_HOSTING_BEHAVIOR 1145 { 1146 DPI_HOSTING_UNSUPPORTED = -2, 1147 DPI_HOSTING_BEHAVIOR_INVALID = -1, 1148 DPI_HOSTING_BEHAVIOR_DEFAULT = 0, 1149 DPI_HOSTING_BEHAVIOR_MIXED = 1 1150 } 1151 1152 static bool IsChildWindow(HWND pWnd) 1153 { 1154 if (pWnd) 1155 { 1156 int style = GetWindowLong(pWnd, GWL_STYLE); 1157 int exStyle = GetWindowLong(pWnd, GWL_EXSTYLE); 1158 return ((style & WS_CHILD) && !(exStyle & WS_EX_MDICHILD)); 1159 } 1160 return false; 1161 } 1162 1163 1164 version(hookMouseForWheelEvents) 1165 { 1166 extern(Windows) LRESULT LowLevelMouseProc(int nCode, WPARAM wParam, LPARAM lParam) 1167 { 1168 bool processed = false; 1169 1170 if (nCode == 0) 1171 { 1172 if (wParam == WM_MOUSEWHEEL) // this is about a global mouse wheel event 1173 { 1174 // What window has the mouse capture, if any? 1175 HWND hwnd = GetCapture(); 1176 1177 if (hwnd != null) 1178 { 1179 // We need to make sure this is destined to a Dplug-window. 1180 // It is one such window, one that can hook the mouse. Send it the mouse wheel event. 1181 // Indeed, casting to Win32Window would not be completely safe. 1182 // Actual mouse data is there. 1183 if (null != GetPropA(hwnd, Win32Window.HOOK_PROPERTY.ptr)) 1184 { 1185 MSLLHOOKSTRUCT* hookData = cast(MSLLHOOKSTRUCT*) lParam; 1186 DWORD lParamMsg = cast(short)(hookData.pt.x) | (cast(short)(hookData.pt.y) << 16); 1187 1188 BOOL res = PostMessageA(hwnd, 1189 WM_MOUSEWHEEL, 1190 hookData.mouseData & 0xffff0000, // Note: no support for horizontal mouse wheel 1191 lParamMsg); 1192 processed = (res != 0); // posted = processed 1193 } 1194 } 1195 } 1196 } 1197 1198 if (!processed) 1199 return CallNextHookEx(null, nCode, wParam, lParam); // never consume the message 1200 else 1201 { 1202 /// If the hook procedure processed the message, it may return a nonzero value to 1203 /// prevent the system from passing the message to the rest of the hook chain or 1204 /// the target window procedure. 1205 return 1; 1206 } 1207 } 1208 } 1209 }