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