1package wayland
2
3import (
4 "slices"
5 "testing"
6
7 "github.com/bnema/go-wayland-bindings/server/pointergestures"
8 "github.com/bnema/go-wayland-bindings/server/wayland"
9 "github.com/bnema/neferwl/internal/ports"
10 "github.com/bnema/wlturbo"
11)
12
13// gestureLog records the events of a zwp_pointer_gesture_*_v1 object as
14// floats: the opcode, then its arguments (fixed values as they are).
15type gestureLog struct {
16 wlturbo.BaseProxy
17 kind ports.GestureKind
18 events chan []float64
19}
20
21func (p *gestureLog) Dispatch(e *wlturbo.Event) {
22 ev := []float64{float64(e.Opcode)}
23 // Hold has no update: its end is opcode 1.
24 op := e.Opcode
25 if p.kind == ports.GestureHold && op == 1 {
26 op = uint16(pointergestures.ZwpPointerGestureSwipeV1EventEnd)
27 }
28 switch op {
29 case uint16(pointergestures.ZwpPointerGestureSwipeV1EventBegin):
30 // serial, time, surface, fingers
31 for range 4 {
32 ev = append(ev, float64(e.Uint32()))
33 }
34 case uint16(pointergestures.ZwpPointerGestureSwipeV1EventUpdate):
35 // time, then dx and dy (and scale and rotation of a pinch)
36 fixed := 2
37 if p.kind == ports.GesturePinch {
38 fixed = 4
39 }
40 ev = append(ev, float64(e.Uint32()))
41 for range fixed {
42 ev = append(ev, e.Fixed().Float64())
43 }
44 case uint16(pointergestures.ZwpPointerGestureSwipeV1EventEnd):
45 ev = append(ev, float64(e.Uint32()), float64(e.Uint32()), float64(e.Int32()))
46 }
47 p.events <- ev
48}
49
50func gestureObjects(t *testing.T, c *wlturbo.Display, manager uint32) (swipe, pinch, hold chan []float64) {
51 t.Helper()
52 seat := bindProtocol(t, c, "wl_seat")
53 registerProtocol(t, c, seat)
54 pointer := c.AllocateID()
55 registerProtocol(t, c, pointer)
56 requestProtocol(t, c, seat, wayland.SeatRequestGetPointer, pointer)
57 var logs [3]chan []float64
58 for i, op := range []uint32{pointergestures.ZwpPointerGesturesV1RequestGetSwipeGesture, pointergestures.ZwpPointerGesturesV1RequestGetPinchGesture, pointergestures.ZwpPointerGesturesV1RequestGetHoldGesture} {
59 id := c.AllocateID()
60 p := &gestureLog{kind: ports.GestureKind(i), events: make(chan []float64, 16)}
61 p.SetID(id)
62 registerWireProxy(c, p)
63 requestProtocol(t, c, manager, op, id, pointer)
64 logs[i] = p.events
65 }
66 return logs[0], logs[1], logs[2]
67}
68
69// quiet fails when the log got an event after a roundtrip.
70func quiet(t *testing.T, c *wlturbo.Display, events chan []float64) {
71 t.Helper()
72 if err := c.Roundtrip(); err != nil {
73 t.Fatal(err)
74 }
75 select {
76 case ev := <-events:
77 t.Fatalf("unexpected event %v", ev)
78 default:
79 }
80}
81
82func TestPointerGestures(t *testing.T) {
83 s, events, commands, dir := lifecycleServer(t)
84 c := protocolClient(t, s, dir)
85 manager := bindVersion(t, c, "zwp_pointer_gestures_v1", 3)
86 swipe, pinch, hold := gestureObjects(t, c, manager)
87 w, surf, _ := surfaceMapper(t, c, events)()
88 // Another client with gestures never has the pointer.
89 other := protocolClient(t, s, dir)
90 oSwipe, oPinch, oHold := gestureObjects(t, other, bindVersion(t, other, "zwp_pointer_gestures_v1", 3))
91
92 // No gesture reaches a client before it has the pointer.
93 commands <- ports.GestureBeginTo{ID: w.ID, Kind: ports.GesturePinch, Fingers: 2}
94 commands <- ports.PointerFocus{ID: w.ID, X: 10, Y: 10}
95 quiet(t, c, pinch)
96
97 // A pinch: begin carries the serial, time, surface and fingers; updates
98 // the centre, scale and rotation; the end a later serial.
99 commands <- ports.GestureBeginTo{ID: w.ID, Kind: ports.GesturePinch, Fingers: 2, Time: 1000 * 1e6}
100 begin := next(t, c, pinch)
101 if begin[0] != 0 || begin[2] != 1000 || begin[3] != float64(surf) || begin[4] != 2 {
102 t.Fatalf("pinch begin %v", begin)
103 }
104 commands <- ports.GestureUpdateTo{ID: w.ID, Kind: ports.GesturePinch, DX: 1.5, DY: -2, Scale: 1.25, Rotation: 10, Time: 1008 * 1e6}
105 if ev := next(t, c, pinch); !slices.Equal(ev, []float64{1, 1008, 1.5, -2, 1.25, 10}) {
106 t.Fatalf("pinch update %v", ev)
107 }
108 commands <- ports.GestureEndTo{ID: w.ID, Kind: ports.GesturePinch, Time: 1016 * 1e6}
109 end := next(t, c, pinch)
110 if end[0] != 2 || end[1] <= begin[1] || end[2] != 1016 || end[3] != 0 {
111 t.Fatalf("pinch end %v (begin %v)", end, begin)
112 }
113
114 // A swipe and a hold, the hold ended cancelled.
115 commands <- ports.GestureBeginTo{ID: w.ID, Kind: ports.GestureSwipe, Fingers: 5, Time: 2000 * 1e6}
116 commands <- ports.GestureUpdateTo{ID: w.ID, Kind: ports.GestureSwipe, DX: 3, DY: 4, Time: 2008 * 1e6}
117 commands <- ports.GestureEndTo{ID: w.ID, Kind: ports.GestureSwipe, Time: 2016 * 1e6}
118 if ev := next(t, c, swipe); ev[0] != 0 || ev[4] != 5 {
119 t.Fatalf("swipe begin %v", ev)
120 }
121 if ev := next(t, c, swipe); !slices.Equal(ev, []float64{1, 2008, 3, 4}) {
122 t.Fatalf("swipe update %v", ev)
123 }
124 if ev := next(t, c, swipe); ev[0] != 2 || ev[3] != 0 {
125 t.Fatalf("swipe end %v", ev)
126 }
127 commands <- ports.GestureBeginTo{ID: w.ID, Kind: ports.GestureHold, Fingers: 3, Time: 3000 * 1e6}
128 commands <- ports.GestureEndTo{ID: w.ID, Kind: ports.GestureHold, Cancelled: true, Time: 3008 * 1e6}
129 if ev := next(t, c, hold); ev[0] != 0 || ev[4] != 3 {
130 t.Fatalf("hold begin %v", ev)
131 }
132 if ev := next(t, c, hold); ev[0] != 1 || ev[3] != 1 {
133 t.Fatalf("hold end %v", ev)
134 }
135
136 // A gesture of one kind does not reach the objects of the others, and an
137 // update of a gesture that is not running is dropped.
138 commands <- ports.GestureUpdateTo{ID: w.ID, Kind: ports.GesturePinch, Scale: 2}
139 quiet(t, c, pinch)
140 quiet(t, c, swipe)
141 quiet(t, c, hold)
142
143 // The pointer leaving the window cancels the gesture. Commands run in
144 // order: the next pinch event being this begin proves the dropped
145 // update above sent nothing.
146 commands <- ports.GestureBeginTo{ID: w.ID, Kind: ports.GesturePinch, Fingers: 2, Time: 4000 * 1e6}
147 if ev := next(t, c, pinch); ev[0] != 0 || ev[2] != 4000 {
148 t.Fatalf("pinch begin %v", ev)
149 }
150 commands <- ports.PointerFocus{}
151 if ev := next(t, c, pinch); ev[0] != 2 || ev[3] != 1 {
152 t.Fatalf("pinch not cancelled on leave: %v", ev)
153 }
154 commands <- ports.GestureEndTo{ID: w.ID, Kind: ports.GesturePinch, Time: 4008 * 1e6}
155 quiet(t, c, pinch)
156
157 // Releasing the manager keeps its objects.
158 commands <- ports.PointerFocus{ID: w.ID, X: 10, Y: 10}
159 requestProtocol(t, c, manager, pointergestures.ZwpPointerGesturesV1RequestRelease)
160 commands <- ports.GestureBeginTo{ID: w.ID, Kind: ports.GestureHold, Fingers: 4, Time: 5000 * 1e6}
161 if ev := next(t, c, hold); ev[0] != 0 || ev[4] != 4 {
162 t.Fatalf("hold begin after release %v", ev)
163 }
164 // The end after the cancel sent nothing: the next pinch event is this
165 // begin.
166 commands <- ports.GestureBeginTo{ID: w.ID, Kind: ports.GesturePinch, Fingers: 2, Time: 6000 * 1e6}
167 if ev := next(t, c, pinch); ev[0] != 0 || ev[2] != 6000 {
168 t.Fatalf("pinch begin after cancelled end %v", ev)
169 }
170
171 // The client without the pointer saw nothing.
172 quiet(t, other, oSwipe)
173 quiet(t, other, oPinch)
174 quiet(t, other, oHold)
175}
176
177// A window with the pointer but no gesture object gets gesture commands
178// without the server failing or the client being disconnected.
179func TestPointerGesturesWithoutObjects(t *testing.T) {
180 s, events, commands, dir := lifecycleServer(t)
181 c := protocolClient(t, s, dir)
182 w, _, _ := surfaceMapper(t, c, events)()
183 commands <- ports.PointerFocus{ID: w.ID, X: 1, Y: 1}
184 commands <- ports.GestureBeginTo{ID: w.ID, Kind: ports.GesturePinch, Fingers: 2}
185 commands <- ports.GestureUpdateTo{ID: w.ID, Kind: ports.GesturePinch, Scale: 2}
186 commands <- ports.GestureEndTo{ID: w.ID, Kind: ports.GesturePinch}
187 if err := c.Roundtrip(); err != nil {
188 t.Fatal(err)
189 }
190}