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>
133 lines
4.1 KiB
Go
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
|
|
}
|
|
}
|
|
}
|
|
}
|