mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-10-28 16:54:11 +08:00
[fix] make mobile landscape locking opt-in
This commit is contained in:
@@ -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
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user