Honor Num Lock during Remote keyboard capture

Forward numeric keypad identities with Num Lock enabled and preserve held-key pairing across lock changes. Suppress local lock feedback during remote capture so it cannot interrupt incoming speech. Add debug speech and capture-end diagnostics, expand private keypad checks, and repair the suspension probe source list.

Validated affected Go packages, focused remote speech race checks, the private X11 Remote input harness, and a reader build. Live physical keyboard, audible PowerShell speech, Braille, and disconnect-cause verification remain pending; the active reader session was left running.
This commit is contained in:
ak47 committed 2026-11-07 22:37:39 -05:00
1 parent abb4890e8a
commit 64f60fe4e8
12 files changed
+120 -18

No files matched your search

+13 -2
View File
@@ -8,6 +8,7 @@ import (
"context"
"encoding/json"
"errors"
"log/slog"
"time"
"git.stormux.org/storm/cthulhu/internal/tentacle"
@@ -43,7 +44,7 @@ func (err *remoteSpeechStreamError) Unwrap() error { return err.cause }
// The caller supplies original-lease policy and optional local-output admission.
func run_remote_speech_playback(ctx context.Context, speaker *configuredSpeaker,
incoming <-chan tentacle.RemoteSpeechDelivery, allow func(tentacle.RemoteSpeechDelivery) bool,
admit remoteSpeechAdmission) error {
admit remoteSpeechAdmission, logger *slog.Logger) error {
if speaker == nil || allow == nil {
return errors.New("Remote speech playback requires a backend and delivery policy")
}
@@ -87,7 +88,17 @@ func run_remote_speech_playback(ctx context.Context, speaker *configuredSpeaker,
if !allow(delivery) {
return errors.New("Remote speech delivery policy changed")
}
return voice.SpeakUtteranceAndWait(request, utterance)
if logger != nil && logger.Enabled(request, slog.LevelDebug) {
for _, fragment := range utterance.fragments {
logger.DebugContext(request, "delivering remote speech", "utterance", fragment.Text,
"characters", fragment.Characters, "language", fragment.Language, "priority", utterance.priority)
}
}
err := voice.SpeakUtteranceAndWait(request, utterance)
if err != nil && request.Err() == nil && logger != nil {
logger.Debug("remote speech rendering failed", "error", err)
}
return err
})
}
+4 -4
View File
@@ -320,7 +320,7 @@ func TestRemoteSpeechStreamIsolatedBackendCancellation(t *testing.T) {
incoming := make(chan tentacle.RemoteSpeechDelivery, 8)
done := make(chan error, 1)
go func() {
done <- run_remote_speech_playback(ctx, speaker, incoming, func(tentacle.RemoteSpeechDelivery) bool { return true }, nil)
done <- run_remote_speech_playback(ctx, speaker, incoming, func(tentacle.RemoteSpeechDelivery) bool { return true }, nil, nil)
}()
lease := tentaclewire.RemoteLease{Lease: strings.Repeat("b", 32), Epoch: 1}
send := func(effect tentaclewire.RemoteSpeech) <-chan struct{} {
@@ -377,7 +377,7 @@ func TestRemoteSpeechStreamEarlyBackendCompletionKeepsReading(t *testing.T) {
incoming := make(chan tentacle.RemoteSpeechDelivery, 8)
done := make(chan error, 1)
go func() {
done <- run_remote_speech_playback(ctx, speaker, incoming, func(tentacle.RemoteSpeechDelivery) bool { return true }, nil)
done <- run_remote_speech_playback(ctx, speaker, incoming, func(tentacle.RemoteSpeechDelivery) bool { return true }, nil, nil)
}()
lease := tentaclewire.RemoteLease{Lease: strings.Repeat("b", 32), Epoch: 1}
for _, text := range []string{"first", "second", "third"} {
@@ -422,7 +422,7 @@ func TestRemoteSpeechStreamPolicyLossDuringAcquisition(t *testing.T) {
Release: func() { close(released) }}
done := make(chan error, 1)
go func() {
done <- run_remote_speech_playback(t.Context(), speaker, incoming, func(tentacle.RemoteSpeechDelivery) bool { return allowed.Load() }, nil)
done <- run_remote_speech_playback(t.Context(), speaker, incoming, func(tentacle.RemoteSpeechDelivery) bool { return allowed.Load() }, nil, nil)
}()
remote_stream_wait(t, held)
allowed.Store(false)
@@ -457,7 +457,7 @@ func test_remote_stream_lease_voice(t *testing.T, active bool) {
incoming := make(chan tentacle.RemoteSpeechDelivery, 4)
done := make(chan error, 1)
go func() {
done <- run_remote_speech_playback(ctx, speaker, incoming, func(delivery tentacle.RemoteSpeechDelivery) bool { return delivery.Context.Err() == nil }, nil)
done <- run_remote_speech_playback(ctx, speaker, incoming, func(delivery tentacle.RemoteSpeechDelivery) bool { return delivery.Context.Err() == nil }, nil, nil)
}()
send := func(lifetime context.Context, epoch uint64, text string) <-chan struct{} {
released := make(chan struct{})
+1
View File
@@ -119,6 +119,7 @@ func (runtime *tentacleRuntime) start_remote_capture(ctx context.Context, sessio
continue
}
if event.Ended != "" {
runtime.logger.Debug("remote keyboard capture ended", "reason", event.Ended)
_ = session.PublishRemoteFocus(lease, tentaclewire.RemoteFocusLocal, event.Ended)
runtime.report("Keyboard returned to this computer.")
continue
+1 -1
View File
@@ -86,7 +86,7 @@ func (runtime *tentacleRuntime) start_remote_speech(ctx context.Context, session
defer cancel()
defer stop()
for ctx.Err() == nil {
err := run_remote_speech_playback(ctx, speaker, incoming, allow, owner.speech.admit_remote_speech)
err := run_remote_speech_playback(ctx, speaker, incoming, allow, owner.speech.admit_remote_speech, runtime.logger)
if ctx.Err() != nil || err == nil {
return
}