diff --git a/images/chromium-headful/client/src/app.vue b/images/chromium-headful/client/src/app.vue index 060184b11..fa2206849 100644 --- a/images/chromium-headful/client/src/app.vue +++ b/images/chromium-headful/client/src/app.vue @@ -209,6 +209,7 @@ shakeKbd = false wasConnected = false + readOnlyOverride: boolean | null = null get volume() { const numberParam = parseFloat(new URL(location.href).searchParams.get('volume') || '1.0') @@ -224,6 +225,10 @@ } get isReadOnlyMode() { + if (this.readOnlyOverride !== null) { + return this.readOnlyOverride + } + const params = new URL(location.href).searchParams const value = params.get('readOnly') || params.get('readonly') || params.get('ro') return typeof value === 'string' && ['1', 'true', 'yes'].includes(value.toLowerCase()) @@ -326,11 +331,44 @@ } if (this.isReadOnlyMode) { + this.applyReadOnlyMode(true, false) + } + } + + mounted() { + window.addEventListener('message', this.onParentMessage) + } + + beforeDestroy() { + window.removeEventListener('message', this.onParentMessage) + } + + private onParentMessage(event: MessageEvent) { + if (event.source !== window.parent) return + if (this.parentOrigin !== '*' && event.origin !== this.parentOrigin) return + + const data = event.data as { type?: string; readOnly?: unknown } + if (data?.type !== 'KERNEL_SET_READ_ONLY' || typeof data.readOnly !== 'boolean') return + + this.applyReadOnlyMode(data.readOnly, true) + } + + private applyReadOnlyMode(readOnly: boolean, releaseControl: boolean) { + this.readOnlyOverride = readOnly + + if (readOnly) { + if (releaseControl) { + this.$accessor.remote.release() + } // Disable implicit hosting so the user doesn't automatically gain control this.$accessor.remote.setImplicitHosting(false) // Lock the session locally to block any input even if hosting is later requested this.$accessor.remote.setLocked(true) + return } + + this.$accessor.remote.setLocked(false) + this.$accessor.remote.setImplicitHosting(true) } // KERNEL: end custom resolution, frame rate, and readOnly control via query params diff --git a/images/chromium-headful/client/src/neko/base.ts b/images/chromium-headful/client/src/neko/base.ts index d72fc069c..5bacc96fe 100644 --- a/images/chromium-headful/client/src/neko/base.ts +++ b/images/chromium-headful/client/src/neko/base.ts @@ -407,6 +407,21 @@ export abstract class BaseClient extends EventEmitter { this.emit('error', (event as ErrorEvent).error) } + private postParentMessage(message: Record) { + if (window.parent === window) { + return + } + + let targetOrigin = '*' + try { + if (document.referrer) { + targetOrigin = new URL(document.referrer).origin + } + } catch (e) {} + + window.parent.postMessage(message, targetOrigin) + } + private onConnected() { if (this._timeout) { clearTimeout(this._timeout) @@ -424,6 +439,14 @@ export abstract class BaseClient extends EventEmitter { private onTimeout() { this.emit('debug', `connection timeout`) + this.postParentMessage({ + type: 'KERNEL_CONNECTION_TIMEOUT', + reason: 'connection timeout', + iceConnectionState: this._peer?.iceConnectionState ?? this._state, + connectionState: this._peer?.connectionState, + signalingState: this._peer?.signalingState, + socketOpen: this.socketOpen, + }) if (this._timeout) { clearTimeout(this._timeout) this._timeout = undefined