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:
1 parent
abb4890e8a
commit
64f60fe4e8
12 files changed
+120
-18
No files matched your search
@@ -38,6 +38,12 @@ releases. `name` is a canonical unshifted key name from
|
|||||||
repeats omit it so a layout change cannot strand a key. A full event queue
|
repeats omit it so a layout change cannot strand a key. A full event queue
|
||||||
fails the session rather than losing a release.
|
fails the session rather than losing a release.
|
||||||
|
|
||||||
|
Local Num Lock selects numeric keypad names (`KP_0`–`KP_9`, `KP_Decimal`)
|
||||||
|
instead of navigation names. Shift remains a separate key transition. A keypad
|
||||||
|
press retains its original identity through repeats and release even if Num Lock
|
||||||
|
changes while it is held. Numeric `KP_0` does not act as the reader's Insert
|
||||||
|
modifier; keypad Insert with Num Lock off still does.
|
||||||
|
|
||||||
Reader code attaches the mailbox with `Session.EnableRemoteCapture` and
|
Reader code attaches the mailbox with `Session.EnableRemoteCapture` and
|
||||||
publishes with `PublishRemoteInput` and `PublishRemoteFocus`; both require the
|
publishes with `PublishRemoteInput` and `PublishRemoteFocus`; both require the
|
||||||
live controller `keyboard-capture` lease. Go exposes `RemoteCapturedKey`,
|
live controller `keyboard-capture` lease. Go exposes `RemoteCapturedKey`,
|
||||||
@@ -73,7 +79,13 @@ Ending releases every forwarded key through the stream, newest first, then
|
|||||||
sends the end marker and closes the stream. Keys still physically held keep the
|
sends the end marker and closes the stream. Keys still physically held keep the
|
||||||
grab until their releases arrive, so no release reaches an application that
|
grab until their releases arrive, so no release reaches an application that
|
||||||
never saw the press. While capture is active the XRecord listener's local key
|
never saw the press. While capture is active the XRecord listener's local key
|
||||||
echo and speech interruption are suppressed.
|
echo, speech interruption and local lock-key announcements are suppressed.
|
||||||
|
The peer supplies its own lock feedback. Local lock tracking continues, and
|
||||||
|
announcements resume for subsequent changes after returning to local control.
|
||||||
|
|
||||||
|
Debug logs record capture-ending reasons and remote speech text handed to the
|
||||||
|
software speech backend, with spelling/language state and priority. Remote speech
|
||||||
|
text is omitted at ordinary log levels, as for other private reader output.
|
||||||
|
|
||||||
`start_remote_capture` attaches this owner for sessions installed with
|
`start_remote_capture` attaches this owner for sessions installed with
|
||||||
`keyboard-capture`, from both local launch and managed startup. It enters
|
`keyboard-capture`, from both local launch and managed startup. It enters
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"log/slog"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.stormux.org/storm/cthulhu/internal/tentacle"
|
"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.
|
// The caller supplies original-lease policy and optional local-output admission.
|
||||||
func run_remote_speech_playback(ctx context.Context, speaker *configuredSpeaker,
|
func run_remote_speech_playback(ctx context.Context, speaker *configuredSpeaker,
|
||||||
incoming <-chan tentacle.RemoteSpeechDelivery, allow func(tentacle.RemoteSpeechDelivery) bool,
|
incoming <-chan tentacle.RemoteSpeechDelivery, allow func(tentacle.RemoteSpeechDelivery) bool,
|
||||||
admit remoteSpeechAdmission) error {
|
admit remoteSpeechAdmission, logger *slog.Logger) error {
|
||||||
if speaker == nil || allow == nil {
|
if speaker == nil || allow == nil {
|
||||||
return errors.New("Remote speech playback requires a backend and delivery policy")
|
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) {
|
if !allow(delivery) {
|
||||||
return errors.New("Remote speech delivery policy changed")
|
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
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -320,7 +320,7 @@ func TestRemoteSpeechStreamIsolatedBackendCancellation(t *testing.T) {
|
|||||||
incoming := make(chan tentacle.RemoteSpeechDelivery, 8)
|
incoming := make(chan tentacle.RemoteSpeechDelivery, 8)
|
||||||
done := make(chan error, 1)
|
done := make(chan error, 1)
|
||||||
go func() {
|
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}
|
lease := tentaclewire.RemoteLease{Lease: strings.Repeat("b", 32), Epoch: 1}
|
||||||
send := func(effect tentaclewire.RemoteSpeech) <-chan struct{} {
|
send := func(effect tentaclewire.RemoteSpeech) <-chan struct{} {
|
||||||
@@ -377,7 +377,7 @@ func TestRemoteSpeechStreamEarlyBackendCompletionKeepsReading(t *testing.T) {
|
|||||||
incoming := make(chan tentacle.RemoteSpeechDelivery, 8)
|
incoming := make(chan tentacle.RemoteSpeechDelivery, 8)
|
||||||
done := make(chan error, 1)
|
done := make(chan error, 1)
|
||||||
go func() {
|
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}
|
lease := tentaclewire.RemoteLease{Lease: strings.Repeat("b", 32), Epoch: 1}
|
||||||
for _, text := range []string{"first", "second", "third"} {
|
for _, text := range []string{"first", "second", "third"} {
|
||||||
@@ -422,7 +422,7 @@ func TestRemoteSpeechStreamPolicyLossDuringAcquisition(t *testing.T) {
|
|||||||
Release: func() { close(released) }}
|
Release: func() { close(released) }}
|
||||||
done := make(chan error, 1)
|
done := make(chan error, 1)
|
||||||
go func() {
|
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)
|
remote_stream_wait(t, held)
|
||||||
allowed.Store(false)
|
allowed.Store(false)
|
||||||
@@ -457,7 +457,7 @@ func test_remote_stream_lease_voice(t *testing.T, active bool) {
|
|||||||
incoming := make(chan tentacle.RemoteSpeechDelivery, 4)
|
incoming := make(chan tentacle.RemoteSpeechDelivery, 4)
|
||||||
done := make(chan error, 1)
|
done := make(chan error, 1)
|
||||||
go func() {
|
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{} {
|
send := func(lifetime context.Context, epoch uint64, text string) <-chan struct{} {
|
||||||
released := make(chan struct{})
|
released := make(chan struct{})
|
||||||
|
|||||||
@@ -119,6 +119,7 @@ func (runtime *tentacleRuntime) start_remote_capture(ctx context.Context, sessio
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if event.Ended != "" {
|
if event.Ended != "" {
|
||||||
|
runtime.logger.Debug("remote keyboard capture ended", "reason", event.Ended)
|
||||||
_ = session.PublishRemoteFocus(lease, tentaclewire.RemoteFocusLocal, event.Ended)
|
_ = session.PublishRemoteFocus(lease, tentaclewire.RemoteFocusLocal, event.Ended)
|
||||||
runtime.report("Keyboard returned to this computer.")
|
runtime.report("Keyboard returned to this computer.")
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ func (runtime *tentacleRuntime) start_remote_speech(ctx context.Context, session
|
|||||||
defer cancel()
|
defer cancel()
|
||||||
defer stop()
|
defer stop()
|
||||||
for ctx.Err() == nil {
|
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 {
|
if ctx.Err() != nil || err == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import "errors"
|
|||||||
var ErrRemoteCaptureChord = errors.New("remote capture local shortcut is invalid")
|
var ErrRemoteCaptureChord = errors.New("remote capture local shortcut is invalid")
|
||||||
|
|
||||||
// RemoteCaptureKey is one normalized captured transition. Symbol is the
|
// RemoteCaptureKey is one normalized captured transition. Symbol is the
|
||||||
// group-zero, level-zero keysym the native owner resolved for a press, the
|
// group-zero, unshifted keysym the native owner resolved for a press, with
|
||||||
// same form the command owner matches against bindings; releases and repeats are
|
// Num Lock selecting numeric keypad identities; releases and repeats are
|
||||||
// matched by keycode, so a later layout change cannot alter ownership.
|
// matched by keycode, so a later layout change cannot alter ownership.
|
||||||
type RemoteCaptureKey struct {
|
type RemoteCaptureKey struct {
|
||||||
Keycode uint8
|
Keycode uint8
|
||||||
@@ -43,8 +43,8 @@ type RemoteCaptureTransition struct {
|
|||||||
// handled by the native owner without waiting for the network or a Tentacle.
|
// handled by the native owner without waiting for the network or a Tentacle.
|
||||||
var RemoteCaptureEscape = CommandChord{Symbol: 0xffc8}
|
var RemoteCaptureEscape = CommandChord{Symbol: 0xffc8}
|
||||||
|
|
||||||
// RemoteCaptureKeyName returns the canonical Remote key name for a
|
// RemoteCaptureKeyName returns the canonical Remote key name for an unshifted
|
||||||
// group-zero, level-zero keysym name, or "" when it has none.
|
// keysym name (with Num Lock selecting keypad identities), or "" when it has none.
|
||||||
func RemoteCaptureKeyName(keysym string) string {
|
func RemoteCaptureKeyName(keysym string) string {
|
||||||
switch keysym {
|
switch keysym {
|
||||||
case "Prior":
|
case "Prior":
|
||||||
|
|||||||
@@ -634,6 +634,17 @@ int next_command_key(CommandDevice *device, CommandKey *key) {
|
|||||||
}
|
}
|
||||||
key->pressed=(event.type==KeyPress); key->code=event.xkey.keycode;
|
key->pressed=(event.type==KeyPress); key->code=event.xkey.keycode;
|
||||||
key->symbol=XLookupKeysym(&event.xkey,0);
|
key->symbol=XLookupKeysym(&event.xkey,0);
|
||||||
|
key->remoteSymbol=key->symbol;
|
||||||
|
// Remote keypad identities must follow Num Lock. Keep ordinary command
|
||||||
|
// matching at level zero, and forward Shift separately like other keys.
|
||||||
|
// Retain this identity in the capture owner until the physical release.
|
||||||
|
if (IsKeypadKey(key->symbol)) {
|
||||||
|
unsigned int consumed;
|
||||||
|
KeySym translated;
|
||||||
|
if (XkbLookupKeySym(device->display,event.xkey.keycode,
|
||||||
|
event.xkey.state & device->numlockMask,&consumed,&translated))
|
||||||
|
key->remoteSymbol=translated;
|
||||||
|
}
|
||||||
key->modifiers=event.xkey.state & ~device->ignoredModifierMask;
|
key->modifiers=event.xkey.state & ~device->ignoredModifierMask;
|
||||||
key->text[0]=0; key->textSymbol=0;
|
key->text[0]=0; key->textSymbol=0;
|
||||||
if (device->internalUI && event.type==KeyPress) {
|
if (device->internalUI && event.type==KeyPress) {
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ typedef struct {
|
|||||||
int nativeButtonFocus;
|
int nativeButtonFocus;
|
||||||
Window browseWindow;
|
Window browseWindow;
|
||||||
} CommandDevice;
|
} CommandDevice;
|
||||||
typedef struct { int pressed, code, symbol, textSymbol, modifiers, locks; unsigned long timestamp; char text[64]; } CommandKey;
|
typedef struct { int pressed, code, symbol, remoteSymbol, textSymbol, modifiers, locks; unsigned long timestamp; char text[64]; } CommandKey;
|
||||||
typedef struct {
|
typedef struct {
|
||||||
int known, pid, x, y, width, height, outerX, outerY, outerWidth, outerHeight;
|
int known, pid, x, y, width, height, outerX, outerY, outerWidth, outerHeight;
|
||||||
int frameX, frameY, frameWidth, frameHeight;
|
int frameX, frameY, frameWidth, frameHeight;
|
||||||
|
|||||||
@@ -309,7 +309,9 @@ func (owner *commandOwner) syncGrabs() bool {
|
|||||||
func (owner *commandOwner) publishLocks(key C.CommandKey) {
|
func (owner *commandOwner) publishLocks(key C.CommandKey) {
|
||||||
for index, name := range []string{"Caps Lock", "Num Lock", "Scroll Lock"} {
|
for index, name := range []string{"Caps Lock", "Num Lock", "Scroll Lock"} {
|
||||||
mask := C.int(1 << index)
|
mask := C.int(1 << index)
|
||||||
if (key.locks^owner.lastLocks)&mask != 0 {
|
// Remote's lock keys belong to the peer, including its feedback. Local
|
||||||
|
// announcements would cancel incoming speech and repeat the peer's echo.
|
||||||
|
if owner.remote == nil && (key.locks^owner.lastLocks)&mask != 0 {
|
||||||
owner.listener.publish_lock_state(input.LockState{Name: name, Locked: key.locks&mask != 0, Timestamp: uint32(key.timestamp)})
|
owner.listener.publish_lock_state(input.LockState{Name: name, Locked: key.locks&mask != 0, Timestamp: uint32(key.timestamp)})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -223,9 +223,9 @@ func (owner *commandOwner) handle_remote_key(key C.CommandKey) {
|
|||||||
}
|
}
|
||||||
routed := input.RemoteCaptureKey{Keycode: transition.Keycode, Pressed: transition.Pressed, Repeat: transition.Repeat}
|
routed := input.RemoteCaptureKey{Keycode: transition.Keycode, Pressed: transition.Pressed, Repeat: transition.Repeat}
|
||||||
if transition.Pressed && !transition.Repeat {
|
if transition.Pressed && !transition.Repeat {
|
||||||
routed.Symbol = uint32(key.symbol)
|
routed.Symbol = uint32(key.remoteSymbol)
|
||||||
name := ""
|
name := ""
|
||||||
if native := C.XKeysymToString(C.KeySym(key.symbol)); native != nil {
|
if native := C.XKeysymToString(C.KeySym(key.remoteSymbol)); native != nil {
|
||||||
name = input.RemoteCaptureKeyName(C.GoString(native))
|
name = input.RemoteCaptureKeyName(C.GoString(native))
|
||||||
}
|
}
|
||||||
capture.names[transition.Keycode] = name
|
capture.names[transition.Keycode] = name
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ func TestNativeRemoteCaptureForwardsBypassesAndEscapes(t *testing.T) {
|
|||||||
if err := exec.CommandContext(ctx, "xdotool", "windowfocus", window).Run(); err != nil {
|
if err := exec.CommandContext(ctx, "xdotool", "windowfocus", window).Run(); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
listener := NewCommands([]string{"Insert"}, input.DesktopKeyboardLayout)
|
listener := NewCommands([]string{"Insert", "KP_Insert"}, input.DesktopKeyboardLayout)
|
||||||
if err := listener.Start(ctx); err != nil {
|
if err := listener.Start(ctx); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -100,6 +100,60 @@ func TestNativeRemoteCaptureForwardsBypassesAndEscapes(t *testing.T) {
|
|||||||
if expect("", false, false) != a {
|
if expect("", false, false) != a {
|
||||||
t.Fatal("release paired with another keycode")
|
t.Fatal("release paired with another keycode")
|
||||||
}
|
}
|
||||||
|
// The same physical keypad key changes identity with Num Lock. Lock changes
|
||||||
|
// during a held key must not lose its release or make KP_0 a
|
||||||
|
// reader modifier. No local lock speech may interrupt the peer's output.
|
||||||
|
remoteNativeState(t, "num-off")
|
||||||
|
run("key", "KP_End")
|
||||||
|
keypad := expect("KP_End", true, true)
|
||||||
|
if expect("", false, false) != keypad {
|
||||||
|
t.Fatal("keypad navigation release changed code")
|
||||||
|
}
|
||||||
|
remoteNativeState(t, "num-on")
|
||||||
|
run("keydown", "KP_End")
|
||||||
|
if expect("KP_1", true, true) != keypad {
|
||||||
|
t.Fatal("Num Lock changed physical keypad code")
|
||||||
|
}
|
||||||
|
remoteNativeState(t, "num-off")
|
||||||
|
run("keyup", "KP_End")
|
||||||
|
if expect("", false, false) != keypad {
|
||||||
|
t.Fatal("Num Lock change lost held keypad release")
|
||||||
|
}
|
||||||
|
remoteNativeState(t, "num-on")
|
||||||
|
for _, key := range []struct{ physical, name string }{
|
||||||
|
{"KP_Insert", "KP_0"}, {"KP_End", "KP_1"}, {"KP_Down", "KP_2"},
|
||||||
|
{"KP_Next", "KP_3"}, {"KP_Left", "KP_4"}, {"KP_Begin", "KP_5"},
|
||||||
|
{"KP_Right", "KP_6"}, {"KP_Home", "KP_7"}, {"KP_Up", "KP_8"},
|
||||||
|
{"KP_Prior", "KP_9"}, {"KP_Delete", "KP_Decimal"},
|
||||||
|
} {
|
||||||
|
run("key", key.physical)
|
||||||
|
code := expect(key.name, true, true)
|
||||||
|
if expect("", false, false) != code {
|
||||||
|
t.Fatalf("numeric %s release changed code", key.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
run("key", "KP_Insert")
|
||||||
|
zero := expect("KP_0", true, true)
|
||||||
|
if expect("", false, false) != zero {
|
||||||
|
t.Fatal("numeric keypad zero was treated as a modifier")
|
||||||
|
}
|
||||||
|
run("key", "KP_Insert+ctrl+r")
|
||||||
|
expect("KP_0", true, true)
|
||||||
|
expect("Control_L", true, true)
|
||||||
|
expect("r", true, true)
|
||||||
|
expect("", false, false)
|
||||||
|
expect("", false, false)
|
||||||
|
expect("", false, false)
|
||||||
|
remoteNativeState(t, "num-off")
|
||||||
|
// A key event is a barrier behind the XKB notifications above.
|
||||||
|
run("key", "c")
|
||||||
|
expect("c", true, true)
|
||||||
|
expect("", false, false)
|
||||||
|
select {
|
||||||
|
case state := <-listener.LockStates():
|
||||||
|
t.Fatalf("local lock feedback during Remote capture: %+v", state)
|
||||||
|
default:
|
||||||
|
}
|
||||||
run("key", "shift+Tab")
|
run("key", "shift+Tab")
|
||||||
shift := expect("Shift_L", true, true)
|
shift := expect("Shift_L", true, true)
|
||||||
tab := expect("Tab", true, true)
|
tab := expect("Tab", true, true)
|
||||||
@@ -166,6 +220,17 @@ func TestNativeRemoteCaptureForwardsBypassesAndEscapes(t *testing.T) {
|
|||||||
t.Fatal("ending a finished session failed")
|
t.Fatal("ending a finished session failed")
|
||||||
}
|
}
|
||||||
run("key", "b")
|
run("key", "b")
|
||||||
|
// Lock feedback resumes after capture, without replaying remote changes.
|
||||||
|
run("key", "Num_Lock")
|
||||||
|
select {
|
||||||
|
case state := <-listener.LockStates():
|
||||||
|
if state.Name != "Num Lock" || !state.Locked {
|
||||||
|
t.Fatalf("local lock feedback after capture: %+v", state)
|
||||||
|
}
|
||||||
|
case <-ctx.Done():
|
||||||
|
t.Fatal("lock feedback did not resume after capture")
|
||||||
|
}
|
||||||
|
remoteNativeState(t, "num-off")
|
||||||
|
|
||||||
// Reader policy ends capture with keys held, releasing them remotely and
|
// Reader policy ends capture with keys held, releasing them remotely and
|
||||||
// keeping the grab until the physical release.
|
// keeping the grab until the physical release.
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ func TestNativeSuspendedQueuedKey(t *testing.T) {
|
|||||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
probe := filepath.Join(t.TempDir(), "suspension-probe")
|
probe := filepath.Join(t.TempDir(), "suspension-probe")
|
||||||
if out, err := exec.CommandContext(ctx, "cc", "-I.", "../../../tests/xorg_suspension_probe.c", "commands_native.c", "commands_local.c", "-o", probe, "-lX11", "-lXtst", "-lXi", "-lxkbcommon", "-lpthread").CombinedOutput(); err != nil {
|
if out, err := exec.CommandContext(ctx, "cc", "-I.", "../../../tests/xorg_suspension_probe.c", "commands_native.c", "commands_local.c", "i3_native.c", "-o", probe, "-lX11", "-lXtst", "-lXi", "-lxkbcommon", "-lpthread").CombinedOutput(); err != nil {
|
||||||
t.Fatalf("compile native probe: %v: %s", err, out)
|
t.Fatalf("compile native probe: %v: %s", err, out)
|
||||||
}
|
}
|
||||||
for _, mode := range []string{"queued", "replay", "passive", "caps-handoff"} {
|
for _, mode := range []string{"queued", "replay", "passive", "caps-handoff"} {
|
||||||
|
|||||||
Reference in new issue
Block a user