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