mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-10-28 00:33:06 +08:00
[feat] add privacy unlock controls and remote relocking
This commit is contained in:
@@ -81,6 +81,7 @@ export class MobileState {
|
||||
privacyEnabled: boolean = false;
|
||||
privacyActive: boolean = false;
|
||||
privacyCanToggle: boolean = false;
|
||||
privacyCanLock: boolean = false;
|
||||
privacyVisible: boolean = true;
|
||||
networkTime: number = 0;
|
||||
unread: number = 0;
|
||||
@@ -266,9 +267,9 @@ export class MobileController {
|
||||
if (this.foreground && this.state.consent) this.checkLocal();
|
||||
}
|
||||
release(): void { this.releaseInput(); this.send({ op: 'release' }); }
|
||||
pointer(x: number, y: number, flag: number, scroll: number): void { this.send({ op: 'pointer', x: x, y: y, flag: flag, scroll: scroll }); }
|
||||
physicalKey(code: number, down: boolean): void { this.send({ op: 'key', code: code, down: down }); }
|
||||
key(code: number, down: boolean): void { this.send({ op: 'virtualKey', code: code, down: down }); }
|
||||
pointer(x: number, y: number, flag: number, scroll: number): void { if (this.state.modal === 'privacyUnlock') return; this.send({ op: 'pointer', x: x, y: y, flag: flag, scroll: scroll }); }
|
||||
physicalKey(code: number, down: boolean): void { if (this.state.modal === 'privacyUnlock') return; this.send({ op: 'key', code: code, down: down }); }
|
||||
key(code: number, down: boolean): void { if (this.state.modal === 'privacyUnlock') return; this.send({ op: 'virtualKey', code: code, down: down }); }
|
||||
chord(code: number, modifiers: number[] = []): void {
|
||||
modifiers.forEach((key: number) => this.key(key, true)); this.key(code, true); this.key(code, false);
|
||||
modifiers.slice().reverse().forEach((key: number) => this.key(key, false));
|
||||
@@ -352,11 +353,17 @@ export class MobileController {
|
||||
this.state.documentTitle = title; this.state.documentBack = back; this.state.page = 'document'; this.state.modal = '';
|
||||
} catch { this.toast('无法读取文档'); }
|
||||
}
|
||||
togglePrivacy(): void {
|
||||
dismissPrivacyUnlock(): void {
|
||||
this.privacyScreen.unlockNotice = false;
|
||||
if (this.state.modal === 'privacyUnlock') this.state.modal = '';
|
||||
}
|
||||
lockPrivacy(): void { this.sendPrivacy(true); }
|
||||
togglePrivacy(): void { this.sendPrivacy(false); }
|
||||
private sendPrivacy(lock: boolean): void {
|
||||
if (!this.state.ready || !this.state.consent) return;
|
||||
const enable = !this.privacyScreen.active(), request = this.privacyScreen.begin(this.now());
|
||||
const enable = !this.privacyScreen.active(), request = this.privacyScreen.begin(this.now(), lock);
|
||||
if (request < 0) return;
|
||||
this.send({ op: 'privacy', flag: enable ? 1 : 2, request: request,
|
||||
this.send({ op: 'privacy', flag: lock ? 3 : enable ? 1 : 2, request: request,
|
||||
blockLocalInput: true });
|
||||
this.syncPrivacy();
|
||||
}
|
||||
@@ -365,6 +372,13 @@ export class MobileController {
|
||||
this.state.privacyStatus = this.privacyScreen.status(now); this.state.privacyFeedback = this.privacyScreen.feedback(now);
|
||||
this.state.privacyButtonTitle = this.privacyScreen.buttonTitle(now);
|
||||
this.state.privacyVisible = this.privacyScreen.desktopAvailable;
|
||||
this.state.privacyCanLock = this.privacyScreen.canLock(now);
|
||||
if (this.foreground && this.state.ready && this.privacyScreen.unlockNotice && !this.state.modal) {
|
||||
this.release();
|
||||
this.state.modal = 'privacyUnlock';
|
||||
} else if (!this.privacyScreen.unlockNotice && this.state.modal === 'privacyUnlock') {
|
||||
this.state.modal = '';
|
||||
}
|
||||
this.state.privacyEnabled = this.privacyScreen.enabled(now);
|
||||
this.state.privacyActive = this.privacyScreen.active(); this.state.privacyCanToggle = this.privacyScreen.canToggle(now);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
export interface PrivacyStatus {
|
||||
state: number; supported: boolean; input_block_supported: boolean;
|
||||
overlay_active: boolean; input_blocked: boolean; remote_paused: boolean;
|
||||
revision: number; reason: string;
|
||||
revision: number; reason: string; local_unlock_revision?: number;
|
||||
}
|
||||
|
||||
export class RemotePrivacyScreen {
|
||||
desktopAvailable: boolean = true;
|
||||
snapshot?: PrivacyStatus;
|
||||
pending: boolean = false;
|
||||
unlockNotice: boolean = false;
|
||||
requestId: number = 0;
|
||||
private requestRevision: number = 0;
|
||||
private receivedAt: number = 0;
|
||||
@@ -26,6 +27,10 @@ export class RemotePrivacyScreen {
|
||||
typeof s.remote_paused !== 'boolean' || typeof s.reason !== 'string' || s.reason.includes('\0')) return;
|
||||
if (s.state === 3 && (!s.supported || !s.overlay_active || s.remote_paused)) return;
|
||||
if (this.snapshot && ((s.revision - this.snapshot.revision) | 0) < 0) return;
|
||||
const unlock = s.local_unlock_revision ?? 0;
|
||||
if (!Number.isInteger(unlock) || unlock < 0 || unlock > 0xffffffff) return;
|
||||
if (this.snapshot && ((unlock - (this.snapshot.local_unlock_revision ?? 0)) | 0) > 0) this.unlockNotice = true;
|
||||
if (s.state === 3) this.unlockNotice = false;
|
||||
this.snapshot = s; this.receivedAt = now; this.failure = '';
|
||||
if (this.pending && ((s.revision - this.requestRevision) | 0) > 0 && s.state !== 2 && s.state !== 4) this.pending = false;
|
||||
}
|
||||
@@ -57,9 +62,12 @@ export class RemotePrivacyScreen {
|
||||
const s = this.snapshot;
|
||||
return this.desktopAvailable && s !== undefined && !this.pending && this.fresh(now) && s.state !== 4 && (this.active() || s.supported && s.state === 1);
|
||||
}
|
||||
begin(now: number): number {
|
||||
canLock(now: number): boolean {
|
||||
return this.desktopAvailable && !this.pending && this.fresh(now) && this.snapshot?.supported === true;
|
||||
}
|
||||
begin(now: number, lock: boolean = false): number {
|
||||
this.tick(now);
|
||||
if (!this.canToggle(now) || !this.snapshot) return -1;
|
||||
if (!(lock ? this.canLock(now) : this.canToggle(now)) || !this.snapshot) return -1;
|
||||
this.pending = true; this.requestedAt = now; this.requestRevision = this.snapshot.revision; this.failure = '';
|
||||
this.requestId = (this.requestId + 1) >>> 0;
|
||||
return this.requestId;
|
||||
|
||||
@@ -91,6 +91,7 @@ struct Mobile {
|
||||
return saved;
|
||||
}
|
||||
private dismissModal(): void {
|
||||
if (this.state.modal === 'privacyUnlock') this.controller?.dismissPrivacyUnlock();
|
||||
if (this.state.modal === 'password') { this.state.password = ''; this.showPassword = false; }
|
||||
this.previewToggle = this.state.previews; this.state.modal = '';
|
||||
}
|
||||
@@ -537,6 +538,21 @@ struct Mobile {
|
||||
}.padding({ left: 26, right: 26, top: 24, bottom: 20 }).width('100%')
|
||||
this.Rule()
|
||||
this.Action('取消连接', () => this.controller?.end(''), true)
|
||||
} else if (this.state.modal === 'privacyUnlock') {
|
||||
Column({ space: 12 }) {
|
||||
Text('隐私屏已被解锁').fontSize(20).fontWeight(600).fontColor(MobileTheme.ink)
|
||||
Text('被控端已通过组合键解除隐私屏。点击“锁定”将重新开启隐私屏,并禁止本机使用组合键解锁。')
|
||||
.fontSize(14).lineHeight(21).fontColor(MobileTheme.secondary).width('100%')
|
||||
if (this.state.privacyFeedback) { Text(this.state.privacyFeedback).fontSize(12).fontColor(MobileTheme.secondary) }
|
||||
}.padding(24).width('100%')
|
||||
this.Rule()
|
||||
Row() {
|
||||
Button('确认', { type: ButtonType.Normal }).fontSize(16).fontColor(MobileTheme.blue).height(50).layoutWeight(1)
|
||||
.backgroundColor(Color.White).onClick(() => this.dismissModal())
|
||||
Divider().vertical(true).height(50).strokeWidth(0.5).color(MobileTheme.line)
|
||||
Button('锁定', { type: ButtonType.Normal }).fontSize(16).fontWeight(600).fontColor(MobileTheme.blue).height(50).layoutWeight(1)
|
||||
.backgroundColor(Color.White).enabled(this.state.privacyCanLock).onClick(() => this.controller?.lockPrivacy())
|
||||
}.width('100%')
|
||||
} else if (this.state.modal === 'clipboard') {
|
||||
Column({ space: 16 }) {
|
||||
this.SheetHeader('剪贴板')
|
||||
|
||||
Reference in New Issue
Block a user