tvOS: Back on the main screen goes to the home screen, like on Android

tvOS only does that with a Back press it gets to see, so the view controller
lets controller events through to UIKit while the top screen is top level
(the new TOP_LEVEL_CHANGED notification), and passes presses that aren't
keyboard keys on to super. Elsewhere Back stays ours.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Henrik RydgårdandClaude Opus 5.5 committed 2026-10-13 09:29:26 +02:00
1 parent 5b05b34b4e
commit 625db43746
5 files changed
+45 -4

No files matched your search

+1
View File
@@ -277,6 +277,7 @@ enum class SystemNotification {
PAD_STATE_CHANGED,
CONFIG_LOADED,
BEFORE_CONFIG_SAVE_ON_EXIT,
TOP_LEVEL_CHANGED, // NativeIsAtTopLevel() changed. From the render thread.
};
// I guess it's not super great architecturally to centralize this, since it's not general - but same with a lot of
+6 -3
View File
@@ -1,4 +1,5 @@
#include "Common/System/Display.h"
#include "Common/System/System.h"
#include "Common/Data/Collections/TinySet.h"
#include "Common/UI/ScreenManager.h"
#include "Common/UI/Root.h"
@@ -133,13 +134,15 @@ void ScreenManager::Update() {
overlayScreen_->update();
}
bool topLevel = false;
if (!stack_.empty()) {
Screen *backScreen = stack_.back().screen;
backScreen->update();
passInputToMapper_ = backScreen->PassInputToMapper();
topIsTopLevel_ = backScreen->isTopLevel();
} else {
topIsTopLevel_ = false;
topLevel = backScreen->isTopLevel();
}
if (topIsTopLevel_.exchange(topLevel) != topLevel) {
System_Notify(SystemNotification::TOP_LEVEL_CHANGED);
}
}
+1
View File
@@ -46,6 +46,7 @@
- (void)willResignActive;
- (void)uiStateChanged;
- (void)topLevelChanged;
- (void)pickPhoto:(NSString *)saveFilename requestId:(int)requestId;
- (void)updateResolutionWithView:(UIView *)view;
+30 -1
View File
@@ -202,7 +202,7 @@ static int GetPickerRequestId(id picker) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(controllerDidDisconnect:) name:GCControllerDidDisconnectNotification object:nil];
#if PPSSPP_PLATFORM(TVOS)
// Already the default. Should become YES at the top level menu, so the Menu button goes home.
// Already the default. See topLevelChanged.
self.controllerUserInteractionEnabled = NO;
#else
// Observe orientation changes
@@ -392,6 +392,15 @@ static int GetPickerRequestId(id picker) {
}
}
// Like Android, Back on the main screen leaves the app. tvOS only does that with a Back press it
// gets to see, so let controller events through to UIKit there. They still reach GCController.
- (void)topLevelChanged {
#if PPSSPP_PLATFORM(TVOS)
self.controllerUserInteractionEnabled = NativeIsAtTopLevel() ? YES : NO;
INFO_LOG(Log::System, "Controller events to UIKit: %s", self.controllerUserInteractionEnabled ? "yes" : "no");
#endif
}
- (void)setupController:(GCController *)controller {
self.gameController = controller;
if (!InitController(controller)) {
@@ -698,16 +707,36 @@ extern float g_safeInsetBottom;
return YES;
}
// Presses that aren't keyboard keys, like the remote's buttons, go on up the responder chain. On
// tvOS, that's what takes Back on the main screen to the home screen (see topLevelChanged).
static NSSet<UIPress *> *NonKeyboardPresses(NSSet<UIPress *> *presses) {
return [presses objectsPassingTest:^BOOL(UIPress *press, BOOL *stop) {
return press.key == nil;
}];
}
- (void)pressesBegan:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event {
KeyboardPressesBegan(presses, event);
NSSet<UIPress *> *others = NonKeyboardPresses(presses);
if (others.count) {
[super pressesBegan:others withEvent:event];
}
}
- (void)pressesEnded:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event {
KeyboardPressesEnded(presses, event);
NSSet<UIPress *> *others = NonKeyboardPresses(presses);
if (others.count) {
[super pressesEnded:others withEvent:event];
}
}
- (void)pressesCancelled:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event {
KeyboardPressesEnded(presses, event);
NSSet<UIPress *> *others = NonKeyboardPresses(presses);
if (others.count) {
[super pressesCancelled:others withEvent:event];
}
}
#pragma mark - Status Bar Control
+7
View File
@@ -438,6 +438,13 @@ void System_Notify(SystemNotification notification) {
}
});
break;
case SystemNotification::TOP_LEVEL_CHANGED:
dispatch_async(dispatch_get_main_queue(), ^{
if (sharedViewController) {
[sharedViewController topLevelChanged];
}
});
break;
case SystemNotification::AUDIO_MODE_CHANGED:
dispatch_async(dispatch_get_main_queue(), ^{
iOSCoreAudioUpdateSession();