diff --git a/modules/juce_gui_basics/juce_gui_basics.h b/modules/juce_gui_basics/juce_gui_basics.h index e577c6c4e1..d7ad1ae9d1 100644 --- a/modules/juce_gui_basics/juce_gui_basics.h +++ b/modules/juce_gui_basics/juce_gui_basics.h @@ -111,6 +111,13 @@ #define JUCE_USE_XCURSOR 1 #endif +/** Config: JUCE_WIN_PER_MONITOR_DPI_AWARE + Enables per-monitor DPI awareness on Windows 8.1 and above. +*/ +#ifndef JUCE_WIN_PER_MONITOR_DPI_AWARE + #define JUCE_WIN_PER_MONITOR_DPI_AWARE 1 +#endif + //============================================================================== namespace juce { diff --git a/modules/juce_gui_basics/native/juce_win32_Windowing.cpp b/modules/juce_gui_basics/native/juce_win32_Windowing.cpp index b771c60561..236fcd13f3 100644 --- a/modules/juce_gui_basics/native/juce_win32_Windowing.cpp +++ b/modules/juce_gui_basics/native/juce_win32_Windowing.cpp @@ -28,6 +28,10 @@ #include #endif +#if JUCE_WIN_PER_MONITOR_DPI_AWARE && JUCE_MODULE_AVAILABLE_juce_gui_extra + #include +#endif + namespace juce { @@ -189,21 +193,21 @@ extern void* getUser32Function (const char*); struct POINTER_INFO { - POINTER_INPUT_TYPE pointerType; - UINT32 pointerId; - UINT32 frameId; - POINTER_FLAGS pointerFlags; - HANDLE sourceDevice; - HWND hwndTarget; - POINT ptPixelLocation; - POINT ptHimetricLocation; - POINT ptPixelLocationRaw; - POINT ptHimetricLocationRaw; - DWORD dwTime; - UINT32 historyCount; - INT32 InputData; - DWORD dwKeyStates; - UINT64 PerformanceCount; + POINTER_INPUT_TYPE pointerType; + UINT32 pointerId; + UINT32 frameId; + POINTER_FLAGS pointerFlags; + HANDLE sourceDevice; + HWND hwndTarget; + POINT ptPixelLocation; + POINT ptHimetricLocation; + POINT ptPixelLocationRaw; + POINT ptHimetricLocationRaw; + DWORD dwTime; + UINT32 historyCount; + INT32 InputData; + DWORD dwKeyStates; + UINT64 PerformanceCount; POINTER_BUTTON_CHANGE_TYPE ButtonChangeType; }; @@ -233,37 +237,45 @@ extern void* getUser32Function (const char*); #endif #ifndef MONITOR_DPI_TYPE - enum Monitor_DPI_Type - { - MDT_Effective_DPI = 0, - MDT_Angular_DPI = 1, - MDT_Raw_DPI = 2, - MDT_Default = MDT_Effective_DPI - }; - - enum Process_DPI_Awareness - { - Process_DPI_Unaware = 0, - Process_System_DPI_Aware = 1, - Process_Per_Monitor_DPI_Aware = 2 - }; + enum Monitor_DPI_Type + { + MDT_Effective_DPI = 0, + MDT_Angular_DPI = 1, + MDT_Raw_DPI = 2, + MDT_Default = MDT_Effective_DPI + }; + + enum Process_DPI_Awareness + { + Process_DPI_Unaware = 0, + Process_System_DPI_Aware = 1, + Process_Per_Monitor_DPI_Aware = 2 + }; +#endif + +#ifndef USER_DEFAULT_SCREEN_DPI + #define USER_DEFAULT_SCREEN_DPI 96 #endif -typedef BOOL (WINAPI* RegisterTouchWindowFunc) (HWND, ULONG); -typedef BOOL (WINAPI* GetTouchInputInfoFunc) (HTOUCHINPUT, UINT, TOUCHINPUT*, int); +#ifndef _DPI_AWARENESS_CONTEXTS_ + typedef HANDLE DPI_AWARENESS_CONTEXT; + + #define DPI_AWARENESS_CONTEXT_UNAWARE ((DPI_AWARENESS_CONTEXT) - 1) + #define DPI_AWARENESS_CONTEXT_SYSTEM_AWARE ((DPI_AWARENESS_CONTEXT) - 2) + #define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE ((DPI_AWARENESS_CONTEXT) - 3) + #define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 ((DPI_AWARENESS_CONTEXT) - 4) +#endif + +//============================================================================== +typedef BOOL (WINAPI* RegisterTouchWindowFunc) (HWND, ULONG); +typedef BOOL (WINAPI* GetTouchInputInfoFunc) (HTOUCHINPUT, UINT, TOUCHINPUT*, int); typedef BOOL (WINAPI* CloseTouchInputHandleFunc) (HTOUCHINPUT); -typedef BOOL (WINAPI* GetGestureInfoFunc) (HGESTUREINFO, GESTUREINFO*); -typedef BOOL (WINAPI* SetProcessDPIAwareFunc)(); -typedef BOOL (WINAPI* SetProcessDPIAwarenessFunc) (Process_DPI_Awareness); -typedef HRESULT (WINAPI* GetDPIForMonitorFunc) (HMONITOR, Monitor_DPI_Type, UINT*, UINT*); - -static RegisterTouchWindowFunc registerTouchWindow = nullptr; -static GetTouchInputInfoFunc getTouchInputInfo = nullptr; -static CloseTouchInputHandleFunc closeTouchInputHandle = nullptr; -static GetGestureInfoFunc getGestureInfo = nullptr; -static SetProcessDPIAwareFunc setProcessDPIAware = nullptr; -static SetProcessDPIAwarenessFunc setProcessDPIAwareness = nullptr; -static GetDPIForMonitorFunc getDPIForMonitor = nullptr; +typedef BOOL (WINAPI* GetGestureInfoFunc) (HGESTUREINFO, GESTUREINFO*); + +static RegisterTouchWindowFunc registerTouchWindow = nullptr; +static GetTouchInputInfoFunc getTouchInputInfo = nullptr; +static CloseTouchInputHandleFunc closeTouchInputHandle = nullptr; +static GetGestureInfoFunc getGestureInfo = nullptr; static bool hasCheckedForMultiTouch = false; @@ -282,105 +294,289 @@ static bool canUseMultiTouch() return registerTouchWindow != nullptr; } -typedef BOOL (WINAPI* GetPointerTypeFunc) (UINT32, POINTER_INPUT_TYPE*); +//============================================================================== +typedef BOOL (WINAPI* GetPointerTypeFunc) (UINT32, POINTER_INPUT_TYPE*); typedef BOOL (WINAPI* GetPointerTouchInfoFunc) (UINT32, POINTER_TOUCH_INFO*); -typedef BOOL (WINAPI* GetPointerPenInfoFunc) (UINT32, POINTER_PEN_INFO*); +typedef BOOL (WINAPI* GetPointerPenInfoFunc) (UINT32, POINTER_PEN_INFO*); static GetPointerTypeFunc getPointerTypeFunction = nullptr; -static GetPointerTouchInfoFunc getPointerTouchInfo = nullptr; -static GetPointerPenInfoFunc getPointerPenInfo = nullptr; +static GetPointerTouchInfoFunc getPointerTouchInfo = nullptr; +static GetPointerPenInfoFunc getPointerPenInfo = nullptr; static bool canUsePointerAPI = false; static void checkForPointerAPI() { - getPointerTypeFunction = (GetPointerTypeFunc) getUser32Function ("GetPointerType"); + getPointerTypeFunction = (GetPointerTypeFunc) getUser32Function ("GetPointerType"); getPointerTouchInfo = (GetPointerTouchInfoFunc) getUser32Function ("GetPointerTouchInfo"); - getPointerPenInfo = (GetPointerPenInfoFunc) getUser32Function ("GetPointerPenInfo"); + getPointerPenInfo = (GetPointerPenInfoFunc) getUser32Function ("GetPointerPenInfo"); canUsePointerAPI = (getPointerTypeFunction != nullptr && getPointerTouchInfo != nullptr && getPointerPenInfo != nullptr); } +//============================================================================== +typedef BOOL (WINAPI* SetProcessDPIAwareFunc) (); +typedef BOOL (WINAPI* SetProcessDPIAwarenessContextFunc) (DPI_AWARENESS_CONTEXT); +typedef BOOL (WINAPI* SetProcessDPIAwarenessFunc) (Process_DPI_Awareness); +typedef DPI_AWARENESS_CONTEXT (WINAPI* SetThreadDPIAwarenessContextFunc) (DPI_AWARENESS_CONTEXT); +typedef HRESULT (WINAPI* GetDPIForMonitorFunc) (HMONITOR, Monitor_DPI_Type, UINT*, UINT*); +typedef UINT (WINAPI* GetDPIForWindowFunc) (HWND); +typedef HRESULT (WINAPI* GetProcessDPIAwarenessFunc) (HANDLE, Process_DPI_Awareness*); +typedef DPI_AWARENESS_CONTEXT (WINAPI* GetWindowDPIAwarenessContextFunc) (HWND); +typedef DPI_AWARENESS_CONTEXT (WINAPI* GetThreadDPIAwarenessContextFunc) (); +typedef Process_DPI_Awareness (WINAPI* GetAwarenessFromDpiAwarenessContextFunc) (DPI_AWARENESS_CONTEXT); +typedef BOOL (WINAPI* EnableNonClientDPIScalingFunc) (HWND); + +static SetProcessDPIAwareFunc setProcessDPIAware = nullptr; +static SetProcessDPIAwarenessContextFunc setProcessDPIAwarenessContext = nullptr; +static SetProcessDPIAwarenessFunc setProcessDPIAwareness = nullptr; +static SetThreadDPIAwarenessContextFunc setThreadDPIAwarenessContext = nullptr; +static GetDPIForMonitorFunc getDPIForMonitor = nullptr; +static GetDPIForWindowFunc getDPIForWindow = nullptr; +static GetProcessDPIAwarenessFunc getProcessDPIAwareness = nullptr; +static GetWindowDPIAwarenessContextFunc getWindowDPIAwarenessContext = nullptr; +static GetThreadDPIAwarenessContextFunc getThreadDPIAwarenessContext = nullptr; +static GetAwarenessFromDpiAwarenessContextFunc getAwarenessFromDPIAwarenessContext = nullptr; +static EnableNonClientDPIScalingFunc enableNonClientDPIScaling = nullptr; + +static bool hasCheckedForDPIAwareness = false; + +static void setDPIAwareness() +{ + if (hasCheckedForDPIAwareness) + return; + + hasCheckedForDPIAwareness = true; + + #if ! JUCE_WIN_PER_MONITOR_DPI_AWARE + if (! JUCEApplicationBase::isStandaloneApp()) + return; + #endif + + HMODULE shcoreModule = GetModuleHandleA ("SHCore.dll"); + + if (shcoreModule != 0) + { + getDPIForMonitor = (GetDPIForMonitorFunc) GetProcAddress (shcoreModule, "GetDpiForMonitor"); + + #if JUCE_WIN_PER_MONITOR_DPI_AWARE + getDPIForWindow = (GetDPIForWindowFunc) getUser32Function ("GetDpiForWindow"); + getProcessDPIAwareness = (GetProcessDPIAwarenessFunc) GetProcAddress (shcoreModule, "GetProcessDpiAwareness"); + getWindowDPIAwarenessContext = (GetWindowDPIAwarenessContextFunc) getUser32Function ("GetWindowDpiAwarenessContext"); + getThreadDPIAwarenessContext = (GetThreadDPIAwarenessContextFunc) getUser32Function ("GetThreadDpiAwarenessContext"); + getAwarenessFromDPIAwarenessContext = (GetAwarenessFromDpiAwarenessContextFunc) getUser32Function ("GetAwarenessFromDpiAwarenessContext"); + setThreadDPIAwarenessContext = (SetThreadDPIAwarenessContextFunc)getUser32Function ("SetThreadDpiAwarenessContext"); + + // Only set the DPI awareness context of the process if we are a standalone app + if (! JUCEApplicationBase::isStandaloneApp()) + return; + + if (getDPIForWindow != nullptr && getProcessDPIAwareness != nullptr) + { + setProcessDPIAwarenessContext = (SetProcessDPIAwarenessContextFunc) getUser32Function ("SetProcessDpiAwarenessContext"); + + if (setProcessDPIAwarenessContext != nullptr + && SUCCEEDED (setProcessDPIAwarenessContext (DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2))) + return; + + setProcessDPIAwareness = (SetProcessDPIAwarenessFunc) GetProcAddress (shcoreModule, "SetProcessDpiAwareness"); + enableNonClientDPIScaling = (EnableNonClientDPIScalingFunc) getUser32Function ("EnableNonClientDpiScaling"); + + if (setProcessDPIAwareness != nullptr && enableNonClientDPIScaling != nullptr + && SUCCEEDED (setProcessDPIAwareness (Process_Per_Monitor_DPI_Aware))) + return; + } + #endif + + if (setProcessDPIAwareness == nullptr) + setProcessDPIAwareness = (SetProcessDPIAwarenessFunc) GetProcAddress (shcoreModule, "SetProcessDpiAwareness"); + + if (setProcessDPIAwareness != nullptr && getDPIForMonitor != nullptr + && SUCCEEDED (setProcessDPIAwareness (Process_System_DPI_Aware))) + return; + } + + // fallback for pre Windows 8.1 - equivalent to Process_System_DPI_Aware + setProcessDPIAware = (SetProcessDPIAwareFunc) getUser32Function ("SetProcessDPIAware"); + + if (setProcessDPIAware != nullptr && JUCEApplicationBase::isStandaloneApp()) + setProcessDPIAware(); +} + +#if JUCE_WIN_PER_MONITOR_DPI_AWARE && JUCE_MODULE_AVAILABLE_juce_gui_extra + ScopedDPIAwarenessDisabler::ScopedDPIAwarenessDisabler() + { + jassert (setThreadDPIAwarenessContext != nullptr); + + previousContext = setThreadDPIAwarenessContext (DPI_AWARENESS_CONTEXT_UNAWARE); + } + + ScopedDPIAwarenessDisabler::~ScopedDPIAwarenessDisabler() + { + jassert (previousContext != nullptr); + + setThreadDPIAwarenessContext ((DPI_AWARENESS_CONTEXT) previousContext); + } +#endif + +static bool isPerMonitorDPIAwareWindow (HWND h) +{ + #if JUCE_WIN_PER_MONITOR_DPI_AWARE + jassert (h != nullptr); + + setDPIAwareness(); + + if (getWindowDPIAwarenessContext != nullptr && getAwarenessFromDPIAwarenessContext != nullptr) + return getAwarenessFromDPIAwarenessContext (getWindowDPIAwarenessContext (h)) == Process_DPI_Awareness::Process_Per_Monitor_DPI_Aware; + + return false; + #else + ignoreUnused (h); + return false; + #endif +} + +#if JUCE_WIN_PER_MONITOR_DPI_AWARE + static bool isPerMonitorDPIAwareThread() + { + setDPIAwareness(); + + if (getThreadDPIAwarenessContext != nullptr && getAwarenessFromDPIAwarenessContext != nullptr) + return getAwarenessFromDPIAwarenessContext (getThreadDPIAwarenessContext()) == Process_DPI_Awareness::Process_Per_Monitor_DPI_Aware; + + return false; + } +#endif + +static double getGlobalDPI() +{ + setDPIAwareness(); + + HDC dc = GetDC (0); + auto dpi = (GetDeviceCaps (dc, LOGPIXELSX) + GetDeviceCaps (dc, LOGPIXELSY)) / 2.0; + ReleaseDC (0, dc); + return dpi; +} + +//============================================================================== typedef void (*SettingChangeCallbackFunc) (void); extern SettingChangeCallbackFunc settingChangeCallback; -static Rectangle rectangleFromRECT (const RECT& r) noexcept +//============================================================================== +static Rectangle rectangleFromRECT (const RECT& r) noexcept { return { r.left, r.top, r.right - r.left, r.bottom - r.top }; } +static RECT RECTFromRectangle (const Rectangle& r) noexcept { return { r.getX(), r.getY(), r.getRight(), r.getBottom() }; } + +static Point pointFromPOINT (const POINT& p) noexcept { return { p.x, p.y }; } +static POINT POINTFromPoint (const Point& p) noexcept { return { p.x, p.y }; } + +//============================================================================== +static Rectangle convertPhysicalScreenRectangleToLogical (const Rectangle& r, HWND h) noexcept { - return Rectangle::leftTopRightBottom ((int) r.left, (int) r.top, (int) r.right, (int) r.bottom); + if (isPerMonitorDPIAwareWindow (h)) + return Desktop::getInstance().getDisplays().physicalToLogical (r); + + return r; } -static void setWindowPos (HWND hwnd, Rectangle bounds, UINT flags) +static Rectangle convertLogicalScreenRectangleToPhysical (const Rectangle& r, HWND h) noexcept { - SetWindowPos (hwnd, 0, bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), flags); + if (isPerMonitorDPIAwareWindow (h)) + return Desktop::getInstance().getDisplays().logicalToPhysical (r); + + return r; } -static RECT getWindowRect (HWND hwnd) +static Point convertPhysicalScreenPointToLogical (const Point& p, HWND h) noexcept { - RECT r; - GetWindowRect (hwnd, &r); - return r; + if (isPerMonitorDPIAwareWindow (h)) + return Desktop::getInstance().getDisplays().physicalToLogical (p); + + return p; } -static void setWindowZOrder (HWND hwnd, HWND insertAfter) +static Point convertLogicalScreenPointToPhysical (const Point& p, HWND h) noexcept { - SetWindowPos (hwnd, insertAfter, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOSENDCHANGING); + if (isPerMonitorDPIAwareWindow (h)) + return Desktop::getInstance().getDisplays().logicalToPhysical (p); + + return p; +} + +static double getScaleFactorForWindow (HWND h) +{ + if (isPerMonitorDPIAwareWindow (h) && getDPIForWindow != nullptr) + return (double) getDPIForWindow (h) / USER_DEFAULT_SCREEN_DPI; + + return 1.0; } //============================================================================== -static void setDPIAwareness() +static void setWindowPos (HWND hwnd, Rectangle bounds, UINT flags) { - #if ! JUCE_DISABLE_WIN32_DPI_AWARENESS - if (JUCEApplicationBase::isStandaloneApp()) + if (isPerMonitorDPIAwareWindow (hwnd)) { - if (setProcessDPIAwareness == nullptr) - { - HMODULE shcoreModule = GetModuleHandleA ("SHCore.dll"); + if (auto* parent = GetParent (hwnd)) + bounds = (bounds.toDouble() * getScaleFactorForWindow (parent)).toNearestInt(); + else + bounds = Desktop::getInstance().getDisplays().logicalToPhysical (bounds); + } - if (shcoreModule != 0) - { - setProcessDPIAwareness = (SetProcessDPIAwarenessFunc) GetProcAddress (shcoreModule, "SetProcessDpiAwareness"); - getDPIForMonitor = (GetDPIForMonitorFunc) GetProcAddress (shcoreModule, "GetDpiForMonitor"); + SetWindowPos (hwnd, 0, bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), flags); +} - if (setProcessDPIAwareness != nullptr && getDPIForMonitor != nullptr -// && SUCCEEDED (setProcessDPIAwareness (Process_Per_Monitor_DPI_Aware))) - && SUCCEEDED (setProcessDPIAwareness (Process_System_DPI_Aware))) // (keep using this mode temporarily..) - return; - } +static RECT getWindowRect (HWND hwnd) +{ + RECT r; + GetWindowRect (hwnd, &r); - if (setProcessDPIAware == nullptr) - { - setProcessDPIAware = (SetProcessDPIAwareFunc) getUser32Function ("SetProcessDPIAware"); + #if JUCE_WIN_PER_MONITOR_DPI_AWARE + auto windowDPIAware = isPerMonitorDPIAwareWindow (hwnd); + auto threadDPIAware = isPerMonitorDPIAwareThread(); - if (setProcessDPIAware != nullptr) - setProcessDPIAware(); - } + // If these don't match then we need to convert the RECT returned from GetWindowRect() as it depends + // on the DPI awareness of the calling thread + if (windowDPIAware != threadDPIAware) + { + if (! windowDPIAware) + { + // Thread is per-monitor DPI aware so RECT needs to be converted from physical to logical for + // the DPI unaware window + return RECTFromRectangle (Desktop::getInstance().getDisplays().physicalToLogical (rectangleFromRECT (r))); + } + else if (! threadDPIAware) + { + // Thread is DPI unaware so RECT needs to be converted from logical to physical for the per-monitor + // DPI aware window + return RECTFromRectangle (Desktop::getInstance().getDisplays().logicalToPhysical (rectangleFromRECT (r))); } } #endif + + return r; } -static double getGlobalDPI() +static void setWindowZOrder (HWND hwnd, HWND insertAfter) { - setDPIAwareness(); - - HDC dc = GetDC (0); - const double dpi = (GetDeviceCaps (dc, LOGPIXELSX) - + GetDeviceCaps (dc, LOGPIXELSY)) / 2.0; - ReleaseDC (0, dc); - return dpi; + SetWindowPos (hwnd, insertAfter, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOSENDCHANGING); } +//============================================================================== double Desktop::getDefaultMasterScale() { - return JUCEApplicationBase::isStandaloneApp() ? getGlobalDPI() / 96.0 - : 1.0; + #if JUCE_WIN_PER_MONITOR_DPI_AWARE + return 1.0; + #else + if (! JUCEApplicationBase::isStandaloneApp()) + return 1.0; + + return getGlobalDPI() / USER_DEFAULT_SCREEN_DPI; + #endif } -bool Desktop::canUseSemiTransparentWindows() noexcept { return true; } +bool Desktop::canUseSemiTransparentWindows() noexcept { return true; } -//============================================================================== Desktop::DisplayOrientation Desktop::getCurrentOrientation() const { return upright; @@ -999,15 +1195,7 @@ public: callFunctionIfNotLocked (&createWindowCallback, this); setTitle (component.getName()); - - if ((windowStyleFlags & windowHasDropShadow) != 0 - && ((! hasTitleBar()) || SystemStats::getOperatingSystemType() < SystemStats::WinVista)) - { - shadower.reset (component.getLookAndFeel().createDropShadowerForComponent (&component)); - - if (shadower != nullptr) - shadower->setOwner (&component); - } + updateShadower(); // make sure that the on-screen keyboard code is loaded OnScreenKeyboard::getInstance(); @@ -1086,10 +1274,10 @@ public: info.cbSize = sizeof (info); if (GetWindowInfo (hwnd, &info)) - windowBorder = BorderSize (info.rcClient.top - info.rcWindow.top, - info.rcClient.left - info.rcWindow.left, - info.rcWindow.bottom - info.rcClient.bottom, - info.rcWindow.right - info.rcClient.right); + windowBorder = BorderSize (roundToInt ((info.rcClient.top - info.rcWindow.top) / scaleFactor), + roundToInt ((info.rcClient.left - info.rcWindow.left) / scaleFactor), + roundToInt ((info.rcWindow.bottom - info.rcClient.bottom) / scaleFactor), + roundToInt ((info.rcWindow.right - info.rcClient.right) / scaleFactor)); #if JUCE_DIRECT2D if (direct2DContext != nullptr) @@ -1097,6 +1285,17 @@ public: #endif } + double getScaleFactorForNewBounds (const Rectangle& newBounds) + { + if (auto* parent = GetParent (hwnd)) + return Desktop::getInstance().getDisplays().findDisplayForRect (convertPhysicalScreenRectangleToLogical (rectangleFromRECT (getWindowRect (parent)), hwnd)).scale; + + if (! Desktop::getInstance().getDisplays().getTotalBounds (false).contains (newBounds)) + return scaleFactor; + + return Desktop::getInstance().getDisplays().findDisplayForRect (newBounds).scale; + } + void setBounds (const Rectangle& bounds, bool isNowFullScreen) override { fullScreen = isNowFullScreen; @@ -1107,12 +1306,13 @@ public: { if (auto parentHwnd = GetParent (hwnd)) { - auto parentRect = getWindowRect (parentHwnd); - newBounds.translate (parentRect.left, parentRect.top); + auto parentRect = convertPhysicalScreenRectangleToLogical (rectangleFromRECT (getWindowRect (parentHwnd)), hwnd); + newBounds.translate (parentRect.getX(), parentRect.getY()); } } auto oldBounds = getBounds(); + const bool hasMoved = (oldBounds.getPosition() != bounds.getPosition()); const bool hasResized = (oldBounds.getWidth() != bounds.getWidth() || oldBounds.getHeight() != bounds.getHeight()); @@ -1121,6 +1321,18 @@ public: if (! hasMoved) flags |= SWP_NOMOVE; if (! hasResized) flags |= SWP_NOSIZE; + if (isPerMonitorDPIAwareWindow (hwnd)) + { + auto newScaleFactor = getScaleFactorForNewBounds (newBounds); + + if (! approximatelyEqual (scaleFactor, newScaleFactor)) + { + scaleFactor = newScaleFactor; + flags &= ~SWP_NOSIZE; // ensure that the window size will be updated + handleScaleFactorChange(); + } + } + setWindowPos (hwnd, newBounds, flags); if (hasResized && isValidPeer (this)) @@ -1132,23 +1344,29 @@ public: Rectangle getBounds() const override { - auto bounds = rectangleFromRECT (getWindowRect (hwnd)); + auto bounds = getWindowRect (hwnd); if (auto parentH = GetParent (hwnd)) { auto r = getWindowRect (parentH); - bounds.translate (-r.left, -r.top); + auto localBounds = Rectangle::leftTopRightBottom (bounds.left, bounds.top, + bounds.right, bounds.bottom).translated (-r.left, -r.top); + + if (isPerMonitorDPIAwareWindow (hwnd)) + localBounds = (localBounds.toDouble() / getScaleFactorForWindow (hwnd)).toNearestInt(); + + return windowBorder.subtractedFrom (localBounds); } - return windowBorder.subtractedFrom (bounds); + return windowBorder.subtractedFrom (convertPhysicalScreenRectangleToLogical (rectangleFromRECT (bounds), hwnd)); } Point getScreenPosition() const { - auto r = getWindowRect (hwnd); + auto r = convertPhysicalScreenRectangleToLogical (rectangleFromRECT (getWindowRect (hwnd)), hwnd); - return { r.left + windowBorder.getLeft(), - r.top + windowBorder.getTop() }; + return { r.getX() + windowBorder.getLeft(), + r.getY() + windowBorder.getTop() }; } Point localToGlobal (Point relativePosition) override { return relativePosition + getScreenPosition().toFloat(); } @@ -1213,7 +1431,7 @@ public: ShowWindow (hwnd, SW_SHOWNORMAL); if (! boundsCopy.isEmpty()) - setBounds (ScalingHelpers::scaledScreenPosToUnscaled (component, boundsCopy), false); + setBounds (boundsCopy, false); } else { @@ -1245,16 +1463,14 @@ public: bool contains (Point localPos, bool trueIfInAChildWindow) const override { - auto r = getWindowRect (hwnd); + auto r = convertPhysicalScreenRectangleToLogical (rectangleFromRECT (getWindowRect (hwnd)), hwnd); - if (! (isPositiveAndBelow (localPos.x, r.right - r.left) - && isPositiveAndBelow (localPos.y, r.bottom - r.top))) + if (! r.withZeroOrigin().contains (localPos)) return false; - POINT p = { localPos.x + r.left + windowBorder.getLeft(), - localPos.y + r.top + windowBorder.getTop() }; + auto w = WindowFromPoint (POINTFromPoint (convertLogicalScreenPointToPhysical ({ localPos.x + r.getX() + windowBorder.getLeft(), + localPos.y + r.getY() + windowBorder.getTop() }, hwnd))); - HWND w = WindowFromPoint (p); return w == hwnd || (trueIfInAChildWindow && (IsChild (hwnd, w) != 0)); } @@ -1355,7 +1571,18 @@ public: void repaint (const Rectangle& area) override { - const RECT r = { area.getX(), area.getY(), area.getRight(), area.getBottom() }; + auto scale = getPlatformScaleFactor(); + + #if JUCE_WIN_PER_MONITOR_DPI_AWARE + // if the calling thread is DPI-aware but we are invalidating a non-DPI aware window RECT, we actually have to + // divide the bounds by the scale factor as it will get multiplied for the virtualised paint callback... + if (isPerMonitorDPIAwareThread() && ! isPerMonitorDPIAwareWindow (hwnd)) + scale = 1.0 / Desktop::getInstance().getDisplays().getMainDisplay().scale; + #endif + + const RECT r = { roundToInt (area.getX() * scale), roundToInt (area.getY() * scale), + roundToInt (area.getRight() * scale), roundToInt (area.getBottom() * scale) }; + InvalidateRect (hwnd, &r, FALSE); } @@ -1475,9 +1702,8 @@ public: Point getMousePos (POINTL mousePos) const { auto& comp = peer.getComponent(); - return comp.getLocalPoint (nullptr, ScalingHelpers::unscaledScreenPosToScaled (comp.getDesktopScaleFactor(), - Point (static_cast (mousePos.x), - static_cast (mousePos.y)))); + return comp.getLocalPoint (nullptr, convertPhysicalScreenPointToLogical (pointFromPOINT ({ mousePos.x, mousePos.y }), + (HWND) peer.getNativeHandle()).toFloat()); } template @@ -1574,6 +1800,23 @@ public: return false; } + + double getPlatformScaleFactor() const noexcept override + { + if (! isPerMonitorDPIAwareWindow (hwnd)) + return 1.0; + + if (auto* parentHWND = GetParent (hwnd)) + { + if (auto* parentPeer = getOwnerOfWindow (parentHWND)) + return parentPeer->getPlatformScaleFactor(); + + return (double) getDPIForWindow (parentHWND) / USER_DEFAULT_SCREEN_DPI; + } + + return scaleFactor; + } + private: HWND hwnd, parentToAddTo; std::unique_ptr shadower; @@ -1594,6 +1837,8 @@ private: ModifierKeyProvider* modProvider = nullptr; #endif + double scaleFactor = 1.0; + static MultiTouchMapper currentTouches; //============================================================================== @@ -1827,6 +2072,12 @@ private: registerTouchWindow (hwnd, 0); setDPIAwareness(); + + #if JUCE_WIN_PER_MONITOR_DPI_AWARE + if (isPerMonitorDPIAwareThread()) + scaleFactor = Desktop::getInstance().getDisplays().getMainDisplay().scale; + #endif + setMessageFilter(); updateBorderSize(); checkForPointerAPI(); @@ -1887,6 +2138,17 @@ private: bool hasTitleBar() const noexcept { return (styleFlags & windowHasTitleBar) != 0; } + void updateShadower() + { + if (! component.isCurrentlyModal() && (styleFlags & windowHasDropShadow) != 0 + && ((! hasTitleBar()) || SystemStats::getOperatingSystemType() < SystemStats::WinVista)) + { + shadower.reset (component.getLookAndFeel().createDropShadowerForComponent (&component)); + + if (shadower != nullptr) + shadower->setOwner (&component); + } + } void setIcon (const Image& newIcon) { @@ -1964,7 +2226,7 @@ private: if (GetUpdateRect (hwnd, &r, false)) { direct2DContext->start(); - direct2DContext->clipToRectangle (rectangleFromRECT (r)); + direct2DContext->clipToRectangle (convertPhysicalScreenRectangleToLogical (rectangleFromRECT (r), hwnd)); handlePaint (*direct2DContext); direct2DContext->end(); ValidateRect (hwnd, &r); @@ -2094,6 +2356,8 @@ private: { std::unique_ptr context (component.getLookAndFeel() .createGraphicsContext (offscreenImage, Point (-x, -y), contextClip)); + + context->addTransform (AffineTransform::scale ((float) getPlatformScaleFactor())); handlePaint (*context); } @@ -2252,10 +2516,10 @@ private: { updateModifiersFromWParam (wParam); - #if JUCE_MODULE_AVAILABLE_juce_audio_plugin_client + #if JUCE_MODULE_AVAILABLE_juce_audio_plugin_client if (modProvider != nullptr) ModifierKeys::currentModifiers = ModifierKeys::currentModifiers.withFlags (modProvider->getWin32Modifiers()); - #endif + #endif isDragging = true; @@ -2299,7 +2563,7 @@ private: constrainerIsResizing = false; } - if (isDragging) + if (ModifierKeys::currentModifiers.isAnyMouseButtonDown()) doMouseUp (getCurrentMousePos(), (WPARAM) 0); } @@ -2313,17 +2577,16 @@ private: ComponentPeer* findPeerUnderMouse (Point& localPos) { - auto globalPos = getCurrentMousePosGlobal().roundToInt(); + auto currentMousePos = getPOINTFromLParam (GetMessagePos()); // Because Windows stupidly sends all wheel events to the window with the keyboard // focus, we have to redirect them here according to the mouse pos.. - POINT p = { globalPos.x, globalPos.y }; - auto* peer = getOwnerOfWindow (WindowFromPoint (p)); + auto* peer = getOwnerOfWindow (WindowFromPoint (currentMousePos)); if (peer == nullptr) peer = this; - localPos = peer->globalToLocal (globalPos.toFloat()); + localPos = peer->globalToLocal (convertPhysicalScreenPointToLogical (pointFromPOINT (currentMousePos), hwnd).toFloat()); return peer; } @@ -2432,7 +2695,8 @@ private: const auto touchIndex = currentTouches.getIndexOfTouch (this, touch.dwID); const auto time = getMouseEventTime(); - const auto pos = globalToLocal ({ touch.x / 100.0f, touch.y / 100.0f }); + const auto pos = globalToLocal (convertPhysicalScreenPointToLogical (pointFromPOINT ({ roundToInt (touch.x / 100.0f), + roundToInt (touch.y / 100.0f) }), hwnd).toFloat()); const auto pressure = touchPressure; auto modsToSend = ModifierKeys::currentModifiers; @@ -2516,8 +2780,7 @@ private: const auto pressure = (penInfo.penMask & PEN_MASK_PRESSURE) ? penInfo.pressure / 1024.0f : MouseInputSource::invalidPressure; - if (! handlePenInput (penInfo, globalToLocal (Point (static_cast (GET_X_LPARAM(lParam)), - static_cast (GET_Y_LPARAM(lParam)))), + if (! handlePenInput (penInfo, globalToLocal (convertPhysicalScreenPointToLogical (pointFromPOINT (getPOINTFromLParam (lParam)), hwnd).toFloat()), pressure, isDown, isUp)) return false; } @@ -2531,11 +2794,18 @@ private: TOUCHINPUT emulateTouchEventFromPointer (LPARAM lParam, WPARAM wParam) { + Point p (GET_X_LPARAM (lParam), GET_Y_LPARAM (lParam)); + + #if JUCE_WIN_PER_MONITOR_DPI_AWARE + if (! isPerMonitorDPIAwareThread()) + p = Desktop::getInstance().getDisplays().physicalToLogical (p); + #endif + TOUCHINPUT touchInput; touchInput.dwID = GET_POINTERID_WPARAM (wParam); - touchInput.x = GET_X_LPARAM (lParam) * 100; - touchInput.y = GET_Y_LPARAM (lParam) * 100; + touchInput.x = p.x * 100; + touchInput.y = p.y * 100; return touchInput; } @@ -2807,18 +3077,12 @@ private: && ! isKioskMode(); } - Rectangle getCurrentScaledBounds (float scale) const - { - return ScalingHelpers::unscaledScreenPosToScaled (scale, windowBorder.addedTo (ScalingHelpers::scaledScreenPosToUnscaled (scale, component.getBounds()))); - } - LRESULT handleSizeConstraining (RECT& r, const WPARAM wParam) { if (isConstrainedNativeWindow()) { - auto scale = getComponent().getDesktopScaleFactor(); - auto pos = ScalingHelpers::unscaledScreenPosToScaled (scale, rectangleFromRECT (r)); - auto current = getCurrentScaledBounds (scale); + auto pos = convertPhysicalScreenRectangleToLogical (rectangleFromRECT (r), hwnd); + auto current = windowBorder.addedTo (component.getBounds()); constrainer->checkBounds (pos, current, Desktop::getInstance().getDisplays().getTotalBounds (true), @@ -2827,26 +3091,59 @@ private: wParam == WMSZ_BOTTOM || wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_BOTTOMRIGHT, wParam == WMSZ_RIGHT || wParam == WMSZ_TOPRIGHT || wParam == WMSZ_BOTTOMRIGHT); - pos = ScalingHelpers::scaledScreenPosToUnscaled (scale, pos); - r.left = pos.getX(); - r.top = pos.getY(); - r.right = pos.getRight(); - r.bottom = pos.getBottom(); + r = RECTFromRectangle (convertLogicalScreenRectangleToPhysical (pos, hwnd)); } return TRUE; } + const Displays::Display* getCurrentDisplayFromScaleFactor() + { + Array candidateDisplays; + + for (auto& d : Desktop::getInstance().getDisplays().displays) + if (approximatelyEqual (d.scale, getPlatformScaleFactor())) + candidateDisplays.add (&d); + + if (candidateDisplays.size() > 0) + { + if (candidateDisplays.size() == 1) + return candidateDisplays[0]; + + auto bounds = getComponent().getTopLevelComponent()->getBounds(); + + Displays::Display* retVal = nullptr; + int maxArea = -1; + + for (auto* d : candidateDisplays) + { + auto intersection = d->totalArea.getIntersection (bounds); + auto area = intersection.getWidth() * intersection.getHeight(); + + if (area > maxArea) + { + maxArea = area; + retVal = d; + } + } + + if (retVal != nullptr) + return retVal; + } + + return &Desktop::getInstance().getDisplays().getMainDisplay(); + } + LRESULT handlePositionChanging (WINDOWPOS& wp) { if (isConstrainedNativeWindow()) { if ((wp.flags & (SWP_NOMOVE | SWP_NOSIZE)) != (SWP_NOMOVE | SWP_NOSIZE) + && (wp.x > -32000 && wp.y > -32000) && ! Component::isMouseButtonDownAnywhere()) { - auto scale = getComponent().getDesktopScaleFactor(); - auto pos = ScalingHelpers::unscaledScreenPosToScaled (scale, Rectangle (wp.x, wp.y, wp.cx, wp.cy)); - auto current = getCurrentScaledBounds (scale); + auto pos = Desktop::getInstance().getDisplays().physicalToLogical (rectangleFromRECT ({ wp.x, wp.y, wp.x + wp.cx, wp.y + wp.cy }), getCurrentDisplayFromScaleFactor()); + auto current = windowBorder.addedTo (component.getBounds()); constrainer->checkBounds (pos, current, Desktop::getInstance().getDisplays().getTotalBounds (true), @@ -2855,9 +3152,21 @@ private: pos.getY() == current.getY() && pos.getBottom() != current.getBottom(), pos.getX() == current.getX() && pos.getRight() != current.getRight()); - pos = ScalingHelpers::scaledScreenPosToUnscaled (scale, pos); - wp.x = pos.getX(); - wp.y = pos.getY(); + if (isPerMonitorDPIAwareWindow (hwnd)) + { + auto newScaleFactor = getScaleFactorForNewBounds (pos); + + if (! approximatelyEqual (scaleFactor, newScaleFactor)) + { + scaleFactor = newScaleFactor; + handleScaleFactorChange(); + } + } + + pos = Desktop::getInstance().getDisplays().logicalToPhysical (pos, getCurrentDisplayFromScaleFactor()); + + wp.x = pos.getX(); + wp.y = pos.getY(); wp.cx = pos.getWidth(); wp.cy = pos.getHeight(); } @@ -2873,6 +3182,11 @@ private: bool handlePositionChanged() { + static bool reentrant = false; + + if (reentrant) + return false; + auto pos = getCurrentMousePos(); if (contains (pos.roundToInt(), false)) @@ -2884,7 +3198,23 @@ private: return true; } + if (isPerMonitorDPIAwareWindow (hwnd)) + { + auto newScaleFactor = getScaleFactorForNewBounds (convertPhysicalScreenRectangleToLogical (rectangleFromRECT (getWindowRect (hwnd)), hwnd)); + + if (! approximatelyEqual (scaleFactor, newScaleFactor)) + { + const ScopedValueSetter setter (reentrant, true); + + // If this changes the window position then handlePositionChanged() will be called again, so + // set a flag to avoid doing unnecessary work. + updateScaleFactorAndCheckWindowSize (newScaleFactor, false); + handleScaleFactorChange(); + } + } + handleMovedOrResized(); + return ! dontRepaint; // to allow non-accelerated openGL windows to draw themselves correctly.. } @@ -2987,13 +3317,8 @@ private: forceDisplayUpdate(); if (fullScreen && ! isMinimised()) - { - auto& display = Desktop::getInstance().getDisplays() - .getDisplayContaining (component.getScreenBounds().getCentre()); - - setWindowPos (hwnd, display.userArea * display.scale, + setWindowPos (hwnd, Desktop::getInstance().getDisplays().findDisplayForRect (component.getScreenBounds()).userArea, SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER | SWP_NOSENDCHANGING); - } } static void forceDisplayUpdate() @@ -3001,8 +3326,62 @@ private: const_cast (Desktop::getInstance().getDisplays()).refresh(); } - void handleDPIChange() // happens when a window moves to a screen with a different DPI. + void updateScaleFactorAndCheckWindowSize (double newScaleFactor, bool callListeners) + { + static bool inSetWindowPos = false; + + if (inSetWindowPos || approximatelyEqual (scaleFactor, newScaleFactor)) + return; + + auto oldScaleFactor = scaleFactor; + scaleFactor = newScaleFactor; + + auto r = getWindowRect (hwnd); + + auto rectWidth = r.right - r.left - roundToInt (windowBorder.getLeftAndRight() * oldScaleFactor); + auto rectHeight = r.bottom - r.top - roundToInt (windowBorder.getTopAndBottom() * oldScaleFactor); + + auto compBounds = component.getBounds(); + + // Don't scale windows that are already correct + if (isWithin ((int) rectWidth, roundToInt (compBounds.getWidth() * oldScaleFactor), 2) + && isWithin ((int) rectHeight, roundToInt (compBounds.getHeight() * oldScaleFactor), 2)) + { + POINT p { r.left, r.top }; + ScreenToClient (GetParent (hwnd), &p); + + auto factor = scaleFactor / oldScaleFactor; + + { + // SetWindowPos() can cause this to be re-entrant so set a flag to avoid double scaling + const ScopedValueSetter setter (inSetWindowPos, true); + + SetWindowPos (hwnd, 0, roundToInt (p.x * factor), roundToInt (p.y * factor), + roundToInt ((r.right - r.left) * factor), roundToInt ((r.bottom - r.top) * factor), + SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOOWNERZORDER); + } + } + + if (callListeners) + scaleFactorListeners.call ([&] (ScaleFactorListener& l) { l.nativeScaleFactorChanged (scaleFactor); }); + } + + static BOOL CALLBACK setChildScaleFactorCallback (HWND hwnd, LPARAM context) + { + if (auto* peer = getOwnerOfWindow (hwnd)) + peer->updateScaleFactorAndCheckWindowSize (*(double*) context, true); + + return TRUE; + } + + void handleScaleFactorChange() { + EnumChildWindows (hwnd, setChildScaleFactorCallback, (LPARAM) &scaleFactor); + + updateShadower(); + InvalidateRect (hwnd, nullptr, FALSE); + + scaleFactorListeners.call ([&] (ScaleFactorListener& l) { l.nativeScaleFactorChanged (scaleFactor); }); } //============================================================================== @@ -3022,6 +3401,11 @@ private: public: static LRESULT CALLBACK windowProc (HWND h, UINT message, WPARAM wParam, LPARAM lParam) { + // Ensure that non-client areas are scaled for per-monitor DPI awareness v1 - can't + // do this in peerWindowProc as we have no window at this point + if (message == WM_NCCREATE && enableNonClientDPIScaling != nullptr) + enableNonClientDPIScaling (h); + if (auto* peer = getOwnerOfWindow (h)) { jassert (isValidPeer (peer)); @@ -3042,20 +3426,30 @@ private: return mm.callFunctionOnMessageThread (callback, userData); } - static Point getPointFromLParam (LPARAM lParam) noexcept + static POINT getPOINTFromLParam (LPARAM lParam) noexcept { - return { static_cast (GET_X_LPARAM (lParam)), - static_cast (GET_Y_LPARAM (lParam)) }; + return { GET_X_LPARAM (lParam), GET_Y_LPARAM (lParam) }; } - static Point getCurrentMousePosGlobal() noexcept + Point getPointFromLocalLParam (LPARAM lParam) noexcept { - return getPointFromLParam (GetMessagePos()); + if (isPerMonitorDPIAwareWindow (hwnd)) + { + // LPARAM is relative to this window's top-left but may be on a different monitor so we need to calculate the + // physical screen position and then convert this to local logical coordinates + auto localPos = getPOINTFromLParam (lParam); + auto r = getWindowRect (hwnd); + + return globalToLocal (convertPhysicalScreenPointToLogical (pointFromPOINT ({ r.left + localPos.x + roundToInt (windowBorder.getLeft() * scaleFactor), + r.top + localPos.y + roundToInt (windowBorder.getTop() * scaleFactor) }), hwnd).toFloat()); + } + + return { static_cast (GET_X_LPARAM (lParam)), static_cast (GET_Y_LPARAM (lParam)) }; } Point getCurrentMousePos() noexcept { - return globalToLocal (getCurrentMousePosGlobal()); + return globalToLocal (convertPhysicalScreenPointToLogical (pointFromPOINT (getPOINTFromLParam (GetMessagePos())), hwnd).toFloat()); } LRESULT peerWindowProc (HWND h, UINT message, WPARAM wParam, LPARAM lParam) @@ -3109,18 +3503,18 @@ private: break; //============================================================================== - case WM_MOUSEMOVE: doMouseMove (getPointFromLParam (lParam), false); return 0; + case WM_MOUSEMOVE: doMouseMove (getPointFromLocalLParam (lParam), false); return 0; case WM_POINTERLEAVE: case WM_MOUSELEAVE: doMouseExit(); return 0; case WM_LBUTTONDOWN: case WM_MBUTTONDOWN: - case WM_RBUTTONDOWN: doMouseDown (getPointFromLParam (lParam), wParam); return 0; + case WM_RBUTTONDOWN: doMouseDown (getPointFromLocalLParam (lParam), wParam); return 0; case WM_LBUTTONUP: case WM_MBUTTONUP: - case WM_RBUTTONUP: doMouseUp (getPointFromLParam (lParam), wParam); return 0; + case WM_RBUTTONUP: doMouseUp (getPointFromLocalLParam (lParam), wParam); return 0; case WM_POINTERWHEEL: case 0x020A: /* WM_MOUSEWHEEL */ doMouseWheel (wParam, true); return 0; @@ -3157,7 +3551,7 @@ private: { const WINDOWPOS& wPos = *reinterpret_cast (lParam); - if ((wPos.flags & SWP_NOMOVE) != 0 && (wPos.flags & SWP_NOSIZE) != 0) + if (GetParent == nullptr && ((wPos.flags & SWP_NOMOVE) != 0 && (wPos.flags & SWP_NOSIZE) != 0)) startTimer(100); else if (handlePositionChanged()) @@ -3293,10 +3687,6 @@ private: doSettingChange(); break; - case 0x2e0: // WM_DPICHANGED - handleDPIChange(); - break; - case WM_INITMENU: initialiseSysMenu ((HMENU) wParam); break; @@ -3887,13 +4277,28 @@ Point MouseInputSource::getCurrentRawMousePosition() { POINT mousePos; GetCursorPos (&mousePos); - return { (float) mousePos.x, (float) mousePos.y }; + + auto p = pointFromPOINT (mousePos); + + #if JUCE_WIN_PER_MONITOR_DPI_AWARE + if (isPerMonitorDPIAwareThread()) + p = Desktop::getInstance().getDisplays().physicalToLogical (p); + #endif + + return p.toFloat(); } void MouseInputSource::setRawMousePosition (Point newPosition) { - SetCursorPos (roundToInt (newPosition.x), - roundToInt (newPosition.y)); + auto newPositionInt = newPosition.roundToInt(); + + #if JUCE_WIN_PER_MONITOR_DPI_AWARE + if (isPerMonitorDPIAwareThread()) + newPositionInt = Desktop::getInstance().getDisplays().logicalToPhysical (newPositionInt); + #endif + + auto point = POINTFromPoint (newPositionInt); + SetCursorPos (point.x, point.y); } //============================================================================== @@ -4004,10 +4409,10 @@ void Desktop::allowedOrientationsChanged() {} //============================================================================== struct MonitorInfo { - MonitorInfo (Rectangle rect, bool main, double d) noexcept - : bounds (rect), dpi (d), isMain (main) {} + MonitorInfo (bool main, const RECT& rect, double d) noexcept + : isMain (main), bounds (rect), dpi (d) {} - Rectangle bounds; + RECT bounds; double dpi; bool isMain; }; @@ -4017,8 +4422,9 @@ static BOOL CALLBACK enumMonitorsProc (HMONITOR hm, HDC, LPRECT r, LPARAM userIn MONITORINFO info = { 0 }; info.cbSize = sizeof (info); GetMonitorInfo (hm, &info); - const bool isMain = (info.dwFlags & 1 /* MONITORINFOF_PRIMARY */) != 0; - double dpi = 0; + + auto isMain = (info.dwFlags & 1 /* MONITORINFOF_PRIMARY */) != 0; + auto dpi = 0.0; if (getDPIForMonitor != nullptr) { @@ -4028,7 +4434,7 @@ static BOOL CALLBACK enumMonitorsProc (HMONITOR hm, HDC, LPRECT r, LPARAM userIn dpi = (dpiX + dpiY) / 2.0; } - ((Array*) userInfo)->add (MonitorInfo (rectangleFromRECT (*r), isMain, dpi)); + ((Array*) userInfo)->add ({ isMain, *r, dpi }); return TRUE; } @@ -4037,44 +4443,75 @@ void Displays::findDisplays (float masterScale) { setDPIAwareness(); + #if JUCE_WIN_PER_MONITOR_DPI_AWARE + // We need to ensure that the displays are always calculated in a DPI-aware context so that the scale factors are correct, + // this will be reset to the previous context at the end. + DPI_AWARENESS_CONTEXT prevContext = nullptr; + + if (getAwarenessFromDPIAwarenessContext (getThreadDPIAwarenessContext()) != Process_DPI_Awareness::Process_Per_Monitor_DPI_Aware + && setThreadDPIAwarenessContext != nullptr) + prevContext = setThreadDPIAwarenessContext (DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE); + #endif + Array monitors; EnumDisplayMonitors (0, 0, &enumMonitorsProc, (LPARAM) &monitors); - const double globalDPI = getGlobalDPI(); + auto globalDPI = getGlobalDPI(); if (monitors.size() == 0) - monitors.add (MonitorInfo (rectangleFromRECT (getWindowRect (GetDesktopWindow())), true, globalDPI)); + monitors.add ({ true, getWindowRect (GetDesktopWindow()), globalDPI }); // make sure the first in the list is the main monitor for (int i = 1; i < monitors.size(); ++i) - if (monitors.getReference(i).isMain) + if (monitors.getReference (i).isMain) monitors.swap (i, 0); - RECT workArea; - SystemParametersInfo (SPI_GETWORKAREA, 0, &workArea, 0); - - for (int i = 0; i < monitors.size(); ++i) + for (auto& monitor : monitors) { Display d; - d.userArea = d.totalArea = monitors.getReference(i).bounds / masterScale; - d.isMain = monitors.getReference(i).isMain; - d.dpi = monitors.getReference(i).dpi; + + d.isMain = monitor.isMain; + d.dpi = monitor.dpi; if (d.dpi == 0) { - d.scale = masterScale; d.dpi = globalDPI; + d.scale = masterScale; } else { - d.scale = d.dpi / 96.0; + d.scale = (d.dpi / USER_DEFAULT_SCREEN_DPI) * masterScale; } + d.userArea = d.totalArea = Rectangle::leftTopRightBottom (monitor.bounds.left, monitor.bounds.top, + monitor.bounds.right, monitor.bounds.bottom); + d.topLeftPhysical = { monitor.bounds.left, monitor.bounds.top }; + if (d.isMain) - d.userArea = d.userArea.getIntersection (rectangleFromRECT (workArea) / masterScale); + { + RECT workArea; + SystemParametersInfo (SPI_GETWORKAREA, 0, &workArea, 0); + + d.userArea = d.userArea.getIntersection (Rectangle::leftTopRightBottom (workArea.left, workArea.top, workArea.right, workArea.bottom)); + } + + #if ! JUCE_WIN_PER_MONITOR_DPI_AWARE + d.userArea /= masterScale; + d.totalArea /= masterScale; + d.scale /= masterScale; + #endif displays.add (d); } + + #if JUCE_WIN_PER_MONITOR_DPI_AWARE + // In per-monitor DPI aware mode, totalArea and userArea will be in physical pixels so must convert to logical + updateToLogical(); + + // Reset the DPI awareness context if it was overridden earlier + if (prevContext != nullptr) + setThreadDPIAwarenessContext (prevContext); + #endif } //============================================================================== diff --git a/modules/juce_gui_extra/embedding/juce_ScopedDPIAwarenessDisabler.h b/modules/juce_gui_extra/embedding/juce_ScopedDPIAwarenessDisabler.h new file mode 100644 index 0000000000..a769fba869 --- /dev/null +++ b/modules/juce_gui_extra/embedding/juce_ScopedDPIAwarenessDisabler.h @@ -0,0 +1,56 @@ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +#if (JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE) || DOXYGEN + +//============================================================================== +/** + A Windows-specific class that temporarily sets the DPI awareness context of + the current thread to be DPI unaware and resets it to the previous context + when it goes out of scope. + + If you create one of these before creating a top-level window, the window + will be DPI unaware and bitmap strectched by the OS on a display with >100% + scaling. + + You shouldn't use this unless you really know what you are doing and + are dealing with native HWNDs. +*/ +class JUCE_API ScopedDPIAwarenessDisabler +{ +public: + ScopedDPIAwarenessDisabler(); + ~ScopedDPIAwarenessDisabler(); + +private: + void* previousContext = nullptr; +}; +#endif + +} // namespace juce diff --git a/modules/juce_gui_extra/juce_gui_extra.h b/modules/juce_gui_extra/juce_gui_extra.h index 8d2c287a5e..1db6e35fe9 100644 --- a/modules/juce_gui_extra/juce_gui_extra.h +++ b/modules/juce_gui_extra/juce_gui_extra.h @@ -88,6 +88,7 @@ #include "embedding/juce_NSViewComponent.h" #include "embedding/juce_UIViewComponent.h" #include "embedding/juce_XEmbedComponent.h" +#include "embedding/juce_ScopedDPIAwarenessDisabler.h" #include "misc/juce_AppleRemote.h" #include "misc/juce_BubbleMessageComponent.h" #include "misc/juce_ColourSelector.h" diff --git a/modules/juce_gui_extra/native/juce_win32_ActiveXComponent.cpp b/modules/juce_gui_extra/native/juce_win32_ActiveXComponent.cpp index 98ead94094..9be0bfba0b 100644 --- a/modules/juce_gui_extra/native/juce_win32_ActiveXComponent.cpp +++ b/modules/juce_gui_extra/native/juce_win32_ActiveXComponent.cpp @@ -231,6 +231,9 @@ namespace ActiveXHelpers //============================================================================== class ActiveXControlComponent::Pimpl : public ComponentMovementWatcher + #if JUCE_WIN_PER_MONITOR_DPI_AWARE + , public ComponentPeer::ScaleFactorListener + #endif { public: Pimpl (HWND hwnd, ActiveXControlComponent& activeXComp) @@ -251,12 +254,26 @@ public: clientSite->Release(); storage->Release(); + + #if JUCE_WIN_PER_MONITOR_DPI_AWARE + for (int i = 0; i < ComponentPeer::getNumPeers(); ++i) + if (auto* peer = ComponentPeer::getPeer (i)) + peer->removeScaleFactorListener (this); + #endif } void setControlBounds (Rectangle newBounds) const { if (controlHWND != 0) + { + #if JUCE_WIN_PER_MONITOR_DPI_AWARE + if (auto* peer = owner.getTopLevelComponent()->getPeer()) + newBounds = (newBounds.toDouble() * peer->getPlatformScaleFactor()).toNearestInt(); + #endif + MoveWindow (controlHWND, newBounds.getX(), newBounds.getY(), newBounds.getWidth(), newBounds.getHeight(), TRUE); + } + } void setControlVisible (bool shouldBeVisible) const @@ -269,12 +286,17 @@ public: void componentMovedOrResized (bool /*wasMoved*/, bool /*wasResized*/) override { if (auto* peer = owner.getTopLevelComponent()->getPeer()) - setControlBounds (peer->getAreaCoveredBy(owner)); + setControlBounds (peer->getAreaCoveredBy (owner)); } void componentPeerChanged() override { componentMovedOrResized (true, true); + + #if JUCE_WIN_PER_MONITOR_DPI_AWARE + if (auto* peer = owner.getTopLevelComponent()->getPeer()) + peer->addScaleFactorListener (this); + #endif } void componentVisibilityChanged() override @@ -283,6 +305,13 @@ public: componentPeerChanged(); } + #if JUCE_WIN_PER_MONITOR_DPI_AWARE + void nativeScaleFactorChanged (double /*newScaleFactor*/) override + { + componentMovedOrResized (true, true); + } + #endif + // intercepts events going to an activeX control, so we can sneakily use the mouse events static LRESULT CALLBACK activeXHookWndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { diff --git a/modules/juce_opengl/native/juce_OpenGL_win32.h b/modules/juce_opengl/native/juce_OpenGL_win32.h index 1672c452bb..a80370aa51 100644 --- a/modules/juce_opengl/native/juce_OpenGL_win32.h +++ b/modules/juce_opengl/native/juce_OpenGL_win32.h @@ -31,6 +31,9 @@ extern ComponentPeer* createNonRepaintingEmbeddedWindowsPeer (Component&, void* //============================================================================== class OpenGLContext::NativeContext + #if JUCE_WIN_PER_MONITOR_DPI_AWARE + : public ComponentPeer::ScaleFactorListener + #endif { public: NativeContext (Component& component, @@ -87,6 +90,12 @@ public: { deleteRenderContext(); releaseDC(); + + #if JUCE_WIN_PER_MONITOR_DPI_AWARE + for (int i = 0; i < ComponentPeer::getNumPeers(); ++i) + if (auto* peer = ComponentPeer::getPeer (i)) + peer->removeScaleFactorListener (this); + #endif } bool initialiseOnRenderThread (OpenGLContext& c) @@ -117,9 +126,17 @@ public: void updateWindowPosition (Rectangle bounds) { if (nativeWindow != nullptr) + { + #if JUCE_WIN_PER_MONITOR_DPI_AWARE + if (safeComponent != nullptr) + if (auto* peer = safeComponent->getTopLevelComponent()->getPeer()) + bounds = (bounds.toDouble() * peer->getPlatformScaleFactor()).toNearestInt(); + #endif + SetWindowPos ((HWND) nativeWindow->getNativeHandle(), 0, bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOOWNERZORDER); + } } bool createdOk() const noexcept { return getRawContext() != nullptr; } @@ -134,6 +151,15 @@ public: struct Locker { Locker (NativeContext&) {} }; + #if JUCE_WIN_PER_MONITOR_DPI_AWARE + void nativeScaleFactorChanged (double /*newScaleFactor*/) override + { + if (safeComponent != nullptr) + if (auto peer = safeComponent->getTopLevelComponent()->getPeer()) + updateWindowPosition (peer->getAreaCoveredBy (*safeComponent)); + } + #endif + private: struct DummyComponent : public Component { @@ -150,6 +176,9 @@ private: HGLRC renderContext; HDC dc; OpenGLContext* context = {}; + #if JUCE_WIN_PER_MONITOR_DPI_AWARE + Component::SafePointer safeComponent; + #endif #define JUCE_DECLARE_WGL_EXTENSION_FUNCTION(name, returnType, params) \ typedef returnType (__stdcall *type_ ## name) params; type_ ## name name; @@ -174,8 +203,15 @@ private: nativeWindow.reset (createNonRepaintingEmbeddedWindowsPeer (*dummyComponent, topComp->getWindowHandle())); if (auto* peer = topComp->getPeer()) + { updateWindowPosition (peer->getAreaCoveredBy (component)); + #if JUCE_WIN_PER_MONITOR_DPI_AWARE + peer->addScaleFactorListener (this); + safeComponent = Component::SafePointer (&component); + #endif + } + nativeWindow->setVisible (true); dc = GetDC ((HWND) nativeWindow->getNativeHandle()); } diff --git a/modules/juce_video/native/juce_win32_Video.h b/modules/juce_video/native/juce_win32_Video.h index 792c3710ff..598a89949d 100644 --- a/modules/juce_video/native/juce_win32_Video.h +++ b/modules/juce_video/native/juce_win32_Video.h @@ -159,6 +159,9 @@ namespace VideoRenderers //============================================================================== struct VideoComponent::Pimpl : public Component + #if JUCE_WIN_PER_MONITOR_DPI_AWARE + , public ComponentPeer::ScaleFactorListener + #endif { Pimpl (VideoComponent& ownerToUse, bool) : owner (ownerToUse), @@ -174,6 +177,12 @@ struct VideoComponent::Pimpl : public Component close(); context = nullptr; componentWatcher = nullptr; + + #if JUCE_WIN_PER_MONITOR_DPI_AWARE + for (int i = 0; i < ComponentPeer::getNumPeers(); ++i) + if (auto* peer = ComponentPeer::getPeer (i)) + peer->removeScaleFactorListener (this); + #endif } Result loadFromString (const String& fileOrURLPath) @@ -300,7 +309,7 @@ struct VideoComponent::Pimpl : public Component if (getWidth() > 0 && getHeight() > 0) if (auto* peer = getTopLevelComponent()->getPeer()) - context->updateWindowPosition (peer->getAreaCoveredBy (*this)); + context->updateWindowPosition ((peer->getAreaCoveredBy (*this).toDouble() * peer->getPlatformScaleFactor()).toNearestInt()); } void updateContextVisibility() @@ -332,6 +341,14 @@ struct VideoComponent::Pimpl : public Component owner.onErrorOccurred (errorMessage); } + #if JUCE_WIN_PER_MONITOR_DPI_AWARE + void nativeScaleFactorChanged (double /*newScaleFactor*/) override + { + if (videoLoaded) + updateContextPosition(); + } + #endif + File currentFile; URL currentURL; @@ -755,6 +772,10 @@ private: hwnd = nativeWindow->hwnd; + #if JUCE_WIN_PER_MONITOR_DPI_AWARE + topLevelPeer->addScaleFactorListener (&component); + #endif + if (hwnd != 0) { hdc = GetDC (hwnd);