diff --git a/images/chromium-headful/client/src/components/video.vue b/images/chromium-headful/client/src/components/video.vue index 2a8a8c8f..2c5955c9 100644 --- a/images/chromium-headful/client/src/components/video.vue +++ b/images/chromium-headful/client/src/components/video.vue @@ -280,6 +280,10 @@ return this.$accessor.connecting } + get controlling() { + return this.$accessor.remote.controlling + } + get hosting() { return this.$accessor.remote.hosting } @@ -807,6 +811,10 @@ onMouseDown(e: MouseEvent) { this.unmuteOnInteraction() + if (!this.controlling && this.implicitHosting && !this.locked) { + this.$accessor.remote.request() + } + if (!this.hosting) { this.$emit('control-attempt', e) } diff --git a/images/chromium-headful/client/src/neko/index.ts b/images/chromium-headful/client/src/neko/index.ts index 92df801d..3571bb88 100644 --- a/images/chromium-headful/client/src/neko/index.ts +++ b/images/chromium-headful/client/src/neko/index.ts @@ -240,17 +240,15 @@ export class NekoClient extends BaseClient implements EventEmitter { return } - if (this.id === id) { - this.$vue.$notify({ - group: 'neko', - type: 'info', - title: this.$vue.$t('notifications.controls_taken', { - name: member.id == this.id && this.$vue.$te('you') ? this.$vue.$t('you') : member.displayname, - }) as string, - duration: 5000, - speed: 1000, - }) - } + // KERNEL: drop the self-only "you took the controls" toast (was gated on + // this.id === id). With implicit hosting, control is now registered + // automatically on the first interaction (see remote.request() in + // video.vue), so it would fire on essentially every session's first click + // — pure noise for a single-viewer embedded live view. The chat event + // below (and other members' take/steal toasts elsewhere) is unaffected. + // Note: this is a shared component, so explicit control-takes in the full + // neko UI also lose this confirmation; acceptable since Kernel only ships + // the embedded live view. this.$accessor.chat.newMessage({ id: member.id, diff --git a/images/chromium-headful/client/src/store/remote.ts b/images/chromium-headful/client/src/store/remote.ts index c8e9d282..52c53d90 100644 --- a/images/chromium-headful/client/src/store/remote.ts +++ b/images/chromium-headful/client/src/store/remote.ts @@ -15,9 +15,16 @@ export const state = () => ({ implicitHosting: true, fileTransfer: true, keyboardModifierState: -1, + // true once a control/request has been sent and we're waiting for the + // server's control/locked response. Prevents rapid clicks from emitting + // multiple requests before `controlling` flips. + requesting: false, }) export const getters = getterTree(state, { + controlling: (state, getters, root) => { + return root.user.id !== '' && root.user.id === state.id + }, hosting: (state, getters, root) => { return root.user.id === state.id || state.implicitHosting }, @@ -36,6 +43,8 @@ export const mutations = mutationTree(state, { } else { state.id = host.id } + // host resolved (for us or someone else) — clear any pending request + state.requesting = false }, setClipboard(state, clipboard: string) { @@ -58,10 +67,15 @@ export const mutations = mutationTree(state, { state.fileTransfer = val }, + setRequesting(state, val: boolean) { + state.requesting = val + }, + reset(state) { state.id = '' state.clipboard = '' state.locked = false + state.requesting = false }, }) @@ -88,11 +102,12 @@ export const actions = actionTree( } }, - request({ getters }) { - if (!accessor.connected || getters.hosting) { + request({ state, getters }) { + if (!accessor.connected || getters.controlling || state.requesting) { return } + accessor.remote.setRequesting(true) $client.sendMessage(EVENT.CONTROL.REQUEST) },