From 88af872d4d5f6c747229a8e8952924cfc8188cb7 Mon Sep 17 00:00:00 2001 From: Anthony Nicholls Date: Mon, 6 Oct 2025 10:56:14 +0100 Subject: [PATCH] AUv3: Fix an issue in detecting the available screen user area On at least iOS 26 using a temporary window frame is unreliable. This change tries to use an existing window for any non-standalone app. It also updates the details on any changes, such as when the device orientation changes. --- .../juce_audio_plugin_client_AUv3.mm | 5 ++ .../juce_gui_basics/desktop/juce_Displays.cpp | 6 +- .../juce_gui_basics/desktop/juce_Displays.h | 6 +- .../native/juce_Windowing_android.cpp | 4 +- .../native/juce_Windowing_ios.mm | 85 +++++++++++++------ .../native/juce_Windowing_linux.cpp | 4 +- .../native/juce_Windowing_mac.mm | 4 +- .../native/juce_Windowing_windows.cpp | 4 +- 8 files changed, 81 insertions(+), 37 deletions(-) diff --git a/modules/juce_audio_plugin_client/juce_audio_plugin_client_AUv3.mm b/modules/juce_audio_plugin_client/juce_audio_plugin_client_AUv3.mm index ca5ebcab55..89bc92f36b 100644 --- a/modules/juce_audio_plugin_client/juce_audio_plugin_client_AUv3.mm +++ b/modules/juce_audio_plugin_client/juce_audio_plugin_client_AUv3.mm @@ -1896,6 +1896,8 @@ public: { JUCE_ASSERT_MESSAGE_THREAD + refreshDisplays(); + if (auto p = createPluginFilterOfType (AudioProcessor::wrapperType_AudioUnitv3)) { processorHolder = new AudioProcessorHolder (std::move (p)); @@ -1932,6 +1934,8 @@ public: void viewDidLayoutSubviews() { + refreshDisplays(); + if (auto holder = processorHolder.get()) { if ([myself view] != nullptr) @@ -2002,6 +2006,7 @@ public: } private: + static void refreshDisplays() { const_cast (Desktop::getInstance().getDisplays()).refresh(); } // There's a chance that createAudioUnit will be called from a background // thread while the processorHolder is being updated on the main thread. diff --git a/modules/juce_gui_basics/desktop/juce_Displays.cpp b/modules/juce_gui_basics/desktop/juce_Displays.cpp index 624bd58fd5..c95dd66caf 100644 --- a/modules/juce_gui_basics/desktop/juce_Displays.cpp +++ b/modules/juce_gui_basics/desktop/juce_Displays.cpp @@ -35,14 +35,14 @@ namespace juce { -Displays::Displays (Desktop& desktop) +Displays::Displays (const Desktop& desktop) { init (desktop); } -void Displays::init (Desktop& desktop) +void Displays::init (const Desktop& desktop) { - findDisplays (desktop.getGlobalScaleFactor()); + findDisplays (desktop); } const Displays::Display* Displays::getDisplayForRect (Rectangle rect, bool isPhysical) const noexcept diff --git a/modules/juce_gui_basics/desktop/juce_Displays.h b/modules/juce_gui_basics/desktop/juce_Displays.h index b35494c39d..643dbf7579 100644 --- a/modules/juce_gui_basics/desktop/juce_Displays.h +++ b/modules/juce_gui_basics/desktop/juce_Displays.h @@ -44,7 +44,7 @@ namespace juce class JUCE_API Displays { private: - Displays (Desktop&); + Displays (const Desktop&); public: //============================================================================== @@ -232,8 +232,8 @@ public: private: friend class Desktop; - void init (Desktop&); - void findDisplays (float masterScale); + void init (const Desktop&); + void findDisplays (const Desktop& desktop); void updateToLogical(); diff --git a/modules/juce_gui_basics/native/juce_Windowing_android.cpp b/modules/juce_gui_basics/native/juce_Windowing_android.cpp index 1ce840196e..837c007d68 100644 --- a/modules/juce_gui_basics/native/juce_Windowing_android.cpp +++ b/modules/juce_gui_basics/native/juce_Windowing_android.cpp @@ -2849,7 +2849,7 @@ DECLARE_JNI_CLASS (AndroidDisplayMetrics, "android/util/DisplayMetrics") #undef JNI_CLASS_MEMBERS //============================================================================== -void Displays::findDisplays (float masterScale) +void Displays::findDisplays (const Desktop& desktop) { auto* env = getEnv(); @@ -2865,7 +2865,7 @@ void Displays::findDisplays (float masterScale) d.scale = env->GetFloatField (displayMetrics, AndroidDisplayMetrics.density); d.dpi = (d.scale * 160.f); - d.scale *= masterScale; + d.scale *= desktop.getGlobalScaleFactor(); d.totalArea = Rectangle (env->GetIntField (displayMetrics, AndroidDisplayMetrics.widthPixels), env->GetIntField (displayMetrics, AndroidDisplayMetrics.heightPixels)) / d.scale; diff --git a/modules/juce_gui_basics/native/juce_Windowing_ios.mm b/modules/juce_gui_basics/native/juce_Windowing_ios.mm index fcd7254c6a..d7894d0b7e 100644 --- a/modules/juce_gui_basics/native/juce_Windowing_ios.mm +++ b/modules/juce_gui_basics/native/juce_Windowing_ios.mm @@ -651,42 +651,78 @@ Desktop::DisplayOrientation Desktop::getCurrentOrientation() const return Orientations::convertToJuce (orientation); } -// The most straightforward way of retrieving the screen area available to an iOS app -// seems to be to create a new window (which will take up all available space) and to -// query its frame. -struct TemporaryWindow +struct WindowInfo { - UIWindow* window = std::invoke ([&] + explicit WindowInfo (const UIWindow* window) + : bounds (convertToRectInt (window.frame)), + safeInsets (window.safeAreaInsets.top, + window.safeAreaInsets.left, + window.safeAreaInsets.bottom, + window.safeAreaInsets.right) + {} + + Rectangle bounds; + BorderSize safeInsets; +}; + +static const UIWindow* findWindow (const UIView* view) +{ + if (view == nullptr) + return nullptr; + + if (view.window != nullptr) + return view.window; + + return findWindow (view.superview); +} + +static const UIWindow* findWindow (const Desktop& desktop) +{ + if (auto* c = desktop.getComponent (0)) + if (auto* p = static_cast (c->getPeer())) + if (auto* w = findWindow (p->view)) + return w; + + return {}; +} + +static WindowInfo getWindowInfo (const Desktop& desktop) +{ + if (! JUCEApplication::isStandaloneApp()) + if (const auto* window = findWindow (desktop)) + return WindowInfo { window }; + + const auto createTemporaryWindow = []() { if (@available (iOS 13, *)) { SharedResourcePointer windowSceneTracker; if (auto* scene = windowSceneTracker->getWindowScene()) - return [[UIWindow alloc] initWithWindowScene: scene]; + return NSUniquePtr { [[UIWindow alloc] initWithWindowScene: scene] }; } - return [[UIWindow alloc] init]; - }); - ~TemporaryWindow() noexcept { [window release]; } -}; + return NSUniquePtr { [[UIWindow alloc] init] }; + }; -static Rectangle getRecommendedWindowBounds() -{ - return convertToRectInt (TemporaryWindow().window.frame); + auto window (createTemporaryWindow()); + return WindowInfo { window.get() }; } -static BorderSize getSafeAreaInsets (float masterScale) +static Rectangle getRecommendedWindowBounds (const Desktop& desktop) { - UIEdgeInsets safeInsets = TemporaryWindow().window.safeAreaInsets; - return detail::WindowingHelpers::roundToInt (BorderSize { safeInsets.top, - safeInsets.left, - safeInsets.bottom, - safeInsets.right }.multipliedBy (1.0 / (double) masterScale)); + return getWindowInfo (desktop).bounds; +} + +static BorderSize getSafeAreaInsets (const Desktop& desktop) +{ + const auto masterScale = (double) desktop.getGlobalScaleFactor(); + const auto safeInsets = getWindowInfo (desktop).safeInsets; + return detail::WindowingHelpers::roundToInt (safeInsets.multipliedBy (1.0 / masterScale)); } //============================================================================== -void Displays::findDisplays (float masterScale) +void Displays::findDisplays (const Desktop& desktop) { JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") static const auto keyboardShownSelector = @selector (juceKeyboardShown:); @@ -716,7 +752,7 @@ void Displays::findDisplays (float masterScale) addMethod (keyboardShownSelector, [] (id self, SEL, NSNotification* notification) { - setKeyboardScreenBounds (self, [&]() -> BorderSize + setKeyboardScreenBounds (self, std::invoke ([&]() -> BorderSize { auto* info = [notification userInfo]; @@ -744,7 +780,7 @@ void Displays::findDisplays (float masterScale) result.setBottom (rect.getHeight()); return result; - }()); + })); }); addMethod (keyboardHiddenSelector, [] (id self, SEL, NSNotification*) @@ -777,9 +813,10 @@ void Displays::findDisplays (float masterScale) UIScreen* s = [UIScreen mainScreen]; Display d; + const auto masterScale = desktop.getGlobalScaleFactor(); d.totalArea = convertToRectInt ([s bounds]) / masterScale; - d.userArea = getRecommendedWindowBounds() / masterScale; - d.safeAreaInsets = getSafeAreaInsets (masterScale); + d.userArea = getRecommendedWindowBounds (desktop) / masterScale; + d.safeAreaInsets = getSafeAreaInsets (desktop); const auto scaledInsets = keyboardChangeDetector.getInsets().multipliedBy (1.0 / (double) masterScale); d.keyboardInsets = detail::WindowingHelpers::roundToInt (scaledInsets); d.isMain = true; diff --git a/modules/juce_gui_basics/native/juce_Windowing_linux.cpp b/modules/juce_gui_basics/native/juce_Windowing_linux.cpp index 235b13da3d..728bf82ff9 100644 --- a/modules/juce_gui_basics/native/juce_Windowing_linux.cpp +++ b/modules/juce_gui_basics/native/juce_Windowing_linux.cpp @@ -631,11 +631,11 @@ void Desktop::setKioskComponent (Component* comp, bool enableOrDisable, bool) comp->setBounds (getDisplays().getDisplayForRect (comp->getScreenBounds())->totalArea); } -void Displays::findDisplays (float masterScale) +void Displays::findDisplays (const Desktop& desktop) { if (XWindowSystem::getInstance()->getDisplay() != nullptr) { - displays = XWindowSystem::getInstance()->findDisplays (masterScale); + displays = XWindowSystem::getInstance()->findDisplays (desktop.getGlobalScaleFactor()); if (! displays.isEmpty()) updateToLogical(); diff --git a/modules/juce_gui_basics/native/juce_Windowing_mac.mm b/modules/juce_gui_basics/native/juce_Windowing_mac.mm index d5a15ef27b..b11975e1af 100644 --- a/modules/juce_gui_basics/native/juce_Windowing_mac.mm +++ b/modules/juce_gui_basics/native/juce_Windowing_mac.mm @@ -473,7 +473,7 @@ static Displays::Display getDisplayFromScreen (NSScreen* s, CGFloat& mainScreenB return d; } -void Displays::findDisplays (const float masterScale) +void Displays::findDisplays (const Desktop& desktop) { JUCE_AUTORELEASEPOOL { @@ -483,7 +483,7 @@ void Displays::findDisplays (const float masterScale) CGFloat mainScreenBottom = 0; for (NSScreen* s in [NSScreen screens]) - displays.add (getDisplayFromScreen (s, mainScreenBottom, masterScale)); + displays.add (getDisplayFromScreen (s, mainScreenBottom, desktop.getGlobalScaleFactor())); } } diff --git a/modules/juce_gui_basics/native/juce_Windowing_windows.cpp b/modules/juce_gui_basics/native/juce_Windowing_windows.cpp index c89140b03f..02b2fd24cc 100644 --- a/modules/juce_gui_basics/native/juce_Windowing_windows.cpp +++ b/modules/juce_gui_basics/native/juce_Windowing_windows.cpp @@ -6038,7 +6038,7 @@ static BOOL CALLBACK enumMonitorsProc (HMONITOR hm, HDC, LPRECT, LPARAM userInfo return TRUE; } -void Displays::findDisplays (float masterScale) +void Displays::findDisplays (const Desktop& desktop) { setDPIAwareness(); @@ -6058,6 +6058,8 @@ void Displays::findDisplays (float masterScale) if (monitors.getReference (i).isMain) monitors.swap (i, 0); + const auto masterScale = desktop.getGlobalScaleFactor(); + for (auto& monitor : monitors) { Display d;