// 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] }