Files
cthulhu/internal/app/tentacle_remote_keyboard.go
Chase CichorzandClaude Opus 5.5 27017bef11 Attach Remote keyboard injection to approved controlled sessions
Local launch and managed startup now start the incoming-key owner for
sessions installed with keyboard-inject, using one input-helper
Supervisor retained for the reader lifetime. Delivery rechecks lock,
XTerm handoff and internal reader UI in addition to lease, grant and
deadline. build-cthulhu.sh and both Arch packages build and install
cthulhu-input-helper; a missing helper leaves keyboard input
unavailable without affecting startup.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 16:06:38 -05:00

133 lines
4.1 KiB
Go

// Copyright (c) 2026 Storm Dragon
// Authors: Storm Dragon, Angus Kola
// SPDX-License-Identifier: GPL-3.0-or-later
package app
import (
"context"
"errors"
"git.stormux.org/storm/cthulhu/internal/remoteinput"
"git.stormux.org/storm/cthulhu/internal/tentacle"
"git.stormux.org/storm/cthulhu/internal/tentaclewire"
)
// run_remote_keyboard is the incoming-key mailbox owner. start_remote_keyboard
// is its production caller; it retains one supervisor across sessions,
// including its permanent uncertainty latch, and provides native focus/security
// policy without native I/O.
// Policy invalidation must cancel ctx or the lease, including while idle. This
// does not establish physical collision safety or trusted command routing.
// Ending the owner disables its session, leaving no accepting orphan mailbox.
func run_remote_keyboard(ctx context.Context, session *tentacle.Session, supervisor *remoteinput.Supervisor,
allow func(tentacle.RemoteKeyDelivery) bool) (result error) {
if ctx == nil || session == nil || supervisor == nil || allow == nil {
return remoteinput.ErrUnavailable
}
defer session.Disable()
ctx, cancel := context.WithCancel(ctx)
defer cancel()
stopOutput := context.AfterFunc(session.OutputContext(), cancel)
defer stopOutput()
incoming := session.EnableRemoteKeys()
var client *remoteinput.Client
var lease tentaclewire.RemoteLease
var helperCancel context.CancelFunc
var stopOwner func() bool
var leaseDone, helperDone <-chan struct{}
retire := func() error {
if client == nil {
return nil
}
// Revoke only this worker's original epoch, never replacement consent.
session.RevokeRemoteEpoch(lease.Epoch)
helperCancel()
stopOwner()
client.Cancel()
err := client.Wait()
client, helperCancel, stopOwner = nil, nil, nil
leaseDone, helperDone = nil, nil
return err
}
defer func() {
if err := retire(); err != nil {
result = err
}
}()
valid := func(delivery tentacle.RemoteKeyDelivery) bool {
if ctx.Err() != nil || delivery.Context == nil || delivery.Context.Err() != nil {
return false
}
lifetime, err := session.RemoteContext(delivery.Lease, "keyboard-inject")
return err == nil && lifetime.Err() == nil && allow(delivery)
}
for {
if ctx.Err() != nil {
return nil
}
select {
case <-ctx.Done():
return nil
case <-leaseDone:
if err := retire(); err != nil {
return err
}
case <-helperDone:
if err := retire(); err != nil {
return err
}
case delivery := <-incoming:
// Keep the host's in-flight budget until native completion or the
// full retirement barrier, including a cancelled/uncertain Apply.
err := func() error {
defer delivery.Release()
if client != nil && lease != delivery.Lease {
if err := retire(); err != nil {
session.RevokeRemoteEpoch(delivery.Lease.Epoch)
return err
}
}
if !valid(delivery) {
session.RevokeRemoteEpoch(delivery.Lease.Epoch)
if client != nil && lease == delivery.Lease {
return retire()
}
return nil
}
if client == nil {
// Derive from the actual original lease, so cancellation is
// visible synchronously even before AfterFunc callbacks run.
helperCtx, cancelHelper := context.WithCancel(delivery.Context)
stop := context.AfterFunc(ctx, cancelHelper)
opened, err := supervisor.Start(helperCtx, delivery.Lease.Epoch)
if err != nil {
stop()
cancelHelper()
session.RevokeRemoteEpoch(delivery.Lease.Epoch)
if errors.Is(err, remoteinput.ErrUncertain) {
return err
}
return nil // A fresh explicit lease may retry a clean startup failure.
}
client, lease = opened, delivery.Lease
helperCancel, stopOwner = cancelHelper, stop
leaseDone, helperDone = delivery.Context.Done(), client.Done()
}
// Launch/readiness may block. Admission and the first check do
// not authorize an effect after the policy or deadline changes.
if !valid(delivery) {
return retire()
}
if err := client.Apply(delivery.Context, delivery.Key.Name, delivery.Key.Pressed); err != nil {
return retire()
}
return nil
}()
if err != nil {
return err
}
}
}
}