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 <[email protected]>
Claude-Session: https://claude.ai/code/session_01JZk5y4Fzw811WJoNWZb8Sc
This commit is contained in:
Henrik RydgårdandClaude Opus 5 committed 2026-08-06 20:56:37 +02:00
1 parent 35f558d677
commit 8bfc151395
6 files changed
+44 -17

No files matched your search

+1 -1
View File
@@ -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);
+8
View File
@@ -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)
+1
View File
@@ -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
+1
View File
@@ -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
+25 -16
View File
@@ -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<UIViewControllerTransitionCoordinator>)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;
+8
View File
@@ -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;
}