mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-10-25 14:47:00 +08:00
113 lines
4.1 KiB
Swift
113 lines
4.1 KiB
Swift
import SwiftUI
|
|
import UIKit
|
|
|
|
final class CrossDeskAppDelegate: NSObject, UIApplicationDelegate {
|
|
static var supportedOrientations: UIInterfaceOrientationMask =
|
|
UIDevice.current.userInterfaceIdiom == .pad ? .all : .portrait
|
|
|
|
func application(_ application: UIApplication,
|
|
supportedInterfaceOrientationsFor window: UIWindow?)
|
|
-> UIInterfaceOrientationMask {
|
|
Self.supportedOrientations
|
|
}
|
|
}
|
|
|
|
@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?
|
|
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@main
|
|
struct CrossDeskMobileApp: App {
|
|
@UIApplicationDelegateAdaptor(CrossDeskAppDelegate.self) private var appDelegate
|
|
@StateObject private var session = RemoteSessionModel()
|
|
|
|
var body: some Scene {
|
|
WindowGroup {
|
|
ContentView(session: session)
|
|
}
|
|
}
|
|
}
|