mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-11-12 03:28:20 +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
@@ -1,5 +1,5 @@
|
||||
import { tr } from './Localization';
|
||||
import { RemoteDisplayResolutions, ResolutionDisplay, ResolutionMode, ResolutionRequest, resolutionLabel } from './RemoteDisplayResolutions';
|
||||
import { RemoteDisplayResolutions, RemoteResolutionStore, ResolutionDisplay, ResolutionMode, ResolutionRequest, ResolutionPreference, resolutionLabel } from './RemoteDisplayResolutions';
|
||||
import { RemotePrivacyScreen, PrivacyStatus } from './RemotePrivacyScreen';
|
||||
import bridge from 'libcrossdesk.so';
|
||||
import { common, bundleManager } from '@kit.AbilityKit';
|
||||
@@ -12,6 +12,7 @@ import { AnnouncementInbox, AnnouncementItem, AnnouncementRequest, AnnouncementR
|
||||
import { VideoSettings, VideoValues, NetworkStatistics, NetworkReport, RecentPresence, PresenceMessage, asciiKey } from './ControllerModels';
|
||||
|
||||
interface Command {
|
||||
displayResolutions?: ResolutionPreference[];
|
||||
displayId?: number; displayKey?: string; width?: number; height?: number;
|
||||
op: string; session?: number; connection?: number; server?: string; port?: number; identity?: string;
|
||||
logs?: string; certificates?: string; directory?: string; previewDirectory?: string; remote?: string; password?: string; version?: string;
|
||||
@@ -106,6 +107,8 @@ export class MobileController {
|
||||
video: VideoSettings = new VideoSettings();
|
||||
private resolutions: RemoteDisplayResolutions = new RemoteDisplayResolutions();
|
||||
private resolutionTimeout: number = -1;
|
||||
private resolutionScope: string = '';
|
||||
private resolutionStore: RemoteResolutionStore;
|
||||
private privacyScreen: RemotePrivacyScreen = new RemotePrivacyScreen();
|
||||
network: NetworkStatistics = new NetworkStatistics();
|
||||
presence: RecentPresence;
|
||||
@@ -141,6 +144,8 @@ export class MobileController {
|
||||
private privacy: string = '';
|
||||
constructor(context: common.UIAbilityContext) {
|
||||
this.context = context; this.store = new MobileStore(context);
|
||||
this.resolutionStore = new RemoteResolutionStore((key: string) => this.store.text(key, '[]'),
|
||||
(key: string, value: string) => this.store.put(key, value));
|
||||
this.state.consent = this.store.flag('consent'); this.state.server = this.store.text('server', 'api.crossdesk.cn');
|
||||
this.state.port = this.store.text('port', '9099'); this.state.remote = this.store.text('lastRemote');
|
||||
this.state.relative = this.store.flag('relative', true); this.state.videoPreference = this.store.number('videoPreference', 1);
|
||||
@@ -248,6 +253,7 @@ export class MobileController {
|
||||
begin(password: string): void {
|
||||
if (!this.state.consent || !/^[0-9]{9}$/.test(this.state.remote) || !password || password.length > 128 || password.includes('@')) { this.toast(tr("请输入有效的访问密码")); return; }
|
||||
this.joinSent = false; this.connection++; this.connectingId = this.state.remote; this.pendingPassword = password;
|
||||
this.resolutionScope = this.scope();
|
||||
this.state.connecting = true; this.state.ready = false; this.state.modal = 'connecting'; this.state.notice = '';
|
||||
this.state.muted = false; this.state.platform = ''; this.state.hostName = ''; this.state.receivedPath = this.state.receivedName = this.state.transfer = '';
|
||||
this.state.remoteClipboard = ''; this.state.displays = []; this.state.width = 16; this.state.height = 9; this.state.hasFrame = false;
|
||||
@@ -265,7 +271,8 @@ export class MobileController {
|
||||
private joinSent: boolean = false;
|
||||
private joinIfReady(): void {
|
||||
if (!this.state.connecting || this.joinSent || this.state.signal !== 1 || !this.state.identity) return;
|
||||
this.joinSent = true; this.send({ op: 'connect', connection: this.connection, remote: this.connectingId, password: this.pendingPassword });
|
||||
this.joinSent = true; this.send({ op: 'connect', connection: this.connection, remote: this.connectingId, password: this.pendingPassword,
|
||||
displayResolutions: this.resolutionStore.load(this.resolutionScope, this.connectingId) });
|
||||
}
|
||||
end(reason: string): void {
|
||||
this.release(); this.send({ op: 'disconnect' }); this.platform.disconnect();
|
||||
@@ -310,6 +317,12 @@ export class MobileController {
|
||||
if (!this.state.ready || !this.state.consent) return;
|
||||
const display = this.resolutions.display(this.state.selectedDisplay);
|
||||
if (!display || display.display_id !== displayId || display.display_key !== displayKey) return;
|
||||
if (!this.resolutions.pending && display.width === mode.width && display.height === mode.height &&
|
||||
display.modes.some((value: ResolutionMode) => value.width === mode.width && value.height === mode.height)) {
|
||||
this.resolutionStore.remember(this.resolutionScope, this.connectingId,
|
||||
{ display_key: displayKey, width: mode.width, height: mode.height });
|
||||
return;
|
||||
}
|
||||
const request = this.resolutions.begin(displayId, mode);
|
||||
if (!request) return;
|
||||
this.send({ op: 'displayResolution', displayId: request.display_id, displayKey: request.display_key,
|
||||
@@ -532,7 +545,10 @@ export class MobileController {
|
||||
if (!this.video.pending && this.settingsTimeout >= 0) clearTimeout(this.settingsTimeout); this.syncVideo(); return;
|
||||
}
|
||||
if (message.type === 16 && message.display_resolution && message.display_resolutions) {
|
||||
this.resolutions.receive(message.display_resolution, message.display_resolutions); this.syncResolutions(); return;
|
||||
const confirmed = this.resolutions.receive(message.display_resolution, message.display_resolutions);
|
||||
if (confirmed) this.resolutionStore.remember(this.resolutionScope, this.connectingId, confirmed);
|
||||
if (!this.resolutions.pending && this.resolutionTimeout >= 0) clearTimeout(this.resolutionTimeout);
|
||||
this.syncResolutions(); return;
|
||||
}
|
||||
if (message.host_info) {
|
||||
const info = message.host_info; this.state.platform = info.platform; this.state.hostName = info.host_name;
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { tr } from './Localization';
|
||||
|
||||
export interface ResolutionMode { width: number; height: number; }
|
||||
export interface ResolutionDisplay extends ResolutionMode {
|
||||
export interface ResolutionPreference extends ResolutionMode { display_key: string; }
|
||||
export interface ResolutionDisplay extends ResolutionPreference {
|
||||
display_id: number; display_key: string; width: number; height: number; modes: ResolutionMode[];
|
||||
}
|
||||
export interface ResolutionRequest extends ResolutionMode {
|
||||
@@ -13,6 +14,51 @@ function validMode(mode: ResolutionMode): boolean {
|
||||
return !!mode && Number.isInteger(mode.width) && Number.isInteger(mode.height) &&
|
||||
mode.width > 0 && mode.width <= 32768 && mode.height > 0 && mode.height <= 32768;
|
||||
}
|
||||
export function validResolutionPreference(value: ResolutionPreference): boolean {
|
||||
if (!validMode(value) || typeof value.display_key !== 'string' || !value.display_key.length ||
|
||||
value.display_key.length > 1024 || value.display_key.includes('\0')) return false;
|
||||
const bytes = Array.from(value.display_key).reduce((n: number, c: string) => {
|
||||
const point = c.codePointAt(0) ?? 0;
|
||||
return n + (point < 128 ? 1 : point < 2048 ? 2 : point < 65536 ? 3 : 4);
|
||||
}, 0);
|
||||
return bytes <= 1024;
|
||||
}
|
||||
export function readResolutionPreferences(text: string): ResolutionPreference[] {
|
||||
if (text.length > 65536) return [];
|
||||
try {
|
||||
const values = JSON.parse(text) as ResolutionPreference[];
|
||||
if (!Array.isArray(values) || values.length > 32) return [];
|
||||
const keys: string[] = [];
|
||||
const result: ResolutionPreference[] = [];
|
||||
for (const value of values) {
|
||||
if (!validResolutionPreference(value) || keys.includes(value.display_key)) return [];
|
||||
keys.push(value.display_key);
|
||||
result.push({ display_key: value.display_key, width: value.width, height: value.height });
|
||||
}
|
||||
return result;
|
||||
} catch { return []; }
|
||||
}
|
||||
export class RemoteResolutionStore {
|
||||
private read: (key: string) => string;
|
||||
private write: (key: string, value: string) => void;
|
||||
constructor(read: (key: string) => string, write: (key: string, value: string) => void) {
|
||||
this.read = read; this.write = write;
|
||||
}
|
||||
load(scope: string, id: string): ResolutionPreference[] {
|
||||
if (!id) return [];
|
||||
try { return readResolutionPreferences(this.read(`resolutions:${JSON.stringify([scope, id])}`)); }
|
||||
catch { return []; }
|
||||
}
|
||||
remember(scope: string, id: string, preference: ResolutionPreference): void {
|
||||
if (!id || !validResolutionPreference(preference)) return;
|
||||
try {
|
||||
const values = this.load(scope, id).filter((value: ResolutionPreference) => value.display_key !== preference.display_key);
|
||||
if (values.length >= 32) values.shift();
|
||||
values.push({ display_key: preference.display_key, width: preference.width, height: preference.height });
|
||||
this.write(`resolutions:${JSON.stringify([scope, id])}`, JSON.stringify(values));
|
||||
} catch { console.warn('Could not save remote display resolution'); }
|
||||
}
|
||||
}
|
||||
export class RemoteDisplayResolutions {
|
||||
displays: ResolutionDisplay[] = [];
|
||||
requested?: ResolutionRequest;
|
||||
@@ -35,13 +81,7 @@ export class RemoteDisplayResolutions {
|
||||
const ids: number[] = [];
|
||||
for (const d of displays) {
|
||||
if (!d || !Number.isInteger(d.display_id) || d.display_id < 0 || d.display_id >= 32 || ids.includes(d.display_id) ||
|
||||
typeof d.display_key !== 'string' || !d.display_key.length || d.display_key.length > 1024 || d.display_key.includes('\0') ||
|
||||
!validMode(d) || !Array.isArray(d.modes) || d.modes.length > 256 || !d.modes.every((m: ResolutionMode) => validMode(m))) return false;
|
||||
const bytes = Array.from(d.display_key).reduce((n: number, c: string) => {
|
||||
const point = c.codePointAt(0) ?? 0;
|
||||
return n + (point < 128 ? 1 : point < 2048 ? 2 : point < 65536 ? 3 : 4);
|
||||
}, 0);
|
||||
if (bytes > 1024) return false;
|
||||
!validResolutionPreference(d) || !Array.isArray(d.modes) || d.modes.length > 256 || !d.modes.every((m: ResolutionMode) => validMode(m))) return false;
|
||||
ids.push(d.display_id);
|
||||
}
|
||||
return true;
|
||||
@@ -54,13 +94,14 @@ export class RemoteDisplayResolutions {
|
||||
d.display_id === request.display_id && d.display_key === request.display_key &&
|
||||
d.modes.some((m: ResolutionMode) => sameMode(m, request)))) this.expire(request.request_id);
|
||||
}
|
||||
receive(response: ResolutionRequest, displays: ResolutionDisplay[]): void {
|
||||
receive(response: ResolutionRequest, displays: ResolutionDisplay[]): ResolutionPreference | undefined {
|
||||
const request = this.requested;
|
||||
if (!request || !response || response.request_id !== request.request_id || response.display_id !== request.display_id ||
|
||||
response.display_key !== request.display_key || !sameMode(response, request) || typeof response.accepted !== 'boolean' || !this.valid(displays)) return;
|
||||
response.display_key !== request.display_key || !sameMode(response, request) || typeof response.accepted !== 'boolean' || !this.valid(displays)) return undefined;
|
||||
this.displays = displays; this.pending = false;
|
||||
this.failed = !response.accepted || !displays.some((d: ResolutionDisplay) =>
|
||||
d.display_id === response.display_id && d.display_key === response.display_key && sameMode(d, response));
|
||||
return this.failed ? undefined : { display_key: response.display_key, width: response.width, height: response.height };
|
||||
}
|
||||
feedback(): string { return this.pending ? tr("等待电脑端确认…") : this.failed ? tr("调整失败,请重试。") : ''; }
|
||||
}
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <mobile_file_transfer_session.h>
|
||||
#include <mobile_video_timing.h>
|
||||
#include <display_stream_id.h>
|
||||
#include <display_resolution_preferences.h>
|
||||
#include <libyuv.h>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
@@ -144,6 +145,7 @@ class MobileSession {
|
||||
s->connecting_ = false; s->connected_ = true;
|
||||
auto info = MakeHostInformation("CrossDesk HarmonyOS Mobile", {}, true, s->version_, HostPlatform::HarmonyOS);
|
||||
info.i.supports_video_codec_status = true;
|
||||
info.preferred_display_resolutions = s->preferred_display_resolutions_;
|
||||
s->Send(info); FreeRemoteAction(info);
|
||||
RemoteAction audio{}; audio.type = audio_capture; audio.a = !s->muted_; s->Send(audio);
|
||||
s->audio_.Start();
|
||||
@@ -238,6 +240,7 @@ class MobileSession {
|
||||
CancelPreview();
|
||||
if (controller_) { MiniRtcLeaveConnection(controller_,remote_.c_str()); MiniRtcDestroyPeer(&controller_); }
|
||||
files_.reset(); renderer_.Clear(); audio_.Stop();
|
||||
preferred_display_resolutions_.clear();
|
||||
if (active) Event({{"type","ended"},{"reason",reason}},true);
|
||||
++connection_; display_=0; frame_id_=0;
|
||||
}
|
||||
@@ -274,6 +277,7 @@ class MobileSession {
|
||||
else if (op=="connect") {
|
||||
if (!online_||login_.empty()) throw std::runtime_error("Not signed in");
|
||||
Disconnect(""); connection_=v.at("connection").get<uint64_t>();
|
||||
ReadDisplayResolutionPreferences(v.value("displayResolutions", json::array()), preferred_display_resolutions_);
|
||||
remote_=v.at("remote").get<std::string>(); const auto password=v.at("password").get<std::string>();
|
||||
if (remote_.size()!=9||!std::all_of(remote_.begin(),remote_.end(),::isdigit)||password.size()>128||password.find('@')!=std::string::npos)
|
||||
throw std::runtime_error("Invalid remote credentials");
|
||||
@@ -405,6 +409,7 @@ class MobileSession {
|
||||
std::unique_ptr<Context> identity_context_,control_context_;
|
||||
std::unique_ptr<mobile_controller::FileTransferSession> files_;
|
||||
std::string server_,logs_,login_,controller_login_,remote_,directory_,preview_directory_,preview_path_,version_;
|
||||
std::vector<DisplayResolutionPreference> preferred_display_resolutions_;
|
||||
int port_=9099,signal_status_=-1;
|
||||
uint64_t signal_generation_=0;
|
||||
std::set<int> keys_;
|
||||
|
||||
Reference in new issue
Block a user