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 }