Reset master to testing content
This commit is contained in:
1 parent
0f2587b719
commit
42afa2a56c
1483 files changed
+145308
-938069
No files matched your search
@@ -0,0 +1,197 @@
|
||||
package xorg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"git.stormux.org/storm/cthulhu/internal/input"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestNativeTentacleBindingsUpdateAndRelease(t *testing.T) {
|
||||
if os.Getenv("CTHULHU_NATIVE_X11") != "1" {
|
||||
t.Skip("requires isolated X11 display")
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
// Own a foreground client; browse-only grabs deliberately replay when
|
||||
// no client owns focus or the foreground window has changed.
|
||||
probe := exec.CommandContext(ctx, "xev", "-name", "Cthulhu Tentacle Bindings", "-event", "keyboard")
|
||||
if err := probe.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() { _ = probe.Process.Kill(); _ = probe.Wait() }()
|
||||
window := ""
|
||||
for window == "" {
|
||||
output, err := exec.CommandContext(ctx, "xdotool", "search", "--onlyvisible", "--name", "^Cthulhu Tentacle Bindings$").Output()
|
||||
if err == nil {
|
||||
window = strings.TrimSpace(string(output))
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
t.Fatal(ctx.Err())
|
||||
case <-time.After(time.Millisecond):
|
||||
}
|
||||
}
|
||||
if err := exec.CommandContext(ctx, "xdotool", "windowfocus", window).Run(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
listener := NewCommands([]string{"Insert"}, input.DesktopKeyboardLayout)
|
||||
custom := []input.BindingOverride{
|
||||
{Layout: input.DesktopKeyboardLayout, Scope: "global", Command: input.Command("tentacle/test/global"), Keys: []string{"Cthulhu+Ctrl+Alt+y"}},
|
||||
{Layout: input.DesktopKeyboardLayout, Scope: "global", Command: input.Command("tentacle/test/global"), Keys: []string{"F9"}},
|
||||
{Layout: input.DesktopKeyboardLayout, Scope: "browse", Command: input.Command("tentacle/test/browse"), Keys: []string{"F6"}},
|
||||
{Layout: input.DesktopKeyboardLayout, Scope: "global", Command: input.Command("tentacle/test/wormhole"), Keys: []string{"Cthulhu+Ctrl+Shift+W"}},
|
||||
}
|
||||
if err := listener.SetTentacleBindings(ctx, custom); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := listener.Start(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer listener.Stop(context.Background())
|
||||
key := func(args ...string) {
|
||||
t.Helper()
|
||||
if out, err := exec.CommandContext(ctx, "xdotool", args...).CombinedOutput(); err != nil {
|
||||
t.Fatalf("keys: %v %s", err, out)
|
||||
}
|
||||
}
|
||||
command := func(want input.Command) {
|
||||
t.Helper()
|
||||
select {
|
||||
case got := <-listener.Commands():
|
||||
if got.Command != want {
|
||||
t.Fatalf("command %s want %s", got.Command, want)
|
||||
}
|
||||
case err := <-listener.Failures():
|
||||
t.Fatal(err)
|
||||
case <-ctx.Done():
|
||||
t.Fatal(ctx.Err())
|
||||
}
|
||||
}
|
||||
key("keydown", "Insert", "keydown", "Control_L", "keydown", "Alt_L", "keydown", "y")
|
||||
command(input.Command("tentacle/test/global"))
|
||||
// Queue a new configuration while Y is held: it must wait for releases.
|
||||
applied := make(chan error, 1)
|
||||
go func() { applied <- listener.SetTentacleBindings(ctx, nil) }()
|
||||
select {
|
||||
case err := <-applied:
|
||||
t.Fatalf("update completed while keys were held: %v", err)
|
||||
case <-time.After(40 * time.Millisecond):
|
||||
}
|
||||
key("keyup", "Insert", "keyup", "Control_L", "keyup", "Alt_L", "keyup", "y")
|
||||
if err := <-applied; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
key("keydown", "Insert", "key", "space", "keyup", "Insert")
|
||||
command(input.Preferences)
|
||||
if err := listener.SetTentacleBindings(ctx, custom); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
key("key", "F9")
|
||||
command(input.Command("tentacle/test/global"))
|
||||
key("keydown", "Insert", "keydown", "Control_L", "keydown", "Shift_L", "key", "w", "keyup", "Shift_L", "keyup", "Control_L", "keyup", "Insert")
|
||||
command(input.Command("tentacle/test/wormhole"))
|
||||
// All modifiers released: an ordinary W must reach the focused application.
|
||||
key("key", "w")
|
||||
key("key", "F9")
|
||||
command(input.Command("tentacle/test/global"))
|
||||
if err := listener.SetKeyBindings(ctx, []input.BindingOverride{{Layout: input.DesktopKeyboardLayout, Scope: "global", Command: input.Preferences, Keys: []string{"F9"}}}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
key("key", "F9")
|
||||
command(input.Preferences)
|
||||
if err := listener.SetKeyBindings(ctx, nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
key("key", "F9")
|
||||
command(input.Command("tentacle/test/global"))
|
||||
key("keydown", "Insert", "key", "h", "keyup", "Insert")
|
||||
command(input.ToggleLearnMode)
|
||||
key("key", "F9")
|
||||
command(input.LearnKey)
|
||||
key("key", "Escape")
|
||||
command(input.ToggleLearnMode)
|
||||
listener.SetBrowseNavigation(true)
|
||||
for !listener.BrowseNavigationEnabled() {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
t.Fatal(ctx.Err())
|
||||
case <-time.After(time.Millisecond):
|
||||
}
|
||||
}
|
||||
key("key", "F6")
|
||||
command(input.Command("tentacle/test/browse"))
|
||||
listener.SetBrowseNavigation(false)
|
||||
for listener.browseApplied.Load() {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
t.Fatal(ctx.Err())
|
||||
case <-time.After(time.Millisecond):
|
||||
}
|
||||
}
|
||||
key("key", "F6")
|
||||
key("key", "F9")
|
||||
command(input.Command("tentacle/test/global")) // F6 outside browse mode must not dispatch.
|
||||
competitor := NewCommands([]string{"KP_Insert"}, input.LaptopKeyboardLayout)
|
||||
if err := competitor.SetKeyBindings(ctx, []input.BindingOverride{{Layout: input.LaptopKeyboardLayout, Scope: "global", Command: input.PresentDate, Keys: []string{"F10"}}}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := competitor.Start(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
collision := input.CloneBindingOverrides(custom)
|
||||
collision[0].Keys = []string{"F10"}
|
||||
if err := listener.SetTentacleBindings(ctx, collision); err == nil {
|
||||
t.Fatal("accepted conflicting native grab")
|
||||
}
|
||||
key("key", "F9")
|
||||
command(input.Command("tentacle/test/global")) // Native rollback retained the old shortcut.
|
||||
browseCollision := input.CloneBindingOverrides(custom)
|
||||
browseCollision[2].Keys = []string{"F10"}
|
||||
if err := listener.SetTentacleBindings(ctx, browseCollision); err == nil {
|
||||
t.Fatal("accepted conflicting prospective browse grab")
|
||||
}
|
||||
key("key", "F9")
|
||||
command(input.Command("tentacle/test/global"))
|
||||
if err := competitor.Stop(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
unavailable := input.CloneBindingOverrides(custom)
|
||||
unavailable[0].Keys = []string{"Cthulhu+F35"}
|
||||
if err := listener.SetTentacleBindings(ctx, unavailable); err == nil {
|
||||
t.Fatal("accepted unavailable key")
|
||||
}
|
||||
key("key", "F9")
|
||||
command(input.Command("tentacle/test/global")) // Rejected update preserved the previous router.
|
||||
listener.SetSecureInputActive(true)
|
||||
for listener.CommandGrabsEnabled() {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
t.Fatal(ctx.Err())
|
||||
case <-time.After(time.Millisecond):
|
||||
}
|
||||
}
|
||||
key("key", "F9")
|
||||
listener.SetSecureInputActive(false)
|
||||
for !listener.CommandGrabsEnabled() {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
t.Fatal(ctx.Err())
|
||||
case <-time.After(time.Millisecond):
|
||||
}
|
||||
}
|
||||
key("key", "F9")
|
||||
command(input.Command("tentacle/test/global"))
|
||||
if err := listener.SetTentacleBindings(ctx, nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(listener.TentacleBindings()) != 0 {
|
||||
t.Fatal("removed bindings still published")
|
||||
}
|
||||
key("key", "F9")
|
||||
key("keydown", "Insert", "key", "space", "keyup", "Insert")
|
||||
command(input.Preferences)
|
||||
}
|
||||
Reference in new issue
Block a user