From 8bfc151395f071d6045bcdd195bb7063ce53ac66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 6 Aug 2026 19:39:29 +0200 Subject: [PATCH] iOS: Hide the status bar when immersive mode is enabled prefersStatusBarHidden was dead code - it computed an orientation and a (commented out) user preference, then unconditionally returned false. So the status bar was only ever hidden on iPhone in landscape, and only because iOS does that on its own in compact height. Now it honors bImmersiveMode from the DisplayLayoutConfig matching the current orientation, so it also applies in portrait and on iPad. Adds the corresponding checkbox to the iOS system settings, and updates the status bar on rotation and when the setting is toggled. Also fixes a missing break in the ROTATE_UPDATED case in System_Notify, and a static/non-static mismatch on sceKernelLoadModuleBufferUsbWlan that broke the build (the header intentionally exposes it for sceVshBridge). Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01JZk5y4Fzw811WJoNWZb8Sc --- Core/HLE/sceKernelModule.cpp | 2 +- UI/GameSettingsScreen.cpp | 8 +++++++ assets/lang/en_US.ini | 1 + ios/ViewControllerCommon.h | 1 + ios/ViewControllerCommon.mm | 41 ++++++++++++++++++++++-------------- ios/main.mm | 8 +++++++ 6 files changed, 44 insertions(+), 17 deletions(-) diff --git a/Core/HLE/sceKernelModule.cpp b/Core/HLE/sceKernelModule.cpp index 4ef998c915..3b0212eb64 100644 --- a/Core/HLE/sceKernelModule.cpp +++ b/Core/HLE/sceKernelModule.cpp @@ -2539,7 +2539,7 @@ static u32 sceKernelLoadModuleDNAS(const char *name, u32 flags) } // Pretty sure this is a badly brute-forced function name... -static SceUID sceKernelLoadModuleBufferUsbWlan(u32 size, u32 bufPtr, u32 flags, u32 lmoptionPtr) +SceUID sceKernelLoadModuleBufferUsbWlan(u32 size, u32 bufPtr, u32 flags, u32 lmoptionPtr) { if (flags != 0) { WARN_LOG_REPORT(Log::Loader, "sceKernelLoadModuleBufferUsbWlan: unsupported flags: %08x", flags); diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index 1787301c2e..e0b978f4c2 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -1165,6 +1165,14 @@ void GameSettingsScreen::CreateSystemSettings(UI::ViewGroup *systemSettings) { switchMode->OnChoice.Add([](EventParams &e) { System_Notify(SystemNotification::APP_SWITCH_MODE_CHANGED); }); + + { + // Note: On iPhone, iOS hides the status bar in landscape no matter what this is set to. + DisplayLayoutConfig &config = g_Config.GetDisplayLayoutConfig(GetDeviceOrientation()); + systemSettings->Add(new CheckBox(&config.bImmersiveMode, sy->T("Hide status bar")))->OnClick.Add([](EventParams &e) { + System_Notify(SystemNotification::IMMERSIVE_MODE_CHANGE); + }); + } #endif #if PPSSPP_PLATFORM(ANDROID) diff --git a/assets/lang/en_US.ini b/assets/lang/en_US.ini index 109d0d8a35..2f7d45b472 100644 --- a/assets/lang/en_US.ini +++ b/assets/lang/en_US.ini @@ -1424,6 +1424,7 @@ Default tab = Default tab Dynarec/JIT (recommended) = Dynarec/JIT (recommended) Final processed image = Final processed image Hide navigation bar = Hide navigation bar +Hide status bar = Hide status bar I/O timing method = I/O timing method JIT using IR = JIT using IR Loaded plugin: %1 = Loaded plugin: %1 diff --git a/ios/ViewControllerCommon.h b/ios/ViewControllerCommon.h index 5ba55cd34d..77427673a6 100644 --- a/ios/ViewControllerCommon.h +++ b/ios/ViewControllerCommon.h @@ -26,6 +26,7 @@ - (void)startVideo:(int)width height:(int)height; - (void)stopVideo; - (void)appSwitchModeChanged; +- (void)immersiveModeChanged; - (void)setupController:(GCController *)controller; // Forwarded from the AppDelegate diff --git a/ios/ViewControllerCommon.mm b/ios/ViewControllerCommon.mm index 4964db869a..855f17de51 100644 --- a/ios/ViewControllerCommon.mm +++ b/ios/ViewControllerCommon.mm @@ -481,26 +481,24 @@ extern float g_safeInsetBottom; #endif #pragma mark - Status Bar Control -// iOS calls this to determine whether to hide the status bar -- (BOOL)prefersStatusBarHidden { - UIInterfaceOrientation orientation; - +// The immersive mode setting is per-orientation, so we need to know which way we're facing. +// Can't just use g_display for this, since it lags behind during rotation. +// Note: Using viewIfLoaded, since this can get called before the view exists, and we don't want to force it into existence. +- (DeviceOrientation)currentDeviceOrientation { if (@available(iOS 13.0, *)) { - UIWindowScene *scene = self.view.window.windowScene; + UIWindowScene *scene = self.viewIfLoaded.window.windowScene; if (scene != nil) { - orientation = scene.interfaceOrientation; - } else { - orientation = UIApplication.sharedApplication.statusBarOrientation; + return UIInterfaceOrientationIsPortrait(scene.interfaceOrientation) ? DeviceOrientation::Portrait : DeviceOrientation::Landscape; } - } else { - orientation = UIApplication.sharedApplication.statusBarOrientation; } + CGSize size = self.viewIfLoaded.bounds.size; + return size.height > size.width ? DeviceOrientation::Portrait : DeviceOrientation::Landscape; +} - BOOL isLandscape = UIInterfaceOrientationIsLandscape(orientation); - - bool userWantsStatusBar = true; // g_Config.bShowStatusBar; - // return isLandscape || !userWantsStatusBar; - return false; +// iOS calls this to determine whether to hide the status bar. +// Note that on iPhone, iOS hides it in landscape regardless of what we return here. +- (BOOL)prefersStatusBarHidden { + return g_Config.GetDisplayLayoutConfig([self currentDeviceOrientation]).bImmersiveMode ? YES : NO; } // Optional: choose light/dark text for the status bar @@ -508,11 +506,22 @@ extern float g_safeInsetBottom; return UIStatusBarStyleLightContent; } -// This should also be called when the user preference changes. - (void)onOrientationChanged { [self setNeedsStatusBarAppearanceUpdate]; } +// Called from the C++ side when the user toggles the immersive mode setting. +- (void)immersiveModeChanged { + [self setNeedsStatusBarAppearanceUpdate]; +} + +- (void)viewWillTransitionToSize:(CGSize)size + withTransitionCoordinator:(id)coordinator { + [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; + // The immersive mode setting is per-orientation, so the status bar may need to change along with the rotation. + [self setNeedsStatusBarAppearanceUpdate]; +} + - (void)updateResolutionWithView:(UIView *)view { // 1. Get the scale from the window scene (safest for Metal) CGFloat scale = 1.0; diff --git a/ios/main.mm b/ios/main.mm index 989e50f5d3..d1dc5f2795 100644 --- a/ios/main.mm +++ b/ios/main.mm @@ -432,6 +432,13 @@ void System_Notify(SystemNotification notification) { } }); break; + case SystemNotification::IMMERSIVE_MODE_CHANGE: + dispatch_async(dispatch_get_main_queue(), ^{ + if (sharedViewController) { + [sharedViewController immersiveModeChanged]; + } + }); + break; case SystemNotification::UI_STATE_CHANGED: dispatch_async(dispatch_get_main_queue(), ^{ if (sharedViewController) { @@ -452,6 +459,7 @@ void System_Notify(SystemNotification notification) { [UIViewController attemptRotationToDeviceOrientation]; } }); + break; default: break; }