mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-10-25 14:47:00 +08:00
[feat] add configurable landscape entry for mobile sessions
This commit is contained in:
@@ -75,7 +75,7 @@ struct ContentView: View {
|
||||
}
|
||||
.onAppear {
|
||||
if !session.sessionVisible {
|
||||
AppOrientation.update(to: .portrait)
|
||||
AppOrientation.endSession()
|
||||
}
|
||||
if scenePhase == .active {
|
||||
session.applicationDidBecomeActive()
|
||||
@@ -840,6 +840,11 @@ private struct ServerSettingsView: View {
|
||||
}
|
||||
.pickerStyle(.segmented)
|
||||
}
|
||||
Section {
|
||||
Toggle("连接后自动横屏", isOn: $session.autoLandscape)
|
||||
} header: {
|
||||
Text("屏幕方向")
|
||||
}
|
||||
Section {
|
||||
DisclosureGroup(isExpanded: $serverConfigurationExpanded) {
|
||||
Toggle("使用自定义服务器", isOn: $usesCustomServer)
|
||||
|
||||
@@ -12,24 +12,88 @@ final class CrossDeskAppDelegate: NSObject, UIApplicationDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
enum AppOrientation {
|
||||
static func update(to orientations: UIInterfaceOrientationMask) {
|
||||
DispatchQueue.main.async {
|
||||
// iPad windows can rotate and resize independently of the session.
|
||||
// Keep all orientations available and let the window manager choose
|
||||
// the geometry; the phone still locks the home screen to portrait.
|
||||
guard UIDevice.current.userInterfaceIdiom != .pad else { return }
|
||||
CrossDeskAppDelegate.supportedOrientations = orientations
|
||||
nonisolated static let autoLandscapeKey = "crossdesk.mobile.auto-landscape"
|
||||
private static var sessionActive = false
|
||||
private static var waitingForLandscape = false
|
||||
private static var deviceObserver: NSObjectProtocol?
|
||||
|
||||
for case let scene as UIWindowScene in UIApplication.shared.connectedScenes {
|
||||
scene.windows.forEach {
|
||||
$0.rootViewController?.setNeedsUpdateOfSupportedInterfaceOrientations()
|
||||
}
|
||||
scene.requestGeometryUpdate(
|
||||
.iOS(interfaceOrientations: orientations)
|
||||
) { error in
|
||||
NSLog("CrossDesk orientation update failed: %@", error.localizedDescription)
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
nonisolated static func endSession() {
|
||||
DispatchQueue.main.async {
|
||||
sessionActive = false
|
||||
waitingForLandscape = false
|
||||
stopObserving()
|
||||
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() }
|
||||
}
|
||||
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
|
||||
for case let scene as UIWindowScene in UIApplication.shared.connectedScenes {
|
||||
scene.windows.forEach {
|
||||
$0.rootViewController?.setNeedsUpdateOfSupportedInterfaceOrientations()
|
||||
}
|
||||
scene.requestGeometryUpdate(.iOS(interfaceOrientations: orientations)) { error in
|
||||
NSLog("CrossDesk orientation update failed: %@", error.localizedDescription)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -312,6 +312,9 @@ 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 {
|
||||
didSet { UserDefaults.standard.set(autoLandscape, forKey: AppOrientation.autoLandscapeKey) }
|
||||
}
|
||||
@Published private(set) var videoSettings = RemoteVideoSettingsState()
|
||||
@Published var remoteID = ""
|
||||
@Published var password = ""
|
||||
@@ -437,6 +440,7 @@ final class RemoteSessionModel: NSObject, ObservableObject, CrossDeskRTCBridgeDe
|
||||
|
||||
func applicationDidBecomeActive() {
|
||||
appIsActive = true
|
||||
AppOrientation.resume()
|
||||
pictureInPicture.didBecomeActive()
|
||||
appUpdates.setEnabled(hasNetworkConsent)
|
||||
privacyNotice.didBecomeActive(hasNetworkConsent: hasNetworkConsent)
|
||||
@@ -447,6 +451,7 @@ final class RemoteSessionModel: NSObject, ObservableObject, CrossDeskRTCBridgeDe
|
||||
|
||||
func applicationDidEnterBackground() {
|
||||
appIsActive = false
|
||||
AppOrientation.suspend()
|
||||
appUpdates.setEnabled(false)
|
||||
privacyNotice.didEnterBackground()
|
||||
suspendVideoForBackground()
|
||||
@@ -591,6 +596,7 @@ final class RemoteSessionModel: NSObject, ObservableObject, CrossDeskRTCBridgeDe
|
||||
}
|
||||
|
||||
private func beginRemoteConnection(_ identifier: String) {
|
||||
AppOrientation.endSession()
|
||||
networkStatistics = RemoteNetworkStatistics()
|
||||
resetRemoteHostInfo()
|
||||
cancelVideoRecovery()
|
||||
@@ -714,7 +720,7 @@ final class RemoteSessionModel: NSObject, ObservableObject, CrossDeskRTCBridgeDe
|
||||
selectedDisplay = 0
|
||||
connectionStatus = "未连接"
|
||||
audioPlayer.endSession()
|
||||
AppOrientation.update(to: .portrait)
|
||||
AppOrientation.endSession()
|
||||
appUpdates.checkNow()
|
||||
}
|
||||
|
||||
@@ -1191,7 +1197,7 @@ final class RemoteSessionModel: NSObject, ObservableObject, CrossDeskRTCBridgeDe
|
||||
recordSuccessfulConnectionIfNeeded()
|
||||
audioPlayer.setEnabled(audioEnabled)
|
||||
bridge.setAudioEnabled(audioEnabled)
|
||||
AppOrientation.update(to: .allButUpsideDown)
|
||||
AppOrientation.beginSession(autoLandscape: autoLandscape)
|
||||
case 2: connectionStatus = "正在收集 ICE 候选"
|
||||
case 3: finishConnection(withError: "网络连接已中断,请重新连接。")
|
||||
case 4:
|
||||
|
||||
@@ -203,6 +203,15 @@ 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.
|
||||
|
||||
## System keyboard and remote text
|
||||
|
||||
The session keyboard button opens the iPhone/iPad's selected system keyboard.
|
||||
|
||||
Reference in New Issue
Block a user