1package core_test
2
3import (
4 "context"
5 "testing"
6
7 "github.com/bnema/neferwl/internal/core"
8 "github.com/bnema/neferwl/internal/ports"
9)
10
11// A launcher holding the keyboard over the overview gets its keys; the
12// overview takes them back once it closes.
13func TestOverviewLauncherKeepsKeys(t *testing.T) {
14 cfg := scrollDefaults()
15 client := make(chan ports.ClientEvent, 8)
16 input := make(chan ports.InputEvent, 8)
17 output := make(chan ports.OutputEvent, 8)
18 commands := make(chan ports.ClientCommand, 256)
19 scenes := make(chan []ports.Scene, 1)
20 c, err := core.New(cfg, core.Channels{Client: client, Input: input, Output: output, Commands: commands, Scenes: scenes}, core.Options{})
21 if err != nil {
22 t.Fatal(err)
23 }
24 ctx, cancel := context.WithCancel(context.Background())
25 defer cancel()
26 go c.Run(ctx)
27 output <- ports.OutputAdded{Info: ports.OutputInfo{Name: "OUT-1", Width: 300, Height: 200}}
28 scene(t, scenes)
29 client <- ports.WindowMapped{ID: 1}
30 scene(t, scenes)
31 client <- ports.WindowMapped{ID: 2}
32 scene(t, scenes)
33 input <- ports.KeyEvent{Keysym: "o", Mods: ports.ModSuper, Pressed: true}
34 sceneMatch(t, scenes, func(s ports.Scene) bool { return len(s.Windows) == 2 && s.Windows[0].Preview > 0 })
35 input <- ports.KeyEvent{Keysym: "o", Mods: ports.ModSuper}
36
37 launcher := ports.LayerSurface{ID: 7, Layer: ports.LayerOverlay, Width: 100, Height: 40, Keyboard: 1}
38 client <- ports.LayerChanged{Layers: []ports.LayerSurface{launcher}}
39 scene(t, scenes)
40 for len(commands) > 0 {
41 <-commands
42 }
43 input <- ports.KeyEvent{Keysym: "h", Pressed: true}
44 if v := next(t, commands, anyOf[ports.ForwardKey]); v.ID != 7 || v.Key.Keysym != "h" {
45 t.Fatalf("forward %+v, want the launcher", v)
46 }
47 input <- ports.KeyEvent{Keysym: "h"}
48 next(t, commands, anyOf[ports.ForwardKey])
49
50 client <- ports.LayerChanged{}
51 scene(t, scenes)
52 input <- ports.KeyEvent{Keysym: "h", Pressed: true}
53 sceneMatch(t, scenes, func(s ports.Scene) bool {
54 for _, w := range s.Windows {
55 if w.ID == 1 && w.Focused && w.Preview > 0 {
56 return true
57 }
58 }
59 return false
60 })
61}
62
63// Focus binds (cmd+arrows and their Vim twins) navigate the overview over
64// a covering float: cmd+k/j round-trips through the columns card, then
65// cmd+up brings it forward, cmd+left selects a column and cmd+down returns.
66func TestOverviewFocusBindsNavigate(t *testing.T) {
67 cfg := scrollDefaults()
68 client := make(chan ports.ClientEvent, 8)
69 input := make(chan ports.InputEvent, 8)
70 output := make(chan ports.OutputEvent, 8)
71 commands := make(chan ports.ClientCommand, 256)
72 scenes := make(chan []ports.Scene, 1)
73 c, err := core.New(cfg, core.Channels{Client: client, Input: input, Output: output, Commands: commands, Scenes: scenes}, core.Options{})
74 if err != nil {
75 t.Fatal(err)
76 }
77 ctx, cancel := context.WithCancel(context.Background())
78 defer cancel()
79 go c.Run(ctx)
80 output <- ports.OutputAdded{Info: ports.OutputInfo{Name: "OUT-1", Width: 300, Height: 200}}
81 scene(t, scenes)
82 for id := ports.WindowID(1); id <= 2; id++ {
83 client <- ports.WindowMapped{ID: id}
84 scene(t, scenes)
85 }
86 client <- ports.WindowMapped{ID: 9, Floating: true, Width: 300, Height: 200}
87 scene(t, scenes)
88 focused := func(id ports.WindowID) func(ports.Scene) bool {
89 return func(s ports.Scene) bool {
90 for _, w := range s.Windows {
91 if w.ID == id && w.Focused && w.Preview > 0 && w.Dim == 0 {
92 return true
93 }
94 }
95 return false
96 }
97 }
98 press := func(key string) {
99 input <- ports.KeyEvent{Keysym: key, Mods: ports.ModSuper, Pressed: true}
100 input <- ports.KeyEvent{Keysym: key, Mods: ports.ModSuper}
101 }
102 press("o")
103 sceneMatch(t, scenes, focused(9))
104 press("k")
105 sceneMatch(t, scenes, focused(2))
106 press("j")
107 sceneMatch(t, scenes, focused(9))
108 press("Up")
109 sceneMatch(t, scenes, focused(2))
110 press("Left")
111 sceneMatch(t, scenes, focused(1))
112 press("Down")
113 sceneMatch(t, scenes, focused(9))
114}
115
116// Closing through a real bind follows the preview, not the covering float.
117func TestOverviewCloseBindTargetsPreview(t *testing.T) {
118 cfg := scrollDefaults()
119 client := make(chan ports.ClientEvent, 8)
120 input := make(chan ports.InputEvent, 8)
121 output := make(chan ports.OutputEvent, 8)
122 commands := make(chan ports.ClientCommand, 256)
123 scenes := make(chan []ports.Scene, 1)
124 c, err := core.New(cfg, core.Channels{Client: client, Input: input, Output: output, Commands: commands, Scenes: scenes}, core.Options{})
125 if err != nil {
126 t.Fatal(err)
127 }
128 ctx, cancel := context.WithCancel(context.Background())
129 defer cancel()
130 go c.Run(ctx)
131 output <- ports.OutputAdded{Info: ports.OutputInfo{Name: "OUT-1", Width: 300, Height: 200}}
132 scene(t, scenes)
133 for _, id := range []ports.WindowID{1, 2} {
134 client <- ports.WindowMapped{ID: id}
135 scene(t, scenes)
136 }
137 client <- ports.WindowMapped{ID: 9, Floating: true, Width: 300, Height: 200}
138 scene(t, scenes)
139 press := func(key string, mods ports.Mods) {
140 input <- ports.KeyEvent{Keysym: key, Mods: mods, Pressed: true}
141 input <- ports.KeyEvent{Keysym: key, Mods: mods}
142 }
143 press("o", ports.ModSuper)
144 sceneMatch(t, scenes, func(s ports.Scene) bool {
145 for _, w := range s.Windows {
146 if w.ID == 9 && w.Focused && w.Preview > 0 {
147 return true
148 }
149 }
150 return false
151 })
152 press("Up", 0)
153 sceneMatch(t, scenes, func(s ports.Scene) bool {
154 for _, w := range s.Windows {
155 if w.ID == 2 && w.Focused && w.Preview > 0 {
156 return true
157 }
158 }
159 return false
160 })
161 press("q", ports.ModSuper)
162 if got := next(t, commands, anyOf[ports.CloseWindow]); got.ID != 2 {
163 t.Fatalf("close target %d, want preview 2", got.ID)
164 }
165}
166
167func TestFloatOnlyMonitorDirectionalExit(t *testing.T) {
168 for _, dir := range []int{-1, 1} {
169 r := startMulti(t, nil, left, right)
170 if dir < 0 {
171 r.key(t, "Right", ports.ModAlt|ports.ModCtrl)
172 }
173 r.client <- ports.WindowMapped{ID: 9, Floating: true, Width: 20, Height: 20}
174 receive(t, r.scenes)
175 key := "Right"
176 want := right.Name
177 if dir < 0 {
178 key, want = "Left", left.Name
179 }
180 r.key(t, key, ports.ModAlt)
181 out := lastOutputs(t, r.commands)
182 if out.Focused != want {
183 t.Fatalf("direction %d: focused output %q, want %q", dir, out.Focused, want)
184 }
185 }
186}
187
188// Three-finger navigation changes selection during movement and resets on lift.
189func TestOverviewThreeFingerNavigation(t *testing.T) {
190 cfg := scrollDefaults()
191 client := make(chan ports.ClientEvent, 8)
192 input := make(chan ports.InputEvent, 8)
193 output := make(chan ports.OutputEvent, 8)
194 commands := make(chan ports.ClientCommand, 256)
195 scenes := make(chan []ports.Scene, 1)
196 c, err := core.New(cfg, core.Channels{Client: client, Input: input, Output: output, Commands: commands, Scenes: scenes}, core.Options{})
197 if err != nil {
198 t.Fatal(err)
199 }
200 ctx, cancel := context.WithCancel(context.Background())
201 defer cancel()
202 go c.Run(ctx)
203 output <- ports.OutputAdded{Info: ports.OutputInfo{Name: "OUT-1", Width: 300, Height: 200}}
204 scene(t, scenes)
205 for id := ports.WindowID(1); id <= 3; id++ {
206 client <- ports.WindowMapped{ID: id}
207 scene(t, scenes)
208 }
209 focused := func(id ports.WindowID) func(ports.Scene) bool {
210 return func(s ports.Scene) bool {
211 for _, w := range s.Windows {
212 if w.ID == id && w.Focused && w.Preview > 0 {
213 return true
214 }
215 }
216 return false
217 }
218 }
219 input <- ports.KeyEvent{Keysym: "o", Mods: ports.ModSuper, Pressed: true}
220 sceneMatch(t, scenes, focused(3))
221 input <- ports.KeyEvent{Keysym: "o", Mods: ports.ModSuper}
222 finger := func(dx, dy float64) ports.SwipeUpdate {
223 return ports.SwipeUpdate{DX: dx, DY: dy}
224 }
225 input <- ports.SwipeBegin{Fingers: 3}
226 // 50 left, lifted, then 50, 50 and 100 left: one step (to 2), the
227 // next ones are longer. Without the reset it would be two (to 1), and
228 // the l below would land on 2.
229 input <- finger(-50, 5)
230 input <- ports.SwipeEnd{}
231 input <- ports.SwipeBegin{Fingers: 3}
232 input <- finger(-50, 5)
233 input <- finger(-50, 0)
234 input <- finger(-100, 0)
235 sceneMatch(t, scenes, focused(2))
236 input <- ports.SwipeEnd{}
237 input <- ports.SwipeBegin{Fingers: 3}
238 input <- ports.KeyEvent{Keysym: "l", Pressed: true}
239 sceneMatch(t, scenes, focused(3))
240 input <- ports.KeyEvent{Keysym: "l"}
241 input <- finger(-60, 0)
242 sceneMatch(t, scenes, focused(2))
243 input <- ports.SwipeEnd{}
244 input <- ports.SwipeBegin{Fingers: 3}
245 // A wheel notch down has nowhere to go (no workspace below with
246 // windows); a finger scroll left moves on.
247 input <- ports.PointerAxis{Source: ports.AxisWheel, Vertical: ports.ScrollAxis{Set: true, Value: 15, V120: 120}}
248 input <- finger(-70, 0)
249 sceneMatch(t, scenes, focused(1))
250}