Merged changes from hunter. Attempt to fix cthulhu announcing modifiers when suspended for xterm.

This commit is contained in:
Storm Dragon committed 2026-10-31 16:07:29 -04:00
1 parent 27867a48cb
commit f2cc47c8a0
24 files changed
+679 -67

No files matched your search

+9 -1
View File
@@ -381,6 +381,13 @@ static void grab_command_keys(CommandDevice *device) {
}
}
int set_command_keys(CommandDevice *device, int enabled) {
// Another reader may have changed Caps Lock while command input was
// delegated. Adopt that state before treating Caps as our modifier again.
if (enabled && !device->commands && device->capsCode) {
XkbStateRec state;
if (XkbGetState(device->display, XkbUseCoreKbd, &state) != Success) return -7;
device->capsLocked = (state.locked_mods & LockMask) != 0;
}
pthread_mutex_lock(&error_mutex);
XSync(device->display,False);
error_display=device->display; grab_error=0;
@@ -491,6 +498,7 @@ CommandDevice *open_commands(int useInsert, int useKeypad, int useCaps, int *err
*error=set_command_keys(device,1);
if (*error) { XCloseDisplay(device->display); free(device); return NULL; }
if (device->capsCode) {
device->capsLocked=0;
XkbLockModifiers(device->display,XkbUseCoreKbd,LockMask,0);
XSync(device->display,False);
}
@@ -502,7 +510,7 @@ int command_lock_state(CommandDevice *device) {
int locks=0;
for (int i=0;i<3;i++) if (indicators & device->lockIndicators[i]) locks |= 1<<i;
// Ignore the transient XKB lock action on a grabbed reader modifier.
if (device->capsCode) locks=(locks & ~1) | device->capsLocked;
if (device->commands && device->capsCode) locks=(locks & ~1) | device->capsLocked;
return locks;
}
void toggle_command_caps_lock(CommandDevice *device) {
@@ -7,6 +7,7 @@ import (
"os"
"os/exec"
"path/filepath"
"slices"
"testing"
"time"
@@ -23,7 +24,7 @@ func TestNativeSuspendedQueuedKey(t *testing.T) {
if out, err := exec.CommandContext(ctx, "cc", "-I.", "../../../tests/xorg_suspension_probe.c", "commands_native.c", "-o", probe, "-lX11", "-lXtst", "-lxkbcommon", "-lpthread").CombinedOutput(); err != nil {
t.Fatalf("compile native probe: %v: %s", err, out)
}
for _, mode := range []string{"queued", "replay", "passive"} {
for _, mode := range []string{"queued", "replay", "passive", "caps-handoff"} {
t.Run(mode, func(t *testing.T) {
if out, err := exec.CommandContext(ctx, probe, mode).CombinedOutput(); err != nil {
t.Fatalf("native suspension: %v: %s", err, out)
@@ -39,12 +40,13 @@ func TestNativeXTermHandoffKeepsKeypadReleased(t *testing.T) {
t.Skip("requires isolated X11 display")
}
for _, scenario := range []struct {
name string
learn, held bool
name string
learn, held, caps bool
}{
{name: "delayed_focus"},
{name: "held_command", held: true},
{name: "learn_mode", learn: true},
{name: "caps_modifier", caps: true},
} {
t.Run(scenario.name, func(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 8*time.Second)
@@ -86,7 +88,13 @@ func TestNativeXTermHandoffKeepsKeypadReleased(t *testing.T) {
}
return state.Keys
}
listener := NewCommands([]string{"Insert", "KP_Insert"}, input.DesktopKeyboardLayout)
modifiers, layout := []string{"Insert", "KP_Insert"}, input.DesktopKeyboardLayout
reviewChord, reviewCommand := "KP_Begin", input.ReviewCurrentWord
if scenario.caps {
modifiers, layout = []string{"Caps_Lock"}, input.LaptopKeyboardLayout
reviewChord, reviewCommand = "Caps_Lock+i", input.ReviewCurrentLine
}
listener := NewCommands(modifiers, layout)
if err := listener.Start(ctx); err != nil {
t.Fatal(err)
}
@@ -140,9 +148,9 @@ func TestNativeXTermHandoffKeepsKeypadReleased(t *testing.T) {
defer exec.Command("xdotool", "keyup", "KP_Begin").Run()
key("keydown", "KP_Begin")
} else {
key("key", "KP_Begin")
key("key", reviewChord)
}
command(input.ReviewCurrentWord)
command(reviewCommand)
if !scenario.held {
wait(func() bool { return !listener.CommandGrabActive() })
}
@@ -168,10 +176,14 @@ func TestNativeXTermHandoffKeepsKeypadReleased(t *testing.T) {
time.Sleep(2 * xtermFocusCheckInterval)
}
action(map[string]string{"action": "clear"})
key("key", "KP_Begin")
key("key", reviewChord)
keys := action(map[string]string{"action": "state"})
if len(keys) != 2 || keys[0].Symbol != 0xff9d || !keys[0].Pressed || keys[1].Symbol != 0xff9d || keys[1].Pressed {
t.Fatalf("xterm keypad press/release = %+v; unknown=%t, grabs=%t, active=%t", keys, unknown, listener.CommandGrabsEnabled(), listener.CommandGrabActive())
expected := []deliveredKey{{Pressed: true, Symbol: 0xff9d}, {Pressed: false, Symbol: 0xff9d}}
if scenario.caps {
expected = []deliveredKey{{Pressed: true, Symbol: 0xffe5}, {Pressed: true, Symbol: 0x69}, {Pressed: false, Symbol: 0xffe5}, {Pressed: false, Symbol: 0x69}}
}
if !slices.Equal(keys, expected) {
t.Fatalf("xterm key press/release = %+v, want %+v; unknown=%t, grabs=%t, active=%t", keys, expected, unknown, listener.CommandGrabsEnabled(), listener.CommandGrabActive())
}
select {
case got := <-listener.Commands():
@@ -183,8 +195,8 @@ func TestNativeXTermHandoffKeepsKeypadReleased(t *testing.T) {
action(map[string]string{"action": "activate"})
focus(false)
wait(listener.CommandGrabsEnabled)
key("key", "KP_Begin")
command(input.ReviewCurrentWord)
key("key", reviewChord)
command(reviewCommand)
wait(func() bool { return !listener.CommandGrabActive() })
})
}