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>
148 lines
6.1 KiB
Go
148 lines
6.1 KiB
Go
// Copyright (c) 2026 Storm Dragon
|
|
// Authors: Storm Dragon, Angus Kola
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
package tentacle
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.stormux.org/storm/cthulhu/internal/tentaclewire"
|
|
)
|
|
|
|
func focusRequest(lease tentaclewire.RemoteLease, sequence uint64, remote bool) tentaclewire.RemoteFocusRequest {
|
|
return tentaclewire.RemoteFocusRequest{RemoteOperation: tentaclewire.RemoteOperation{Lease: lease.Lease, Epoch: lease.Epoch, Sequence: sequence}, Remote: remote}
|
|
}
|
|
|
|
func nextSDKEvent(t *testing.T, session *Session) tentaclewire.SessionEvent {
|
|
t.Helper()
|
|
var event tentaclewire.SessionEvent
|
|
if err := json.Unmarshal(invokeSessionSDK(t, session, "event", nil), &event); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return event
|
|
}
|
|
|
|
func TestRemoteCaptureSDKFocusAndInputEvents(t *testing.T) {
|
|
for _, language := range []string{"go", "python"} {
|
|
t.Run(language, func(t *testing.T) {
|
|
manager := test_manager(t, DefaultLimits(), nil)
|
|
session := startSessionSDK(t, manager, language, "capture", "keyboard-capture")
|
|
lease := speech_fixture_lease(t, session, "controller", []string{"remote-present", "keyboard-capture"})
|
|
if _, code := remoteCall(t, session, "remote.focus", focusRequest(lease, 1, true)); code != -32601 {
|
|
t.Fatal("focus admitted without capture owner", code)
|
|
}
|
|
deliveries := session.EnableRemoteCapture()
|
|
if session.EnableRemoteCapture() != deliveries {
|
|
t.Fatal("attachment created another capture mailbox")
|
|
}
|
|
if _, code := remoteCall(t, session, "remote.focus", focusRequest(lease, 1, true)); code != 0 {
|
|
t.Fatal("valid focus request refused", code)
|
|
}
|
|
delivery := <-deliveries
|
|
if !delivery.Remote || delivery.Lease != lease || delivery.Sequence != 1 || delivery.Context.Err() != nil {
|
|
t.Fatal("focus delivery changed identity or lifetime", delivery)
|
|
}
|
|
if _, code := remoteCall(t, session, "remote.focus", focusRequest(lease, 1, false)); code != -32001 {
|
|
t.Fatal("replayed focus sequence accepted", code)
|
|
}
|
|
if err := session.PublishRemoteFocus(lease, tentaclewire.RemoteFocusRemote, "requested"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if event := nextSDKEvent(t, session); event.Kind != "remote.focus" || event.Focus != "remote" || event.Reason != "requested" || event.Lease != lease.Lease {
|
|
t.Fatal("bad focus event", event)
|
|
}
|
|
keys := []tentaclewire.RemoteCapturedKey{
|
|
{Code: 50, Name: "Shift_L", Pressed: true},
|
|
{Code: 38, Name: "a", Pressed: true},
|
|
{Code: 38, Pressed: true, Repeat: true},
|
|
{Code: 38},
|
|
{Code: 50},
|
|
}
|
|
for _, key := range keys {
|
|
if err := session.PublishRemoteInput(lease, key); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
for _, key := range keys {
|
|
event := nextSDKEvent(t, session)
|
|
if event.Kind != "remote.input" || event.Input == nil || *event.Input != key || event.Epoch != lease.Epoch {
|
|
t.Fatal("captured key changed in transit", event)
|
|
}
|
|
}
|
|
for _, invalid := range []tentaclewire.RemoteCapturedKey{
|
|
{Code: 7, Name: "a", Pressed: true},
|
|
{Code: 38, Name: "A", Pressed: true},
|
|
{Code: 38, Name: "a"}, // Release carries no name.
|
|
{Code: 38, Repeat: true},
|
|
} {
|
|
if !errors.Is(session.PublishRemoteInput(lease, invalid), ErrPermission) {
|
|
t.Fatal("invalid captured key published", invalid)
|
|
}
|
|
}
|
|
if !errors.Is(session.PublishRemoteFocus(lease, "remote", "network"), ErrPermission) {
|
|
t.Fatal("unknown focus reason published")
|
|
}
|
|
session.RevokeRemote()
|
|
if event := nextSDKEvent(t, session); event.Kind != "remote.revoked" {
|
|
t.Fatal("missing revocation", event)
|
|
}
|
|
if !errors.Is(session.PublishRemoteInput(lease, keys[0]), ErrPermission) || !errors.Is(session.PublishRemoteFocus(lease, "local", "local"), ErrPermission) {
|
|
t.Fatal("retired lease published capture events")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRemoteCaptureSDKPermissionAndParameters(t *testing.T) {
|
|
for _, language := range []string{"go", "python"} {
|
|
t.Run(language, func(t *testing.T) {
|
|
manager := test_manager(t, DefaultLimits(), nil)
|
|
session := startSessionSDK(t, manager, language, "capture", "keyboard-capture", "keyboard-inject")
|
|
deliveries := session.EnableRemoteCapture()
|
|
lease := speech_fixture_lease(t, session, "controller", []string{"remote-present"})
|
|
if _, code := remoteCall(t, session, "remote.focus", focusRequest(lease, 1, true)); code != -32001 {
|
|
t.Fatal("speech-only controller requested capture", code)
|
|
}
|
|
if !errors.Is(session.PublishRemoteInput(lease, tentaclewire.RemoteCapturedKey{Code: 38, Name: "a", Pressed: true}), ErrPermission) {
|
|
t.Fatal("input published without keyboard-capture")
|
|
}
|
|
lease = speech_fixture_lease(t, session, "controlled", []string{"keyboard-inject"})
|
|
if _, code := remoteCall(t, session, "remote.focus", focusRequest(lease, 1, true)); code != -32001 {
|
|
t.Fatal("controlled role requested capture", code)
|
|
}
|
|
lease = speech_fixture_lease(t, session, "controller", []string{"keyboard-capture"})
|
|
data, _ := json.Marshal(focusRequest(lease, 1, true))
|
|
for _, raw := range []string{
|
|
strings.Replace(string(data), `"remote":true`, `"remote":null`, 1),
|
|
strings.Replace(string(data), `,"remote":true`, ``, 1),
|
|
strings.Replace(string(data), `"sequence":1`, `"sequence":1,"origin":"local"`, 1),
|
|
} {
|
|
if _, code := remoteCall(t, session, "remote.focus", json.RawMessage(raw)); code != -32602 {
|
|
t.Fatal("invalid focus parameters admitted", code, raw)
|
|
}
|
|
}
|
|
if len(deliveries) != 0 {
|
|
t.Fatal("denied focus reached the owner")
|
|
}
|
|
for sequence := uint64(1); sequence <= tentaclewire.RemoteFocusQueueSize; sequence++ {
|
|
if _, code := remoteCall(t, session, "remote.focus", focusRequest(lease, sequence, sequence%2 == 1)); code != 0 {
|
|
t.Fatal("focus request refused before saturation", code)
|
|
}
|
|
}
|
|
if _, code := remoteCall(t, session, "remote.focus", focusRequest(lease, 99, false)); code != -32002 {
|
|
t.Fatal("full focus queue did not fail", code)
|
|
}
|
|
if _, err := session.RemoteContext(lease, "keyboard-capture"); err == nil {
|
|
t.Fatal("full focus queue left capture consent active")
|
|
}
|
|
if len(deliveries) != 0 {
|
|
t.Fatal("revocation did not drain pending focus work")
|
|
}
|
|
})
|
|
}
|
|
}
|