remote.focus lets a controller keyboard-capture lease ask core to enter or leave remote focus; the reply acknowledges admission only. Core reports the actual state with remote.focus events and forwards routed transitions as remote.input events carrying the source keycode and, on fresh presses, a canonical key name. A full focus mailbox revokes the lease; revocation drains pending focus work. No native capture owner is attached yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
108 lines
3.9 KiB
Go
108 lines
3.9 KiB
Go
// 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
|
|
}
|