Files
crossdesk/apps/ios/CrossDeskMobile/RemoteVideoSettings.swift
T

164 lines
6.2 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import Foundation
// Values match crossdesk::VideoSettings on the reliable control stream.
struct RemoteVideoSettings: Equatable {
var quality = 2
var frameRate = 60
var preference = 1
var isValid: Bool {
(0...2).contains(quality) && [30, 60].contains(frameRate) &&
(0...2).contains(preference)
}
}
struct RemoteVideoSettingsState {
private(set) var supported = false
private(set) var selection: RemoteVideoSettings
private(set) var applied: RemoteVideoSettings
private(set) var requestID: UInt32 = 0
private(set) var pending = false
private(set) var failed = false
private var appliedRequestID: UInt32 = 0
init(preference: Int = 1) {
let settings = RemoteVideoSettings(preference: preference)
selection = settings
applied = settings
}
mutating func setSupported(_ value: Bool) {
supported = value
if !value {
selection = applied
pending = false
failed = false
}
}
mutating func begin(_ settings: RemoteVideoSettings) -> UInt32? {
guard supported, settings.isValid else { return nil }
requestID &+= 1
selection = settings
pending = true
failed = false
return requestID
}
mutating func receive(_ settings: RemoteVideoSettings,
requestID responseID: UInt32, accepted: Bool) {
guard supported, settings.isValid,
Int32(bitPattern: requestID &- responseID) >= 0 else { return }
// A superseded success still establishes the rollback value for a
// newer request. A stale response must never replace its selection.
if accepted, Int32(bitPattern: responseID &- appliedRequestID) >= 0 {
applied = settings
appliedRequestID = responseID
}
guard responseID == requestID else { return }
pending = false
failed = !accepted
selection = applied
}
mutating func expire(requestID expiredID: UInt32) {
guard pending, expiredID == requestID else { return }
selection = applied
pending = false
failed = true
}
}
struct RemoteDisplayResolution: Codable, Equatable, Hashable {
let width: Int
let height: Int
var label: String { "\(width) × \(height)" }
var valid: Bool { (1...32768).contains(width) && (1...32768).contains(height) }
}
struct RemoteDisplayResolutionInfo: Decodable {
let display_id: Int
let display_key: String
let width: Int
let height: Int
let modes: [RemoteDisplayResolution]
var current: RemoteDisplayResolution { RemoteDisplayResolution(width: width, height: height) }
var valid: Bool {
(0..<32).contains(display_id) && !display_key.isEmpty &&
display_key.utf8.count <= 1024 && !display_key.contains("\0") &&
current.valid && modes.count <= 256 && modes.allSatisfy(\.valid)
}
}
struct RemoteDisplayResolutionRequest: Decodable, Equatable {
let display_id: Int
let display_key: String
let width: Int
let height: Int
let request_id: UInt32
var accepted = false
var resolution: RemoteDisplayResolution { RemoteDisplayResolution(width: width, height: height) }
func matches(_ other: Self) -> Bool {
display_id == other.display_id && display_key == other.display_key &&
resolution == other.resolution && request_id == other.request_id
}
}
struct RemoteDisplayResolutionState {
private(set) var displays: [RemoteDisplayResolutionInfo] = []
private(set) var requested: RemoteDisplayResolutionRequest?
private(set) var pending = false
private(set) var failed = false
func display(_ id: Int) -> RemoteDisplayResolutionInfo? {
displays.first { $0.display_id == id }
}
mutating func begin(displayID: Int, resolution: RemoteDisplayResolution) -> RemoteDisplayResolutionRequest? {
guard !pending, let display = display(displayID), display.modes.contains(resolution),
display.current != resolution else { return nil }
let next = (requested?.request_id ?? 0) &+ 1
let request = RemoteDisplayResolutionRequest(display_id: displayID,
display_key: display.display_key, width: resolution.width,
height: resolution.height, request_id: next == 0 ? 1 : next)
requested = request; pending = true; failed = false
return request
}
mutating func expire(requestID: UInt32) {
guard pending, requested?.request_id == requestID else { return }
pending = false; failed = true
}
mutating func receive(_ data: Data) {
struct Host: Decodable { let display_resolutions: [RemoteDisplayResolutionInfo]? }
struct Message: Decodable {
let type: Int
let host_info: Host?
let display_resolution: RemoteDisplayResolutionRequest?
let display_resolutions: [RemoteDisplayResolutionInfo]?
}
guard let message = try? JSONDecoder().decode(Message.self, from: data) else { return }
let actual: [RemoteDisplayResolutionInfo]
if message.type == 3, let host = message.host_info {
actual = host.display_resolutions ?? []
} else if message.type == 16, let response = message.display_resolution,
let requested, response.matches(requested), let values = message.display_resolutions {
actual = values
} else { return }
guard actual.count <= 32, actual.allSatisfy(\.valid),
Set(actual.map(\.display_id)).count == actual.count else { return }
displays = actual
if message.type == 16, let response = message.display_resolution {
pending = false
failed = !response.accepted || !actual.contains {
$0.display_id == response.display_id && $0.display_key == response.display_key &&
$0.current == response.resolution
}
} else if pending, let requested, !actual.contains(where: {
$0.display_id == requested.display_id && $0.display_key == requested.display_key &&
$0.modes.contains(requested.resolution)
}) {
expire(requestID: requested.request_id)
}
}
}