diff --git a/client/src/components/context.vue b/client/src/components/context.vue
index d5c15a3b..aa6ac3c1 100644
--- a/client/src/components/context.vue
+++ b/client/src/components/context.vue
@@ -18,20 +18,20 @@
{{ $t('context.mute') }}
{{ $t('context.unmute') }}
-
+
{{ $t('context.release') }}
-
+
{{ $t('context.take') }}
- {{
+ {{
$t('context.give')
}}
-
+
{{ $t('context.give') }}
@@ -161,6 +161,10 @@
return this.$accessor.remote.id
}
+ get implicitHosting() {
+ return this.$accessor.remote.implicitHosting
+ }
+
open(event: MouseEvent, data: any) {
this.context.open(event, data)
}
diff --git a/client/src/components/controls.vue b/client/src/components/controls.vue
index b191d39d..eb6174ec 100644
--- a/client/src/components/controls.vue
+++ b/client/src/components/controls.vue
@@ -1,6 +1,6 @@
- -
+
-
- -
+
-
+
+
+ -
@@ -105,6 +117,10 @@
font-size: 24px;
cursor: pointer;
+ &.no-pointer {
+ cursor: default;
+ }
+
i {
padding: 0 5px;
@@ -242,12 +258,8 @@
export default class extends Vue {
@Prop(Boolean) readonly shakeKbd!: boolean
- get severLocked(): boolean {
- return 'control' in this.$accessor.locked && this.$accessor.locked['control']
- }
-
- get seesControl(): boolean {
- return !this.severLocked || this.$accessor.user.admin || this.hosting
+ get controlLocked() {
+ return 'control' in this.$accessor.locked && this.$accessor.locked['control'] && !this.$accessor.user.admin
}
get disabeld() {
@@ -258,6 +270,10 @@
return this.$accessor.remote.hosting
}
+ get implicitHosting() {
+ return this.$accessor.remote.implicitHosting
+ }
+
get volume() {
return this.$accessor.video.volume
}
diff --git a/client/src/components/video.vue b/client/src/components/video.vue
index c823a300..4ff014a6 100644
--- a/client/src/components/video.vue
+++ b/client/src/components/video.vue
@@ -240,6 +240,10 @@
return this.$accessor.remote.hosting
}
+ get implicitHosting() {
+ return this.$accessor.remote.implicitHosting
+ }
+
get hosted() {
return this.$accessor.remote.hosted
}
@@ -272,8 +276,13 @@
return this.$accessor.settings.autoplay
}
+ // server-side lock
+ get controlLocked() {
+ return 'control' in this.$accessor.locked && this.$accessor.locked['control'] && !this.$accessor.user.admin
+ }
+
get locked() {
- return this.$accessor.remote.locked
+ return this.$accessor.remote.locked || (this.controlLocked && (!this.hosting || this.implicitHosting))
}
get scroll() {
diff --git a/client/src/locale/en-us.ts b/client/src/locale/en-us.ts
index cce19ce5..91d98d9c 100644
--- a/client/src/locale/en-us.ts
+++ b/client/src/locale/en-us.ts
@@ -49,6 +49,8 @@ export const controls = {
request: 'Request Controls',
lock: 'Lock Controls',
unlock: 'Unlock Controls',
+ has: 'You have control',
+ hasnot: 'You do not have control',
}
export const locks = {
diff --git a/client/src/locale/es-sp.ts b/client/src/locale/es-sp.ts
index 06846799..3c33b1d3 100644
--- a/client/src/locale/es-sp.ts
+++ b/client/src/locale/es-sp.ts
@@ -51,6 +51,9 @@ export const controls = {
request: 'Controles solicitados',
lock: 'Controles bloqueados',
unlock: 'Controles desbloqueados',
+ // TODO
+ //has: 'You have control',
+ //hasnot: 'You do not have control',
}
export const locks = {
diff --git a/client/src/locale/fr-fr.ts b/client/src/locale/fr-fr.ts
index 2261c011..7a7c9c67 100644
--- a/client/src/locale/fr-fr.ts
+++ b/client/src/locale/fr-fr.ts
@@ -46,6 +46,16 @@ export const context = {
},
}
+export const controls = {
+ release: 'Relacher le contrôle',
+ request: 'Demander le contrôle',
+ lock: 'Vérouiller le contrôle',
+ unlock: 'Débloquer le contrôle',
+ // TODO
+ // has: 'You have control',
+ // hasnot: 'You do not have control',
+}
+
export const locks = {
// TODO
//control: {
@@ -57,22 +67,15 @@ export const locks = {
// notif_unlocked: 'unlocked controls for users',
//},
login: {
- release: 'Relacher le contrôle',
- request: 'Demander le contrôle',
- lock: 'Vérouiller le contrôle',
- unlock: 'Débloquer le contrôle',
+ lock: 'Vérouiller la salle (pour les utilisateurs)',
+ unlock: 'Dévérouiller la salle (pour les utilisateurs)',
+ locked: 'Salle vérouillée (pour les utilisateurs)',
+ unlocked: 'Salle dévérouillée (pour les utilisateurs)',
notif_locked: 'a vérouillé la salle',
notif_unlocked: 'a dévérouillé la salle',
},
}
-export const room = {
- lock: 'Vérouiller la salle (pour les utilisateurs)',
- unlock: 'Dévérouiller la salle (pour les utilisateurs)',
- locked: 'Salle vérouillée (pour les utilisateurs)',
- unlocked: 'Salle dévérouillée (pour les utilisateurs)',
-}
-
export const setting = {
scroll: 'Sensibilité de défilement (scroll)',
scroll_invert: 'Inverser le défilement (scroll)',
diff --git a/client/src/locale/nb-no.ts b/client/src/locale/nb-no.ts
index 9359b7ad..f19e9999 100644
--- a/client/src/locale/nb-no.ts
+++ b/client/src/locale/nb-no.ts
@@ -51,6 +51,9 @@ export const controls = {
request: 'Forespør kontroll',
lock: 'Lås kontrollen',
unlock: 'Lås opp kontrollen',
+ // TODO
+ //has: 'You have control',
+ //hasnot: 'You do not have control',
}
export const locks = {
diff --git a/client/src/locale/sk-sk.ts b/client/src/locale/sk-sk.ts
index 16116635..93fe5c9c 100644
--- a/client/src/locale/sk-sk.ts
+++ b/client/src/locale/sk-sk.ts
@@ -51,6 +51,9 @@ export const controls = {
request: 'Požiadať o ovládanie',
lock: 'Zamknúť ovládanie',
unlock: 'Odomknúť ovládanie',
+ // TODO
+ //has: 'You have control',
+ //hasnot: 'You do not have control',
}
export const locks = {
diff --git a/client/src/locale/sv-se.ts b/client/src/locale/sv-se.ts
index ceb1af44..ad9904ab 100644
--- a/client/src/locale/sv-se.ts
+++ b/client/src/locale/sv-se.ts
@@ -51,6 +51,9 @@ export const controls = {
request: 'Fråga om kontroll',
lock: 'Lås kontrollen',
unlock: 'Lås upp kontrollen',
+ // TODO
+ //has: 'You have control',
+ //hasnot: 'You do not have control',
}
export const locks = {
diff --git a/client/src/store/remote.ts b/client/src/store/remote.ts
index 3e0daff6..c6834f7c 100644
--- a/client/src/store/remote.ts
+++ b/client/src/store/remote.ts
@@ -18,13 +18,13 @@ export const state = () => ({
export const getters = getterTree(state, {
hosting: (state, getters, root) => {
- return root.user.id === state.id
+ return root.user.id === state.id || state.implicitHosting
},
hosted: (state, getters, root) => {
- return state.id !== ''
+ return state.id !== '' || state.implicitHosting
},
host: (state, getters, root) => {
- return root.user.members[state.id] || null
+ return root.user.members[state.id] || (state.implicitHosting && root.user.id) || null
},
})
@@ -57,8 +57,6 @@ export const mutations = mutationTree(state, {
state.id = ''
state.clipboard = ''
state.locked = false
- state.implicitHosting = false
- state.keyboardModifierState = -1
},
})
diff --git a/docs/changelog.md b/docs/changelog.md
index 92175237..e5fce1ce 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -5,6 +5,7 @@
### New Features
- Added `m1k1o/neko:microsoft-edge` tag.
- Fixed clipboard sync in chromium based browsers.
+- Added support for implicit control (using `NEKO_IMPLICITCONTROL=1`). That means, users do not need to request control prior usage.
### Misc
- Automatic WebRTC SDP negotiation using onnegotiationneeded handlers. This allows adding/removing track on demand in a session.
diff --git a/server/internal/session/manager.go b/server/internal/session/manager.go
index 2e77bf42..b17d5d8e 100644
--- a/server/internal/session/manager.go
+++ b/server/internal/session/manager.go
@@ -29,6 +29,8 @@ type SessionManager struct {
remote types.RemoteManager
members map[string]*Session
emmiter events.EventEmmiter
+ // TODO: Handle locks in sessions as flags.
+ controlLocked bool
}
func (manager *SessionManager) New(id string, admin bool, socket types.WebSocket) types.Session {
@@ -104,6 +106,16 @@ func (manager *SessionManager) Get(id string) (types.Session, bool) {
return session, ok
}
+// TODO: Handle locks in sessions as flags.
+func (manager *SessionManager) SetControlLocked(locked bool) {
+ manager.controlLocked = locked
+}
+
+func (manager *SessionManager) CanControl(id string) bool {
+ session, ok := manager.Get(id)
+ return ok && (!manager.controlLocked || session.Admin())
+}
+
func (manager *SessionManager) Admins() []*types.Member {
manager.mu.Lock()
defer manager.mu.Unlock()
diff --git a/server/internal/types/config/webrtc.go b/server/internal/types/config/webrtc.go
index dfdfbfbe..2d05ac32 100644
--- a/server/internal/types/config/webrtc.go
+++ b/server/internal/types/config/webrtc.go
@@ -22,6 +22,8 @@ type WebRTC struct {
NAT1To1IPs []string
TCPMUX int
UDPMUX int
+
+ ImplicitControl bool
}
func (WebRTC) Init(cmd *cobra.Command) error {
@@ -65,6 +67,12 @@ func (WebRTC) Init(cmd *cobra.Command) error {
return err
}
+ // TODO: Should be moved to session config.
+ cmd.PersistentFlags().Bool("implicitcontrol", false, "if enabled members can gain control implicitly")
+ if err := viper.BindPFlag("implicitcontrol", cmd.PersistentFlags().Lookup("implicitcontrol")); err != nil {
+ return err
+ }
+
return nil
}
@@ -120,4 +128,7 @@ func (s *WebRTC) Set() {
s.EphemeralMin = min
s.EphemeralMax = max
}
+
+ // TODO: Should be moved to session config.
+ s.ImplicitControl = viper.GetBool("implicitcontrol")
}
diff --git a/server/internal/types/session.go b/server/internal/types/session.go
index 3e765e63..04a4626c 100644
--- a/server/internal/types/session.go
+++ b/server/internal/types/session.go
@@ -38,6 +38,8 @@ type SessionManager interface {
ClearHost()
Has(id string) bool
Get(id string) (Session, bool)
+ SetControlLocked(locked bool)
+ CanControl(id string) bool
Members() []*Member
Admins() []*Member
Destroy(id string)
diff --git a/server/internal/types/webrtc.go b/server/internal/types/webrtc.go
index bda3524a..26595607 100644
--- a/server/internal/types/webrtc.go
+++ b/server/internal/types/webrtc.go
@@ -13,6 +13,7 @@ type WebRTCManager interface {
CreatePeer(id string, session Session) (Peer, error)
ICELite() bool
ICEServers() []webrtc.ICEServer
+ ImplicitControl() bool
}
type Peer interface {
diff --git a/server/internal/webrtc/handle.go b/server/internal/webrtc/handle.go
index 4f28fc69..c75fffb5 100644
--- a/server/internal/webrtc/handle.go
+++ b/server/internal/webrtc/handle.go
@@ -39,7 +39,7 @@ type PayloadKey struct {
}
func (manager *WebRTCManager) handle(id string, msg webrtc.DataChannelMessage) error {
- if !manager.sessions.IsHost(id) {
+ if (!manager.config.ImplicitControl && !manager.sessions.IsHost(id)) || (manager.config.ImplicitControl && !manager.sessions.CanControl(id)) {
return nil
}
diff --git a/server/internal/webrtc/webrtc.go b/server/internal/webrtc/webrtc.go
index 78978ab0..44748b74 100644
--- a/server/internal/webrtc/webrtc.go
+++ b/server/internal/webrtc/webrtc.go
@@ -301,6 +301,10 @@ func (manager *WebRTCManager) ICEServers() []webrtc.ICEServer {
return manager.config.ICEServers
}
+func (manager *WebRTCManager) ImplicitControl() bool {
+ return manager.config.ImplicitControl
+}
+
func (manager *WebRTCManager) createTrack(codecName string) (*webrtc.TrackLocalStaticSample, webrtc.RTPCodecParameters, error) {
var codec webrtc.RTPCodecParameters
diff --git a/server/internal/websocket/admin.go b/server/internal/websocket/admin.go
index b7786b9f..72e38a43 100644
--- a/server/internal/websocket/admin.go
+++ b/server/internal/websocket/admin.go
@@ -25,6 +25,11 @@ func (h *MessageHandler) adminLock(id string, session types.Session, payload *me
return nil
}
+ // TODO: Handle locks in sessions as flags.
+ if payload.Resource == "control" {
+ h.sessions.SetControlLocked(true)
+ }
+
h.locked[payload.Resource] = id
if err := h.sessions.Broadcast(
@@ -52,6 +57,11 @@ func (h *MessageHandler) adminUnlock(id string, session types.Session, payload *
return nil
}
+ // TODO: Handle locks in sessions as flags.
+ if payload.Resource == "control" {
+ h.sessions.SetControlLocked(false)
+ }
+
delete(h.locked, payload.Resource)
if err := h.sessions.Broadcast(
diff --git a/server/internal/websocket/control.go b/server/internal/websocket/control.go
index 8774b515..2f100169 100644
--- a/server/internal/websocket/control.go
+++ b/server/internal/websocket/control.go
@@ -125,9 +125,9 @@ func (h *MessageHandler) controlGive(id string, session types.Session, payload *
}
func (h *MessageHandler) controlClipboard(id string, session types.Session, payload *message.Clipboard) error {
- // check if session is host
- if !h.sessions.IsHost(id) {
- h.logger.Debug().Str("id", id).Msg("is not the host")
+ // check if session can access clipboard
+ if (!h.webrtc.ImplicitControl() && !h.sessions.IsHost(id)) || (h.webrtc.ImplicitControl() && !h.sessions.CanControl(id)) {
+ h.logger.Debug().Str("id", id).Msg("cannot access clipboard")
return nil
}
@@ -136,9 +136,9 @@ func (h *MessageHandler) controlClipboard(id string, session types.Session, payl
}
func (h *MessageHandler) controlKeyboard(id string, session types.Session, payload *message.Keyboard) error {
- // check if session is host
- if !h.sessions.IsHost(id) {
- h.logger.Debug().Str("id", id).Msg("is not the host")
+ // check if session can control keyboard
+ if (!h.webrtc.ImplicitControl() && !h.sessions.IsHost(id)) || (h.webrtc.ImplicitControl() && !h.sessions.CanControl(id)) {
+ h.logger.Debug().Str("id", id).Msg("cannot control keyboard")
return nil
}
diff --git a/server/internal/websocket/session.go b/server/internal/websocket/session.go
index 3b246506..2f5fb656 100644
--- a/server/internal/websocket/session.go
+++ b/server/internal/websocket/session.go
@@ -15,7 +15,7 @@ func (h *MessageHandler) SessionCreated(id string, session types.Session) error
// send initialization information
if err := session.Send(message.SystemInit{
Event: event.SYSTEM_INIT,
- ImplicitHosting: true,
+ ImplicitHosting: h.webrtc.ImplicitControl(),
Locks: h.locked,
}); err != nil {
h.logger.Warn().Str("id", id).Err(err).Msgf("sending event %s has failed", event.SYSTEM_INIT)
diff --git a/server/internal/websocket/websocket.go b/server/internal/websocket/websocket.go
index bbb37750..b4523c82 100644
--- a/server/internal/websocket/websocket.go
+++ b/server/internal/websocket/websocket.go
@@ -105,6 +105,7 @@ func (ws *WebSocketHandler) Start() {
sess, ok := ws.handler.locked["control"]
if ok && ws.conf.ControlProtection && sess == CONTROL_PROTECTION_SESSION && len(ws.sessions.Admins()) > 0 {
delete(ws.handler.locked, "control")
+ ws.sessions.SetControlLocked(false) // TODO: Handle locks in sessions as flags.
ws.logger.Info().Msgf("control unlocked on behalf of control protection")
if err := ws.sessions.Broadcast(
@@ -140,6 +141,7 @@ func (ws *WebSocketHandler) Start() {
_, ok := ws.handler.locked["control"]
if !ok && ws.conf.ControlProtection && adminCount == 0 {
ws.handler.locked["control"] = CONTROL_PROTECTION_SESSION
+ ws.sessions.SetControlLocked(true) // TODO: Handle locks in sessions as flags.
ws.logger.Info().Msgf("control locked and released on behalf of control protection")
ws.handler.adminRelease(id, session)