Resume Cthulhu on empty i3 workspaces
This commit is contained in:
1 parent
7752c4e566
commit
d5f8a392cf
8 files changed
+191
-44
No files matched your search
@@ -15,9 +15,93 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.stormux.org/storm/cthulhu/internal/input"
|
||||
"git.stormux.org/storm/cthulhu/internal/platform/xorg"
|
||||
)
|
||||
|
||||
func TestNativeI3EmptyWorkspaceFocus(t *testing.T) {
|
||||
if os.Getenv("CTHULHU_NATIVE_I3") != "1" {
|
||||
t.Skip("requires private i3 harness")
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
command := func(text string) {
|
||||
t.Helper()
|
||||
if out, err := exec.CommandContext(ctx, "i3-msg", "-s", xorg.I3Socket(), text).CombinedOutput(); err != nil {
|
||||
t.Fatalf("i3 command: %v %s", err, out)
|
||||
}
|
||||
}
|
||||
command(`workspace "handoff terminal"`)
|
||||
terminal := exec.CommandContext(ctx, "xterm", "-T", "handoff fixture", "-e", "sleep", "30")
|
||||
if err := terminal.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() { _ = terminal.Process.Kill(); _ = terminal.Wait() }()
|
||||
listener := xorg.NewCommands([]string{"Insert"}, input.DesktopKeyboardLayout)
|
||||
if err := listener.Start(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer listener.Stop(context.Background())
|
||||
waitFor := func(check func() bool, message string) {
|
||||
t.Helper()
|
||||
deadline := time.Now().Add(time.Second)
|
||||
for !check() {
|
||||
if time.Now().After(deadline) {
|
||||
t.Fatal(message)
|
||||
}
|
||||
time.Sleep(time.Millisecond)
|
||||
}
|
||||
}
|
||||
wantFocus := func(xterm bool) {
|
||||
t.Helper()
|
||||
timer := time.NewTimer(2 * time.Second)
|
||||
defer timer.Stop()
|
||||
for {
|
||||
select {
|
||||
case state := <-listener.FocusChanges():
|
||||
if state.Known && state.XTerm == xterm {
|
||||
return
|
||||
}
|
||||
case err := <-listener.Failures():
|
||||
t.Fatal(err)
|
||||
case <-timer.C:
|
||||
out, _ := exec.CommandContext(ctx, "xprop", "-root", "_NET_ACTIVE_WINDOW", "_NET_SUPPORTING_WM_CHECK").CombinedOutput()
|
||||
focus, _ := exec.CommandContext(ctx, "xdotool", "getwindowfocus").CombinedOutput()
|
||||
t.Fatalf("missing focus state XTerm=%t; %s input focus: %s", xterm, out, focus)
|
||||
}
|
||||
}
|
||||
}
|
||||
wantFocus(true)
|
||||
for _, floating := range []string{"disable", "enable"} {
|
||||
command(`[title="handoff fixture"] floating ` + floating)
|
||||
command(`workspace "handoff empty"`)
|
||||
wantFocus(false)
|
||||
waitFor(listener.CommandGrabsEnabled, "empty workspace did not restore grabs")
|
||||
// A real shortcut proves that the native XTerm veto was cleared,
|
||||
// rather than merely reporting the new state to the app.
|
||||
if out, err := exec.CommandContext(ctx, "xdotool", "key", "Insert+h").CombinedOutput(); err != nil {
|
||||
t.Fatalf("shortcut: %v %s", err, out)
|
||||
}
|
||||
select {
|
||||
case event := <-listener.Commands():
|
||||
if event.Command != input.ToggleLearnMode {
|
||||
t.Fatalf("empty-workspace shortcut = %+v", event)
|
||||
}
|
||||
case <-ctx.Done():
|
||||
t.Fatal("empty-workspace shortcut did not dispatch")
|
||||
}
|
||||
// Returning to XTerm must end learn mode and release its keyboard.
|
||||
command(`workspace "handoff terminal"`)
|
||||
wantFocus(true)
|
||||
waitFor(func() bool { return !listener.CommandGrabsEnabled() && !listener.LearnModeActive() },
|
||||
"returning to XTerm kept command or learn-mode grabs")
|
||||
}
|
||||
// Losing the last client without switching workspaces uses the same path.
|
||||
command(`[title="handoff fixture"] move container to workspace "handoff moved"`)
|
||||
wantFocus(false)
|
||||
waitFor(listener.CommandGrabsEnabled, "moving the last client did not restore grabs")
|
||||
}
|
||||
|
||||
func TestNativeI3Events(t *testing.T) {
|
||||
if os.Getenv("CTHULHU_NATIVE_I3") != "1" {
|
||||
t.Skip("requires private i3 harness")
|
||||
@@ -220,4 +304,22 @@ func TestNativeI3Unavailable(t *testing.T) {
|
||||
if path := xorg.I3Socket(); path != "" {
|
||||
t.Fatalf("activated without i3: %q", path)
|
||||
}
|
||||
// No active client on an unverified WM must remain unknown. The i3
|
||||
// exception must not relax the existing focus policy on other desktops.
|
||||
if out, err := exec.Command("xprop", "-root", "-f", "_NET_ACTIVE_WINDOW", "32x", "-set", "_NET_ACTIVE_WINDOW", "0x0").CombinedOutput(); err != nil {
|
||||
t.Fatalf("set empty foreground: %v %s", err, out)
|
||||
}
|
||||
defer exec.Command("xprop", "-root", "-remove", "_NET_ACTIVE_WINDOW").Run()
|
||||
listener := xorg.NewCommands([]string{"Insert"}, input.DesktopKeyboardLayout)
|
||||
if err := listener.Start(context.Background()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer listener.Stop(context.Background())
|
||||
select {
|
||||
case state := <-listener.FocusChanges():
|
||||
t.Fatalf("unverified empty foreground became known: %+v", state)
|
||||
case err := <-listener.Failures():
|
||||
t.Fatal(err)
|
||||
case <-time.After(200 * time.Millisecond):
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user