[fix] make mobile landscape locking opt-in

This commit is contained in:
dijunkun
2026-10-27 03:55:17 +08:00
parent 0e052e6d7a
commit a9e3178042
12 changed files with 57 additions and 154 deletions
+3 -1
View File
@@ -840,9 +840,11 @@ private struct ServerSettingsView: View {
.pickerStyle(.segmented)
}
Section {
Toggle("连接后自动横屏", isOn: $session.autoLandscape)
Toggle("连接建立后锁定横屏", isOn: $session.autoLandscape)
} header: {
Text("屏幕方向")
} footer: {
Text("默认关闭。开启后,iPhone 连接时进入并保持横屏;关闭时跟随设备方向。iPad 不受影响。")
}
Section {
DisclosureGroup(isExpanded: $serverConfigurationExpanded) {
@@ -15,79 +15,38 @@ final class CrossDeskAppDelegate: NSObject, UIApplicationDelegate {
@MainActor
enum AppOrientation {
nonisolated static let autoLandscapeKey = "crossdesk.mobile.auto-landscape"
private static var sessionActive = false
private static var waitingForLandscape = false
private static var deviceObserver: NSObjectProtocol?
private static var sessionOrientations: UIInterfaceOrientationMask?
nonisolated static func beginSession(autoLandscape: Bool) {
DispatchQueue.main.async { beginSessionOnMain(autoLandscape: autoLandscape) }
}
private static func beginSessionOnMain(autoLandscape: Bool) {
// Repeated transport-ready callbacks must not lock an existing session again.
guard !sessionActive else { return }
sessionActive = true
waitingForLandscape = autoLandscape && UIDevice.current.userInterfaceIdiom == .phone
if waitingForLandscape {
CrossDeskAppDelegate.supportedOrientations = .landscape
resumeObserving()
} else {
apply(to: .allButUpsideDown)
}
// Preserve the selected policy through repeated transport-ready callbacks.
guard sessionOrientations == nil else { return }
let orientations: UIInterfaceOrientationMask = autoLandscape ? .landscape : .allButUpsideDown
sessionOrientations = orientations
apply(to: orientations)
}
nonisolated static func endSession() {
DispatchQueue.main.async {
sessionActive = false
waitingForLandscape = false
stopObserving()
sessionOrientations = nil
apply(to: .portrait)
}
}
nonisolated static func resume() {
DispatchQueue.main.async { resumeObserving() }
}
private static func resumeObserving() {
guard sessionActive, waitingForLandscape, deviceObserver == nil,
UIApplication.shared.applicationState == .active else { return }
apply(to: .landscape)
deviceObserver = NotificationCenter.default.addObserver(
forName: UIDevice.orientationDidChangeNotification, object: nil, queue: .main
) { _ in
MainActor.assumeIsolated { deviceDidRotate() }
DispatchQueue.main.async {
apply(to: sessionOrientations ?? .portrait)
}
UIDevice.current.beginGeneratingDeviceOrientationNotifications()
deviceDidRotate()
}
nonisolated static func suspend() {
DispatchQueue.main.async { stopObserving() }
}
private static func stopObserving() {
guard let observer = deviceObserver else { return }
NotificationCenter.default.removeObserver(observer)
deviceObserver = nil
UIDevice.current.endGeneratingDeviceOrientationNotifications()
}
private static func deviceDidRotate() {
// UIDevice reports physical posture; window geometry also changes when
// we request landscape and must never release the initial lock itself.
guard sessionActive, waitingForLandscape, deviceObserver != nil,
UIApplication.shared.applicationState == .active,
UIDevice.current.orientation.isLandscape else { return }
waitingForLandscape = false
stopObserving()
apply(to: .allButUpsideDown)
}
private static func apply(to orientations: UIInterfaceOrientationMask) {
// iPad keeps every orientation available, including in resizable windows.
guard UIDevice.current.userInterfaceIdiom != .pad else { return }
CrossDeskAppDelegate.supportedOrientations = orientations
guard UIApplication.shared.applicationState == .active else { return }
for case let scene as UIWindowScene in UIApplication.shared.connectedScenes {
scene.windows.forEach {
$0.rootViewController?.setNeedsUpdateOfSupportedInterfaceOrientations()
@@ -312,7 +312,7 @@ final class RemoteSessionModel: NSObject, ObservableObject, CrossDeskRTCBridgeDe
@Published var videoAdaptationPolicy = VideoAdaptationPolicy.saved {
didSet { videoAdaptationPolicy.save() }
}
@Published var autoLandscape = UserDefaults.standard.object(forKey: AppOrientation.autoLandscapeKey) as? Bool ?? true {
@Published var autoLandscape = UserDefaults.standard.object(forKey: AppOrientation.autoLandscapeKey) as? Bool ?? false {
didSet { UserDefaults.standard.set(autoLandscape, forKey: AppOrientation.autoLandscapeKey) }
}
@Published private(set) var videoSettings = RemoteVideoSettingsState()
@@ -457,7 +457,6 @@ final class RemoteSessionModel: NSObject, ObservableObject, CrossDeskRTCBridgeDe
func applicationDidEnterBackground() {
appIsActive = false
AppOrientation.suspend()
appUpdates.setEnabled(false)
privacyNotice.didEnterBackground()
suspendVideoForBackground()
+7 -8
View File
@@ -214,14 +214,13 @@ the available space. Settings and reading pages are limited to 720 points.
Resizing preserves the connection fields and active session. iPhone retains its
portrait home screen and two-column recent-connection grid.
On iPhone, Settings → Screen orientation (屏幕方向) → Landscape after connecting
(连接后自动横屏) is enabled by default and saved locally. A new session starts in
landscape and keeps that orientation until the phone is physically held sideways;
it then resumes the existing device-following policy for the rest of the session.
Portrait, flat and unknown device poses do not release the initial landscape hold.
Turning the option off keeps the existing rotation behavior. Leaving the session
restores portrait; foregrounding the same session does not reapply a released hold.
iPad orientation and multitasking are unaffected.
On iPhone, Settings → Screen orientation (屏幕方向) → Lock landscape after connecting
(连接建立后锁定横屏) is disabled by default and saved locally. When enabled, the session
enters and stays in landscape; when disabled, it follows the device orientation.
Leaving the session restores portrait, and returning to a background session keeps
that session's orientation setting. The physical-device monitoring that released
the initial landscape hold has been removed. iPad orientation and multitasking
are unaffected.
## System keyboard and remote text