From 7d2c3526b27a607b8167698bbbd304a484ff1ab7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Sat, 4 Dec 2021 21:47:52 +0100 Subject: [PATCH 1/7] rename func. --- client/src/components/video.vue | 38 +++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/client/src/components/video.vue b/client/src/components/video.vue index f30b505..7372fe8 100644 --- a/client/src/components/video.vue +++ b/client/src/components/video.vue @@ -217,7 +217,7 @@ @Prop(Boolean) readonly hideControls!: boolean private keyboard = GuacamoleKeyboard() - private observer = new ResizeObserver(this.onResise.bind(this)) + private observer = new ResizeObserver(this.onResize.bind(this)) private focused = false private fullscreen = false private startsMuted = true @@ -322,12 +322,12 @@ @Watch('width') onWidthChanged(width: number) { - this.onResise() + this.onResize() } @Watch('height') onHeightChanged(height: number) { - this.onResise() + this.onResize() } @Watch('volume') @@ -384,17 +384,17 @@ } mounted() { - this._container.addEventListener('resize', this.onResise) + this._container.addEventListener('resize', this.onResize) this.onVolumeChanged(this.volume) this.onMutedChanged(this.muted) this.onStreamChanged(this.stream) - this.onResise() + this.onResize() this.observer.observe(this._component) this._player.addEventListener('fullscreenchange', () => { this.fullscreen = document.fullscreenElement !== null - this.onResise() + this.onResize() }) this._video.addEventListener('canplaythrough', () => { @@ -513,7 +513,7 @@ try { await this._video.play() - this.onResise() + this.onResize() } catch (err: any) { this.$log.error(err) } @@ -551,6 +551,10 @@ this.$accessor.remote.toggle() } + requestControl() { + this.$accessor.remote.request() + } + _elementRequestFullscreen(el: HTMLElement) { if (typeof el.requestFullscreen === 'function') { el.requestFullscreen() @@ -576,13 +580,13 @@ requestFullscreen() { // try to fullscreen player element if (this._elementRequestFullscreen(this._player)) { - this.onResise() + this.onResize() return } // fallback to fullscreen video itself (on mobile devices) if (this._elementRequestFullscreen(this._video)) { - this.onResise() + this.onResize() return } } @@ -590,7 +594,7 @@ requestPictureInPicture() { //@ts-ignore this._video.requestPictureInPicture() - this.onResise() + this.onResize() } async onFocus() { @@ -611,9 +615,10 @@ } } - onMousePos(e: MouseEvent) { + sendMousePos(e: MouseEvent) { const { w, h } = this.$accessor.video.resolution const rect = this._overlay.getBoundingClientRect() + this.$client.sendData('mousemove', { x: Math.round((w / rect.width) * (e.clientX - rect.left)), y: Math.round((h / rect.height) * (e.clientY - rect.top)), @@ -625,7 +630,6 @@ if (!this.hosting || this.locked) { return } - this.onMousePos(e) let x = e.deltaX let y = e.deltaY @@ -648,6 +652,8 @@ x = Math.min(Math.max(x, -this.scroll), this.scroll) y = Math.min(Math.max(y, -this.scroll), this.scroll) + this.sendMousePos(e) + if (!this.wheelThrottle) { this.wheelThrottle = true this.$client.sendData('wheel', { x, y }) @@ -667,7 +673,7 @@ return } - this.onMousePos(e) + this.sendMousePos(e) this.$client.sendData('mousedown', { key: e.button + 1 }) } @@ -676,7 +682,7 @@ return } - this.onMousePos(e) + this.sendMousePos(e) this.$client.sendData('mouseup', { key: e.button + 1 }) } @@ -685,7 +691,7 @@ return } - this.onMousePos(e) + this.sendMousePos(e) } onMouseEnter(e: MouseEvent) { @@ -715,7 +721,7 @@ this.focused = false } - onResise() { + onResize() { let height = 0 if (!this.fullscreen) { const { offsetWidth, offsetHeight } = this._component From da200698dd3fa29d2b582be9a3f910ea54375f90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Sat, 4 Dec 2021 21:50:08 +0100 Subject: [PATCH 2/7] move elementRequestFullscreen to utils. --- client/src/components/video.vue | 27 +++------------------------ client/src/utils/index.ts | 21 +++++++++++++++++++++ 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/client/src/components/video.vue b/client/src/components/video.vue index 7372fe8..67932f9 100644 --- a/client/src/components/video.vue +++ b/client/src/components/video.vue @@ -186,6 +186,7 @@ From 8db06a76257ba42e9fa8ad35ae88c03183d961fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Sat, 4 Dec 2021 22:44:13 +0100 Subject: [PATCH 4/7] implement system init. --- client/src/neko/events.ts | 1 + client/src/neko/index.ts | 14 ++++++++++++++ client/src/neko/messages.ts | 9 +++++++++ client/src/store/remote.ts | 7 ++++++- server/internal/types/event/events.go | 1 + server/internal/types/message/messages.go | 6 ++++++ server/internal/websocket/session.go | 18 ++++++++---------- 7 files changed, 45 insertions(+), 11 deletions(-) diff --git a/client/src/neko/events.ts b/client/src/neko/events.ts index a29f580..7b637a6 100644 --- a/client/src/neko/events.ts +++ b/client/src/neko/events.ts @@ -10,6 +10,7 @@ export const EVENT = { // Websocket Events SYSTEM: { + INIT: 'system/init', DISCONNECT: 'system/disconnect', ERROR: 'system/error', }, diff --git a/client/src/neko/index.ts b/client/src/neko/index.ts index b156318..35f057f 100644 --- a/client/src/neko/index.ts +++ b/client/src/neko/index.ts @@ -22,6 +22,8 @@ import { AdminPayload, AdminTargetPayload, AdminLockMessage, + SystemInitPayload, + AdminLockResource, } from './messages' interface NekoEvents extends BaseEvents {} @@ -131,6 +133,18 @@ export class NekoClient extends BaseClient implements EventEmitter { ///////////////////////////// // System Events ///////////////////////////// + protected [EVENT.SYSTEM.INIT]({ implicit_hosting, locks }: SystemInitPayload) { + this.$accessor.remote.setImplicitHosting(implicit_hosting) + + for (const resource in locks) { + this[EVENT.ADMIN.LOCK]({ + event: EVENT.ADMIN.LOCK, + resource: resource as AdminLockResource, + id: locks[resource], + }) + } + } + protected [EVENT.SYSTEM.DISCONNECT]({ message }: SystemMessagePayload) { if (message == 'kicked') { this.$accessor.logout() diff --git a/client/src/neko/messages.ts b/client/src/neko/messages.ts index 2fa31cd..65c3969 100644 --- a/client/src/neko/messages.ts +++ b/client/src/neko/messages.ts @@ -52,6 +52,15 @@ export interface WebSocketMessage { /* SYSTEM MESSAGES/PAYLOADS */ +// system/init +export interface SystemInit extends WebSocketMessage, SystemInitPayload { + event: typeof EVENT.SYSTEM.INIT +} +export interface SystemInitPayload { + implicit_hosting: boolean + locks: Record +} + // system/disconnect // system/error export interface SystemMessage extends WebSocketMessage, SystemMessagePayload { diff --git a/client/src/store/remote.ts b/client/src/store/remote.ts index b76b750..3e0daff 100644 --- a/client/src/store/remote.ts +++ b/client/src/store/remote.ts @@ -12,7 +12,7 @@ export const state = () => ({ id: '', clipboard: '', locked: false, - + implicitHosting: true, keyboardModifierState: -1, }) @@ -49,10 +49,15 @@ export const mutations = mutationTree(state, { state.locked = locked }, + setImplicitHosting(state, val: boolean) { + state.implicitHosting = val + }, + reset(state) { state.id = '' state.clipboard = '' state.locked = false + state.implicitHosting = false state.keyboardModifierState = -1 }, }) diff --git a/server/internal/types/event/events.go b/server/internal/types/event/events.go index c9c9ec1..fca67f8 100644 --- a/server/internal/types/event/events.go +++ b/server/internal/types/event/events.go @@ -1,6 +1,7 @@ package event const ( + SYSTEM_INIT = "system/init" SYSTEM_DISCONNECT = "system/disconnect" SYSTEM_ERROR = "system/error" ) diff --git a/server/internal/types/message/messages.go b/server/internal/types/message/messages.go index 4c40caa..5d8944f 100644 --- a/server/internal/types/message/messages.go +++ b/server/internal/types/message/messages.go @@ -10,6 +10,12 @@ type Message struct { Event string `json:"event"` } +type SystemInit struct { + Event string `json:"event"` + ImplicitHosting bool `json:"implicit_hosting"` + Locks map[string]string `json:"locks"` +} + type SystemMessage struct { Event string `json:"event"` Title string `json:"title"` diff --git a/server/internal/websocket/session.go b/server/internal/websocket/session.go index 534f0e4..3b24650 100644 --- a/server/internal/websocket/session.go +++ b/server/internal/websocket/session.go @@ -12,16 +12,14 @@ func (h *MessageHandler) SessionCreated(id string, session types.Session) error return err } - // notify all about what is locked - for resource, id := range h.locked { - if err := session.Send(message.AdminLock{ - Event: event.ADMIN_LOCK, - ID: id, - Resource: resource, - }); err != nil { - h.logger.Warn().Str("id", id).Err(err).Msgf("sending event %s has failed", event.ADMIN_LOCK) - return err - } + // send initialization information + if err := session.Send(message.SystemInit{ + Event: event.SYSTEM_INIT, + ImplicitHosting: true, + Locks: h.locked, + }); err != nil { + h.logger.Warn().Str("id", id).Err(err).Msgf("sending event %s has failed", event.SYSTEM_INIT) + return err } if session.Admin() { From 42fdc43ff5f841d53c994d4f0e620a1268e70a4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Sat, 11 Dec 2021 13:52:31 +0100 Subject: [PATCH 5/7] add admin broadcast. --- server/internal/session/manager.go | 24 ++++++++++++++++++++++++ server/internal/types/session.go | 1 + server/internal/websocket/broadcast.go | 19 +++++++++++++++++-- 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/server/internal/session/manager.go b/server/internal/session/manager.go index a06b147..2e77bf4 100644 --- a/server/internal/session/manager.go +++ b/server/internal/session/manager.go @@ -184,6 +184,30 @@ func (manager *SessionManager) Broadcast(v interface{}, exclude interface{}) err return err } } + + return nil +} + +func (manager *SessionManager) AdminBroadcast(v interface{}, exclude interface{}) error { + manager.mu.Lock() + defer manager.mu.Unlock() + + for id, session := range manager.members { + if !session.connected || !session.admin { + continue + } + + if exclude != nil { + if in, _ := utils.ArrayIn(id, exclude); in { + continue + } + } + + if err := session.Send(v); err != nil { + return err + } + } + return nil } diff --git a/server/internal/types/session.go b/server/internal/types/session.go index da4eb41..3e765e6 100644 --- a/server/internal/types/session.go +++ b/server/internal/types/session.go @@ -43,6 +43,7 @@ type SessionManager interface { Destroy(id string) Clear() error Broadcast(v interface{}, exclude interface{}) error + AdminBroadcast(v interface{}, exclude interface{}) error OnHost(listener func(id string)) OnHostCleared(listener func(id string)) OnDestroy(listener func(id string, session Session)) diff --git a/server/internal/websocket/broadcast.go b/server/internal/websocket/broadcast.go index 1eb8bcd..865c031 100644 --- a/server/internal/websocket/broadcast.go +++ b/server/internal/websocket/broadcast.go @@ -25,7 +25,7 @@ func (h *MessageHandler) boradcastCreate(session types.Session, payload *message } } - if err := h.boradcastStatus(session); err != nil { + if err := h.boradcastStatus(nil); err != nil { return err } @@ -40,7 +40,7 @@ func (h *MessageHandler) boradcastDestroy(session types.Session) error { h.broadcast.Destroy() - if err := h.boradcastStatus(session); err != nil { + if err := h.boradcastStatus(nil); err != nil { return err } @@ -48,6 +48,21 @@ func (h *MessageHandler) boradcastDestroy(session types.Session) error { } func (h *MessageHandler) boradcastStatus(session types.Session) error { + // if no session, broadcast change + if session == nil { + if err := h.sessions.AdminBroadcast( + message.BroadcastStatus{ + Event: event.BORADCAST_STATUS, + IsActive: h.broadcast.IsActive(), + URL: h.broadcast.GetUrl(), + }, nil); err != nil { + h.logger.Warn().Err(err).Msgf("broadcasting event %s has failed", event.BORADCAST_STATUS) + return err + } + + return nil + } + if !session.Admin() { h.logger.Debug().Msg("user not admin") return nil From c00c30e2111de212cd62b76cad92e15b42b3652e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Sat, 11 Dec 2021 14:04:03 +0100 Subject: [PATCH 6/7] fix keyboard sync. --- client/src/components/video.vue | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/client/src/components/video.vue b/client/src/components/video.vue index 7f3a705..c823a30 100644 --- a/client/src/components/video.vue +++ b/client/src/components/video.vue @@ -378,9 +378,14 @@ } @Watch('clipboard') - onClipboardChanged(clipboard: string) { + async onClipboardChanged(clipboard: string) { if (this.clipboard_write_available) { - navigator.clipboard.writeText(clipboard).catch(console.error) + try { + await navigator.clipboard.writeText(clipboard) + this.$accessor.remote.setClipboard(clipboard) + } catch (err: any) { + this.$log.error(err) + } } } @@ -434,8 +439,6 @@ this.$accessor.video.pause() }) - document.addEventListener('focusin', this.onFocus.bind(this)) - /* Initialize Guacamole Keyboard */ this.keyboard.onkeydown = (key: number) => { if (!this.focused || !this.hosting || this.locked) { @@ -458,7 +461,6 @@ beforeDestroy() { this.observer.disconnect() this.$accessor.video.setPlayable(false) - document.removeEventListener('focusin', this.onFocus.bind(this)) /* Guacamole Keyboard does not provide destroy functions */ } @@ -584,12 +586,8 @@ this._clipboard.open() } - async onFocus() { - if (!document.hasFocus() || !this.$accessor.active) { - return - } - - if (this.hosting && this.clipboard_read_available) { + async syncClipboard() { + if (this.clipboard_read_available) { try { const text = await navigator.clipboard.readText() if (this.clipboard !== text) { @@ -688,10 +686,11 @@ numLock: e.getModifierState('NumLock'), scrollLock: e.getModifierState('ScrollLock'), }) + + this.syncClipboard() } this._overlay.focus() - this.onFocus() this.focused = true } From 22fd5ee1c9e5dcc267c7cd62fa9bfe8ec09a5212 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Sat, 11 Dec 2021 14:15:52 +0100 Subject: [PATCH 7/7] update changelog. --- docs/changelog.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/changelog.md b/docs/changelog.md index e851e3f..9217523 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -4,10 +4,12 @@ ### New Features - Added `m1k1o/neko:microsoft-edge` tag. +- Fixed clipboard sync in chromium based browsers. ### Misc - Automatic WebRTC SDP negotiation using onnegotiationneeded handlers. This allows adding/removing track on demand in a session. - Added UDP and TCP mux for WebRTC connection. It should handle multiple peers. +- Broadcast status change is sent to all admins now. ## [n.eko v2.5](https://github.com/m1k1o/neko/releases/tag/v2.5)