mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-11-11 19:07:33 +08:00
[feat] remember remote display resolutions on mobile clients
This commit is contained in:
1 parent
23f48dc761
commit
25fb2a3d7f
15 files changed
+253
-33
No files matched your search
@@ -180,7 +180,8 @@ typedef struct {
|
||||
- (void)sendAnnouncementRequest:(NSData *)request requestID:(NSString *)requestID
|
||||
NS_SWIFT_NAME(sendAnnouncementRequest(_:requestID:));
|
||||
|
||||
- (void)connectToRemoteID:(NSString *)remoteID password:(NSString *)password;
|
||||
- (void)connectToRemoteID:(NSString *)remoteID password:(NSString *)password
|
||||
displayResolutions:(NSData *)displayResolutions;
|
||||
- (void)disconnect;
|
||||
// Close both session and signaling peers, including automatic reconnection.
|
||||
- (void)stopNetworking;
|
||||
|
||||
@@ -86,6 +86,7 @@ struct RTCState {
|
||||
std::string identity_base;
|
||||
std::string controller_login;
|
||||
std::string pending_remote_id;
|
||||
std::vector<crossdesk::DisplayResolutionPreference> preferred_display_resolutions;
|
||||
uint64_t pending_controller_generation = 0;
|
||||
std::string pending_password;
|
||||
std::string transmission_id;
|
||||
@@ -800,7 +801,14 @@ MiniRtcParams MakeParams(const RTCState &state, const std::string &user_id,
|
||||
});
|
||||
}
|
||||
|
||||
- (void)connectToRemoteID:(NSString *)remoteID password:(NSString *)password {
|
||||
- (void)connectToRemoteID:(NSString *)remoteID password:(NSString *)password
|
||||
displayResolutions:(NSData *)displayResolutions {
|
||||
std::vector<crossdesk::DisplayResolutionPreference> preferences;
|
||||
if (displayResolutions.length > 0 && displayResolutions.length <= 65536) {
|
||||
const auto *bytes = static_cast<const char *>(displayResolutions.bytes);
|
||||
crossdesk::ParseDisplayResolutionPreferences(
|
||||
std::string(bytes, displayResolutions.length), preferences);
|
||||
}
|
||||
NSString *cleanID = [remoteID
|
||||
stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceAndNewlineCharacterSet];
|
||||
NSString *cleanPassword = [password
|
||||
@@ -820,6 +828,7 @@ MiniRtcParams MakeParams(const RTCState &state, const std::string &user_id,
|
||||
dispatch_async(_rtcQueue, ^{
|
||||
if (generation != self->_controllerGenerationCounter.load()) return;
|
||||
self->_state->pending_remote_id = identifier;
|
||||
self->_state->preferred_display_resolutions = preferences;
|
||||
self->_state->pending_controller_generation = generation;
|
||||
self->_state->pending_password = password_value;
|
||||
[self destroyControllerPeer];
|
||||
@@ -1458,6 +1467,7 @@ MiniRtcParams MakeParams(const RTCState &state, const std::string &user_id,
|
||||
: "iPhone-" + self->_state->identity_base;
|
||||
RemoteAction action{};
|
||||
action.type = ControlType::host_infomation;
|
||||
action.preferred_display_resolutions = self->_state->preferred_display_resolutions;
|
||||
action.i.platform = crossdesk::HostPlatform::IOS;
|
||||
action.i.supports_privacy_screen = true;
|
||||
action.i.supports_video_codec_status = true;
|
||||
|
||||
@@ -370,6 +370,7 @@ final class RemoteSessionModel: NSObject, ObservableObject, CrossDeskRTCBridgeDe
|
||||
private var appIsActive = false
|
||||
private lazy var audioPlayer = RemoteAudioPlayer()
|
||||
private var activeRemoteID = ""
|
||||
private var resolutionPreferenceKey = ""
|
||||
private var remoteAppVersion = ""
|
||||
private var remoteHostInfoReceived = false
|
||||
private var remoteVersionCheckAttempted = false
|
||||
@@ -610,6 +611,7 @@ final class RemoteSessionModel: NSObject, ObservableObject, CrossDeskRTCBridgeDe
|
||||
}
|
||||
|
||||
private func beginRemoteConnection(_ identifier: String) {
|
||||
guard let configuration = bridgeConfiguration else { return }
|
||||
AppOrientation.endSession()
|
||||
networkStatistics = RemoteNetworkStatistics()
|
||||
resetRemoteHostInfo()
|
||||
@@ -617,6 +619,8 @@ final class RemoteSessionModel: NSObject, ObservableObject, CrossDeskRTCBridgeDe
|
||||
resetVideoSettings()
|
||||
cancelPresenceProbe()
|
||||
activeRemoteID = identifier
|
||||
resolutionPreferenceKey = RemoteDisplayResolutionStore.key(host: configuration.host,
|
||||
port: configuration.signalPort, remoteID: identifier)
|
||||
activeDisplayName = ""
|
||||
displaySizes = []
|
||||
connectionRecorded = false
|
||||
@@ -626,7 +630,8 @@ final class RemoteSessionModel: NSObject, ObservableObject, CrossDeskRTCBridgeDe
|
||||
sessionVisible = false
|
||||
resetRemoteCursorState()
|
||||
connectionStatus = NSLocalizedString("正在准备连接…", comment: "")
|
||||
bridge.connect(toRemoteID: identifier, password: password)
|
||||
bridge.connect(toRemoteID: identifier, password: password,
|
||||
displayResolutions: RemoteDisplayResolutionStore.message(key: resolutionPreferenceKey))
|
||||
}
|
||||
|
||||
private func beginPresenceProbe(_ identifier: String) {
|
||||
@@ -1044,8 +1049,13 @@ final class RemoteSessionModel: NSObject, ObservableObject, CrossDeskRTCBridgeDe
|
||||
|
||||
func updateDisplayResolution(_ resolution: RemoteDisplayResolution, displayID: Int, displayKey: String) {
|
||||
guard hasNetworkConsent, isConnected, selectedDisplay == displayID,
|
||||
displayResolutions.display(displayID)?.display_key == displayKey,
|
||||
let request = displayResolutions.begin(displayID: selectedDisplay, resolution: resolution) else { return }
|
||||
let display = displayResolutions.display(displayID), display.display_key == displayKey else { return }
|
||||
if !displayResolutions.pending, display.current == resolution, display.modes.contains(resolution) {
|
||||
RemoteDisplayResolutionStore.remember(RemoteDisplayResolutionPreference(display_key: displayKey,
|
||||
width: resolution.width, height: resolution.height), key: resolutionPreferenceKey)
|
||||
return
|
||||
}
|
||||
guard let request = displayResolutions.begin(displayID: selectedDisplay, resolution: resolution) else { return }
|
||||
displayResolutionTimeoutTask?.cancel()
|
||||
bridge.sendDisplayResolution(displayID: request.display_id, displayKey: request.display_key,
|
||||
width: request.width, height: request.height, requestID: request.request_id)
|
||||
@@ -1059,7 +1069,9 @@ final class RemoteSessionModel: NSObject, ObservableObject, CrossDeskRTCBridgeDe
|
||||
// Host information can arrive before the connection-state callback.
|
||||
// The bridge already rejects callbacks from previous sessions.
|
||||
guard hasNetworkConsent else { return }
|
||||
displayResolutions.receive(message)
|
||||
if let confirmed = displayResolutions.receive(message) {
|
||||
RemoteDisplayResolutionStore.remember(confirmed, key: resolutionPreferenceKey)
|
||||
}
|
||||
if !displayResolutions.pending {
|
||||
displayResolutionTimeoutTask?.cancel(); displayResolutionTimeoutTask = nil
|
||||
}
|
||||
|
||||
@@ -77,6 +77,42 @@ struct RemoteDisplayResolution: Codable, Equatable, Hashable {
|
||||
var valid: Bool { (1...32768).contains(width) && (1...32768).contains(height) }
|
||||
}
|
||||
|
||||
struct RemoteDisplayResolutionPreference: Codable, Equatable {
|
||||
let display_key: String
|
||||
let width: Int
|
||||
let height: Int
|
||||
var valid: Bool {
|
||||
!display_key.isEmpty && display_key.utf8.count <= 1024 && !display_key.contains("\0") &&
|
||||
RemoteDisplayResolution(width: width, height: height).valid
|
||||
}
|
||||
}
|
||||
|
||||
enum RemoteDisplayResolutionStore {
|
||||
static func key(host: String, port: Int, remoteID: String) -> String {
|
||||
let scope = [host.trimmingCharacters(in: .whitespacesAndNewlines).lowercased(), String(port), remoteID]
|
||||
guard !remoteID.isEmpty, let data = try? JSONEncoder().encode(scope) else { return "" }
|
||||
return "crossdesk.remoteDisplayResolutions.v1." + data.base64EncodedString()
|
||||
}
|
||||
static func load(key: String, defaults: UserDefaults = .standard) -> [RemoteDisplayResolutionPreference] {
|
||||
guard !key.isEmpty, let data = defaults.data(forKey: key), data.count <= 65536,
|
||||
let values = try? JSONDecoder().decode([RemoteDisplayResolutionPreference].self, from: data),
|
||||
values.count <= 32, values.allSatisfy(\.valid),
|
||||
Set(values.map(\.display_key)).count == values.count else { return [] }
|
||||
return values
|
||||
}
|
||||
static func remember(_ preference: RemoteDisplayResolutionPreference, key: String,
|
||||
defaults: UserDefaults = .standard) {
|
||||
guard !key.isEmpty, preference.valid else { return }
|
||||
var values = load(key: key, defaults: defaults).filter { $0.display_key != preference.display_key }
|
||||
if values.count >= 32 { values.removeFirst() }
|
||||
values.append(preference)
|
||||
if let data = try? JSONEncoder().encode(values) { defaults.set(data, forKey: key) }
|
||||
}
|
||||
static func message(key: String, defaults: UserDefaults = .standard) -> Data {
|
||||
(try? JSONEncoder().encode(load(key: key, defaults: defaults))) ?? Data("[]".utf8)
|
||||
}
|
||||
}
|
||||
|
||||
struct RemoteDisplayResolutionInfo: Decodable {
|
||||
let display_id: Int
|
||||
let display_key: String
|
||||
@@ -128,7 +164,8 @@ struct RemoteDisplayResolutionState {
|
||||
guard pending, requested?.request_id == requestID else { return }
|
||||
pending = false; failed = true
|
||||
}
|
||||
mutating func receive(_ data: Data) {
|
||||
@discardableResult
|
||||
mutating func receive(_ data: Data) -> RemoteDisplayResolutionPreference? {
|
||||
struct Host: Decodable { let display_resolutions: [RemoteDisplayResolutionInfo]? }
|
||||
struct Message: Decodable {
|
||||
let type: Int
|
||||
@@ -136,16 +173,16 @@ struct RemoteDisplayResolutionState {
|
||||
let display_resolution: RemoteDisplayResolutionRequest?
|
||||
let display_resolutions: [RemoteDisplayResolutionInfo]?
|
||||
}
|
||||
guard let message = try? JSONDecoder().decode(Message.self, from: data) else { return }
|
||||
guard let message = try? JSONDecoder().decode(Message.self, from: data) else { return nil }
|
||||
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 }
|
||||
} else { return nil }
|
||||
guard actual.count <= 32, actual.allSatisfy(\.valid),
|
||||
Set(actual.map(\.display_id)).count == actual.count else { return }
|
||||
Set(actual.map(\.display_id)).count == actual.count else { return nil }
|
||||
displays = actual
|
||||
if message.type == 16, let response = message.display_resolution {
|
||||
pending = false
|
||||
@@ -153,11 +190,16 @@ struct RemoteDisplayResolutionState {
|
||||
$0.display_id == response.display_id && $0.display_key == response.display_key &&
|
||||
$0.current == response.resolution
|
||||
}
|
||||
if !failed {
|
||||
return RemoteDisplayResolutionPreference(display_key: response.display_key,
|
||||
width: response.width, height: response.height)
|
||||
}
|
||||
} 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)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user