1package core_test
2
3import (
4 "context"
5 "testing"
6 "testing/synctest"
7 "time"
8
9 "github.com/bnema/neferwl/internal/adapters/config"
10 "github.com/bnema/neferwl/internal/core"
11 "github.com/bnema/neferwl/internal/ports"
12)
13
14// commandOf waits for the next command of type T, skipping others.
15func commandOf[T ports.ClientCommand](t *testing.T, commands <-chan ports.ClientCommand) T {
16 t.Helper()
17 deadline := time.After(2 * time.Second)
18 for {
19 select {
20 case c := <-commands:
21 if v, ok := c.(T); ok {
22 return v
23 }
24 case <-deadline:
25 var zero T
26 t.Fatalf("no %T", zero)
27 return zero
28 }
29 }
30}
31
32// A focused window inhibiting shortcuts gets bound keys; it stops when
33// it loses focus, and binds run again.
34func TestShortcutsInhibit(t *testing.T) { synctest.Test(t, shortcutsInhibit) }
35
36func shortcutsInhibit(t *testing.T) {
37 cfg := config.Defaults()
38 input := make(chan ports.InputEvent, 4)
39 client := make(chan ports.ClientEvent, 4)
40 commands := make(chan ports.ClientCommand, 64)
41 scenes := make(chan []ports.Scene, 1)
42 spawn := make(chan ports.SpawnRequest, 4)
43 state := make(chan ports.State, 1)
44 output := make(chan ports.OutputEvent, 1)
45 c, err := core.New(cfg, core.Channels{Input: input, Client: client, Output: output, Commands: commands, Scenes: scenes, Spawn: spawn, State: state}, core.Options{})
46 if err != nil {
47 t.Fatal(err)
48 }
49 ctx, cancel := context.WithCancel(context.Background())
50 done := make(chan error, 1)
51 go func() { done <- c.Run(ctx) }()
52 defer func() { cancel(); receive(t, done) }()
53 output <- ports.OutputAdded{Info: ports.OutputInfo{Name: "OUT-1", Width: 100, Height: 80}}
54 scene(t, scenes)
55 client <- ports.WindowMapped{ID: 1}
56 scene(t, scenes)
57 client <- ports.ShortcutsInhibit{Window: 1, Active: true}
58 if v := commandOf[ports.ShortcutsInhibitState](t, commands); v != (ports.ShortcutsInhibitState{Window: 1, Active: true}) {
59 t.Fatal(v)
60 }
61 bound := ports.KeyEvent{Keysym: "Return", Mods: ports.ModSuper, Pressed: true}
62 input <- bound
63 if v := commandOf[ports.ForwardKey](t, commands); v != (ports.ForwardKey{ID: 1, Key: bound}) {
64 t.Fatal(v)
65 }
66 synctest.Wait()
67 select {
68 case r := <-spawn:
69 t.Fatalf("bind ran: %v", r)
70 default:
71 }
72 // A new window takes focus: the inhibitor goes inactive, binds run.
73 client <- ports.WindowMapped{ID: 2}
74 if v := commandOf[ports.ShortcutsInhibitState](t, commands); v != (ports.ShortcutsInhibitState{Window: 1}) {
75 t.Fatal(v)
76 }
77 input <- bound
78 receive(t, spawn)
79 // Idle inhibitors show in the state.
80 client <- ports.IdleInhibit{Window: 2, Active: true}
81 deadline := time.After(2 * time.Second)
82 for {
83 select {
84 case st := <-state:
85 for _, w := range st.Windows {
86 if w.ID == 2 && w.IdleInhibit {
87 return
88 }
89 }
90 case <-deadline:
91 t.Fatal("idle inhibit not in state")
92 }
93 }
94}
95
96// A key whose press ran a bind before inhibiting began is not released to
97// the window: it never saw the press.
98func TestShortcutsInhibitDropsBoundRelease(t *testing.T) {
99 cfg := config.Defaults()
100 input := make(chan ports.InputEvent, 4)
101 client := make(chan ports.ClientEvent, 4)
102 commands := make(chan ports.ClientCommand, 64)
103 scenes := make(chan []ports.Scene, 1)
104 spawn := make(chan ports.SpawnRequest, 4)
105 output := make(chan ports.OutputEvent, 1)
106 c, err := core.New(cfg, core.Channels{Input: input, Client: client, Output: output, Commands: commands, Scenes: scenes, Spawn: spawn}, core.Options{})
107 if err != nil {
108 t.Fatal(err)
109 }
110 ctx, cancel := context.WithCancel(context.Background())
111 done := make(chan error, 1)
112 go func() { done <- c.Run(ctx) }()
113 defer func() { cancel(); receive(t, done) }()
114 output <- ports.OutputAdded{Info: ports.OutputInfo{Name: "OUT-1", Width: 100, Height: 80}}
115 scene(t, scenes)
116 client <- ports.WindowMapped{ID: 1}
117 scene(t, scenes)
118 press := ports.KeyEvent{Keysym: "Return", Keycode: 36, Mods: ports.ModSuper, Pressed: true}
119 input <- press
120 receive(t, spawn)
121 client <- ports.ShortcutsInhibit{Window: 1, Active: true}
122 commandOf[ports.ShortcutsInhibitState](t, commands)
123 release := press
124 release.Pressed = false
125 input <- release
126 other := ports.KeyEvent{Keysym: "a", Keycode: 38, Pressed: true}
127 input <- other
128 if v := commandOf[ports.ForwardKey](t, commands); v.Key != other {
129 t.Fatalf("forwarded %+v before %+v", v.Key, other)
130 }
131}