Files
Chase CichorzandClaude Opus 5.5 2d5327adf4 Add Remote controller capture IPC to the host and both SDKs
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>
2026-11-06 16:06:38 -05:00

101 lines
3.2 KiB
Go

// Copyright (c) 2026 Storm Dragon
// Authors: Storm Dragon, Angus Kola
// SPDX-License-Identifier: GPL-3.0-or-later
package tentaclewire
import (
"encoding/json"
"git.stormux.org/storm/cthulhu/internal/input"
)
// RemoteFocusQueueSize bounds pending controller focus requests per session.
const RemoteFocusQueueSize = 4
// Focus states and the reasons a controller focus event can report.
const (
RemoteFocusLocal = "local"
RemoteFocusRemote = "remote"
)
var remoteFocusReasons = map[string]bool{
"requested": true, // The child asked for this state.
"escape": true, // The core escape returned to local focus.
"local": true, // Reader policy (lock, handoff, reader UI) ended capture.
"unavailable": true, // Capture could not be acquired.
}
// RemoteCapturedKey is one controller transition from the reader's exclusive
// capture, after repeat normalization and local-chord routing. Code is the
// source Xorg keycode, used only to pair repeats and releases. Name is the
// canonical unshifted key name and is present only on a fresh press; releases
// and repeats are matched by Code so a layout change cannot strand a key.
type RemoteCapturedKey struct {
Code int `json:"code"`
Name string `json:"name,omitempty"`
Pressed bool `json:"pressed"`
Repeat bool `json:"repeat,omitempty"`
}
func ValidRemoteCapturedKey(key RemoteCapturedKey) bool {
if key.Code < 8 || key.Code > 255 {
return false
}
if key.Pressed && !key.Repeat {
return input.ValidRemoteKeyName(key.Name)
}
return key.Name == "" && (key.Pressed || !key.Repeat)
}
func (key *RemoteCapturedKey) UnmarshalJSON(data []byte) error {
var fields map[string]json.RawMessage
if DecodeStrict(data, &fields) != nil || fields["code"] == nil || fields["pressed"] == nil {
return ErrProtocol
}
type plain RemoteCapturedKey
var value plain
if DecodeStrict(data, &value) != nil || !ValidRemoteCapturedKey(RemoteCapturedKey(value)) {
return ErrProtocol
}
*key = RemoteCapturedKey(value)
return nil
}
// RemoteFocusRequest asks core to enter or leave remote focus for the current
// controller keyboard-capture lease. The reply acknowledges admission only;
// the actual state is reported by a remote.focus event.
type RemoteFocusRequest struct {
RemoteOperation
Remote bool `json:"remote"`
}
func ValidRemoteFocusRequest(request RemoteFocusRequest) bool {
return len(request.Lease) == 32 && request.Epoch != 0 && request.Sequence != 0
}
func (request *RemoteFocusRequest) UnmarshalJSON(data []byte) error {
var fields map[string]json.RawMessage
if DecodeStrict(data, &fields) != nil || len(fields) != 4 || fields["lease"] == nil || fields["epoch"] == nil ||
fields["sequence"] == nil || fields["remote"] == nil {
return ErrProtocol
}
var value struct {
RemoteOperation
Remote *bool `json:"remote"`
}
if DecodeStrict(data, &value) != nil || value.Remote == nil {
return ErrProtocol
}
result := RemoteFocusRequest{RemoteOperation: value.RemoteOperation, Remote: *value.Remote}
if !ValidRemoteFocusRequest(result) {
return ErrProtocol
}
*request = result
return nil
}
func ValidRemoteFocusEvent(focus, reason string) bool {
return (focus == RemoteFocusLocal || focus == RemoteFocusRemote) && remoteFocusReasons[reason]
}