// Copyright (c) 2026 Storm Dragon // Authors: Storm Dragon, Angus Kola // SPDX-License-Identifier: GPL-3.0-or-later package tentacle import ( "context" "encoding/json" "git.stormux.org/storm/cthulhu/internal/tentaclewire" ) // RemoteFocusDelivery is an admitted controller focus request. The reader's // capture owner must recheck Context and RemoteContext(keyboard-capture) and // its own focus/security policy before acquiring capture, and must report the // actual result with PublishRemoteFocus. A child request never proves that // capture is physically active. type RemoteFocusDelivery struct { Context context.Context Lease tentaclewire.RemoteLease Sequence uint64 Remote bool } // EnableRemoteCapture attaches one reader-owned controller capture mailbox. // It performs no native setup and grants no authority. func (session *Session) EnableRemoteCapture() <-chan RemoteFocusDelivery { session.mutex.Lock() defer session.mutex.Unlock() if session.remoteCapture == nil { session.remoteCapture = make(chan RemoteFocusDelivery, tentaclewire.RemoteFocusQueueSize) } return session.remoteCapture } func (session *Session) remote_focus_locked(params json.RawMessage) (any, *RPCError) { var request tentaclewire.RemoteFocusRequest if decode_strict(params, &request) != nil { return nil, &RPCError{Code: -32602, Message: "Invalid session parameters"} } lease := tentaclewire.RemoteLease{Lease: request.Lease, Epoch: request.Epoch} access := session.remote_access_locked(lease, "keyboard-capture") if access == nil || access.role != "controller" || request.Sequence <= access.sequence { return nil, &RPCError{Code: -32001, Message: "Session permission denied"} } if session.remoteCapture == nil { return nil, &RPCError{Code: -32601, Message: "Remote keyboard capture unavailable"} } access.sequence = request.Sequence select { case session.remoteCapture <- RemoteFocusDelivery{Context: access.ctx, Lease: lease, Sequence: request.Sequence, Remote: request.Remote}: return struct{}{}, nil default: // A lost "local" request must never leave capture active. session.revoke_remote_locked("local", true) return nil, &RPCError{Code: -32002, Message: "Remote focus queue full"} } } func (session *Session) capture_access_locked(lease tentaclewire.RemoteLease) *remoteAccess { access := session.remote_access_locked(lease, "keyboard-capture") if access == nil || access.role != "controller" { return nil } return access } // PublishRemoteInput forwards one captured transition. Origin is the reader's // exclusive capture owner, never child RPC or network input. A full event // queue fails the session: losing a release must not leave a healthy stream. func (session *Session) PublishRemoteInput(lease tentaclewire.RemoteLease, key tentaclewire.RemoteCapturedKey) error { if !tentaclewire.ValidRemoteCapturedKey(key) { return ErrPermission } session.mutex.Lock() defer session.mutex.Unlock() if session.capture_access_locked(lease) == nil { return ErrPermission } event := tentaclewire.SessionEvent{Kind: "remote.input", Lease: lease.Lease, Epoch: lease.Epoch, Input: &key} if !session.remote_event_locked(&event) { session.fail_locked(ErrLimit) return ErrLimit } return nil } // PublishRemoteFocus reports the actual capture state for this lease. // Reporting local focus is allowed after the lease was revoked only through // the revocation event itself, so a retired lease returns ErrPermission. func (session *Session) PublishRemoteFocus(lease tentaclewire.RemoteLease, focus, reason string) error { if !tentaclewire.ValidRemoteFocusEvent(focus, reason) { return ErrPermission } session.mutex.Lock() defer session.mutex.Unlock() if session.capture_access_locked(lease) == nil { return ErrPermission } event := tentaclewire.SessionEvent{Kind: "remote.focus", Lease: lease.Lease, Epoch: lease.Epoch, Focus: focus, Reason: reason} if !session.remote_event_locked(&event) { session.fail_locked(ErrLimit) return ErrLimit } return nil }