feat(wayland): serve zwp_pointer_gestures_v1 to clients
- commit
- 9c0be447afc151511a6ef0d769dc131b0446f48a
- author
- brice · · 2026-10-08 09:32 UTC
- committer
- brice · · 2026-10-08 09:59 UTC
- parent
- 9447c77
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
7 files · +432 -5
M docs/config.md +2 -0
| @@ -152,6 +152,8 @@ Over the shown stash a sideways swipe slides it with the fingers, with a stop on | |||
| 152 | 152 | ||
| 153 | 153 | Two-finger scroll goes to the window under the pointer with the touchpad's timestamps, so apps with kinetic scrolling keep their inertia. | |
| 154 | 154 | ||
| 155 | + | Three- and four-finger swipes belong to the compositor. Every other touchpad gesture goes to the window under the pointer through `zwp_pointer_gestures_v1` (version 3): swipes of other finger counts, pinch (zoom and rotation) and hold. A gesture stays with the window that had the pointer when it began; it ends as cancelled if the pointer moves to another window or the window closes. Nothing is sent to windows while the session is locked. | |
| 156 | + | ||
| 155 | 157 | ## HDR | |
| 156 | 158 | ||
| 157 | 159 | `output.<name>.hdr = on` sends HDR10 (BT.2020, PQ) to displays whose EDID and connector support it. The desktop stays SDR content, shown at `sdr-brightness` nits. | |
| @@ -152,6 +152,8 @@ Over the shown stash a sideways swipe slides it with the fingers, with a stop on | |||||
| 152 | 152 | ||||
| 153 | Two-finger scroll goes to the window under the pointer with the touchpad's timestamps, so apps with kinetic scrolling keep their inertia. | 153 | Two-finger scroll goes to the window under the pointer with the touchpad's timestamps, so apps with kinetic scrolling keep their inertia. | ||
| 154 | 154 | ||||
| 155 | + | Three- and four-finger swipes belong to the compositor. Every other touchpad gesture goes to the window under the pointer through `zwp_pointer_gestures_v1` (version 3): swipes of other finger counts, pinch (zoom and rotation) and hold. A gesture stays with the window that had the pointer when it began; it ends as cancelled if the pointer moves to another window or the window closes. Nothing is sent to windows while the session is locked. | |||
| 156 | + | ||||
| 155 | ## HDR | 157 | ## HDR | ||
| 156 | 158 | ||||
| 157 | `output.<name>.hdr = on` sends HDR10 (BT.2020, PQ) to displays whose EDID and connector support it. The desktop stays SDR content, shown at `sdr-brightness` nits. | 159 | `output.<name>.hdr = on` sends HDR10 (BT.2020, PQ) to displays whose EDID and connector support it. The desktop stays SDR content, shown at `sdr-brightness` nits. | ||
A internal/adapters/wayland/gestures.go +220 -0
| @@ -0,0 +1,220 @@ | |||
| 1 | + | package wayland | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "time" | |
| 5 | + | ||
| 6 | + | "github.com/bnema/go-wayland-bindings/server/pointergestures" | |
| 7 | + | "github.com/bnema/go-wayland-bindings/server/wayland" | |
| 8 | + | "github.com/bnema/neferwl/internal/ports" | |
| 9 | + | "github.com/bnema/purego-libwayland/server" | |
| 10 | + | ) | |
| 11 | + | ||
| 12 | + | // zwp_pointer_gestures_v1: touchpad swipes (other than the compositor's own | |
| 13 | + | // three and four fingers), pinches and holds go to the client under the | |
| 14 | + | // pointer. A gesture belongs to the window that had the pointer when it | |
| 15 | + | // began; it ends cancelled when the pointer leaves that window. | |
| 16 | + | ||
| 17 | + | // gestureObject is one swipe, pinch or hold object of a client. Exactly one | |
| 18 | + | // of the three is set. | |
| 19 | + | type gestureObject struct { | |
| 20 | + | swipe *pointergestures.ZwpPointerGestureSwipeV1 | |
| 21 | + | pinch *pointergestures.ZwpPointerGesturePinchV1 | |
| 22 | + | hold *pointergestures.ZwpPointerGestureHoldV1 | |
| 23 | + | } | |
| 24 | + | ||
| 25 | + | func (g *gestureObject) alive() bool { | |
| 26 | + | switch { | |
| 27 | + | case g.swipe != nil: | |
| 28 | + | return g.swipe.Resource.Alive() | |
| 29 | + | case g.pinch != nil: | |
| 30 | + | return g.pinch.Resource.Alive() | |
| 31 | + | case g.hold != nil: | |
| 32 | + | return g.hold.Resource.Alive() | |
| 33 | + | } | |
| 34 | + | return false | |
| 35 | + | } | |
| 36 | + | ||
| 37 | + | func (g *gestureObject) kind() ports.GestureKind { | |
| 38 | + | switch { | |
| 39 | + | case g.pinch != nil: | |
| 40 | + | return ports.GesturePinch | |
| 41 | + | case g.hold != nil: | |
| 42 | + | return ports.GestureHold | |
| 43 | + | } | |
| 44 | + | return ports.GestureSwipe | |
| 45 | + | } | |
| 46 | + | ||
| 47 | + | func (g *gestureObject) begin(serial uint32, at uint32, surf *wayland.Surface, fingers uint32) { | |
| 48 | + | switch { | |
| 49 | + | case g.swipe != nil: | |
| 50 | + | g.swipe.SendBegin(serial, at, surf, fingers) | |
| 51 | + | case g.pinch != nil: | |
| 52 | + | g.pinch.SendBegin(serial, at, surf, fingers) | |
| 53 | + | case g.hold != nil: | |
| 54 | + | g.hold.SendBegin(serial, at, surf, fingers) | |
| 55 | + | } | |
| 56 | + | } | |
| 57 | + | ||
| 58 | + | func (g *gestureObject) update(u ports.GestureUpdateTo) { | |
| 59 | + | at := wireMsec(u.Time) | |
| 60 | + | dx, dy := server.FixedFromFloat(u.DX), server.FixedFromFloat(u.DY) | |
| 61 | + | switch { | |
| 62 | + | case g.swipe != nil: | |
| 63 | + | g.swipe.SendUpdate(at, dx, dy) | |
| 64 | + | case g.pinch != nil: | |
| 65 | + | g.pinch.SendUpdate(at, dx, dy, server.FixedFromFloat(u.Scale), server.FixedFromFloat(u.Rotation)) | |
| 66 | + | } | |
| 67 | + | } | |
| 68 | + | ||
| 69 | + | func (g *gestureObject) end(serial uint32, at uint32, cancelled int32) { | |
| 70 | + | switch { | |
| 71 | + | case g.swipe != nil: | |
| 72 | + | g.swipe.SendEnd(serial, at, cancelled) | |
| 73 | + | case g.pinch != nil: | |
| 74 | + | g.pinch.SendEnd(serial, at, cancelled) | |
| 75 | + | case g.hold != nil: | |
| 76 | + | g.hold.SendEnd(serial, at, cancelled) | |
| 77 | + | } | |
| 78 | + | } | |
| 79 | + | ||
| 80 | + | // activeGesture is the gesture sent to a window: the objects that got its | |
| 81 | + | // begin are the ones that get the rest, even when a client makes more | |
| 82 | + | // meanwhile. | |
| 83 | + | type activeGesture struct { | |
| 84 | + | id ports.WindowID | |
| 85 | + | kind ports.GestureKind | |
| 86 | + | objs []*gestureObject | |
| 87 | + | // at is the latest device time sent, for a cancellation. | |
| 88 | + | at time.Duration | |
| 89 | + | } | |
| 90 | + | ||
| 91 | + | func registerPointerGestures(d *server.Display, s *Server) error { | |
| 92 | + | return pointergestures.NewZwpPointerGesturesV1Global(d, 3, func(c server.Client, v, id uint32) { | |
| 93 | + | _, _ = pointergestures.NewZwpPointerGesturesV1(c, int32(v), id, gestureManager{s}) | |
| 94 | + | }) | |
| 95 | + | } | |
| 96 | + | ||
| 97 | + | // gestureManager is zwp_pointer_gestures_v1. Releasing it leaves the | |
| 98 | + | // gesture objects it made alive, as the protocol says. | |
| 99 | + | type gestureManager struct{ server *Server } | |
| 100 | + | ||
| 101 | + | func (gestureManager) Release(*pointergestures.ZwpPointerGesturesV1) {} | |
| 102 | + | ||
| 103 | + | // gestureDestroy handles the destroy request of the three gesture objects; | |
| 104 | + | // the resource's OnDestroy forgets the object. | |
| 105 | + | type gestureDestroy struct{} | |
| 106 | + | ||
| 107 | + | func (gestureDestroy) Destroy(*pointergestures.ZwpPointerGestureSwipeV1) {} | |
| 108 | + | ||
| 109 | + | type pinchDestroy struct{} | |
| 110 | + | ||
| 111 | + | func (pinchDestroy) Destroy(*pointergestures.ZwpPointerGesturePinchV1) {} | |
| 112 | + | ||
| 113 | + | type holdDestroy struct{} | |
| 114 | + | ||
| 115 | + | func (holdDestroy) Destroy(*pointergestures.ZwpPointerGestureHoldV1) {} | |
| 116 | + | ||
| 117 | + | func (m gestureManager) GetSwipeGesture(r *pointergestures.ZwpPointerGesturesV1, id uint32, _ *wayland.Pointer) { | |
| 118 | + | if res, err := pointergestures.NewZwpPointerGestureSwipeV1(r.Client(), r.Version(), id, gestureDestroy{}); err == nil { | |
| 119 | + | m.server.addGesture(r.Client(), &gestureObject{swipe: res}, res.Resource) | |
| 120 | + | } | |
| 121 | + | } | |
| 122 | + | ||
| 123 | + | func (m gestureManager) GetPinchGesture(r *pointergestures.ZwpPointerGesturesV1, id uint32, _ *wayland.Pointer) { | |
| 124 | + | if res, err := pointergestures.NewZwpPointerGesturePinchV1(r.Client(), r.Version(), id, pinchDestroy{}); err == nil { | |
| 125 | + | m.server.addGesture(r.Client(), &gestureObject{pinch: res}, res.Resource) | |
| 126 | + | } | |
| 127 | + | } | |
| 128 | + | ||
| 129 | + | func (m gestureManager) GetHoldGesture(r *pointergestures.ZwpPointerGesturesV1, id uint32, _ *wayland.Pointer) { | |
| 130 | + | if res, err := pointergestures.NewZwpPointerGestureHoldV1(r.Client(), r.Version(), id, holdDestroy{}); err == nil { | |
| 131 | + | m.server.addGesture(r.Client(), &gestureObject{hold: res}, res.Resource) | |
| 132 | + | } | |
| 133 | + | } | |
| 134 | + | ||
| 135 | + | // addGesture lists a client's gesture object until its resource dies. | |
| 136 | + | func (s *Server) addGesture(c server.Client, o *gestureObject, res *server.Resource) { | |
| 137 | + | s.gestures[c] = append(s.gestures[c], o) | |
| 138 | + | res.OnDestroy = func() { | |
| 139 | + | if list := removeItem(s.gestures[c], o); len(list) > 0 { | |
| 140 | + | s.gestures[c] = list | |
| 141 | + | } else { | |
| 142 | + | delete(s.gestures, c) | |
| 143 | + | } | |
| 144 | + | } | |
| 145 | + | } | |
| 146 | + | ||
| 147 | + | // beginGesture sends the begin of a gesture to the objects of the kind that | |
| 148 | + | // the client of the pointer-focus window made. A gesture still running | |
| 149 | + | // ends cancelled first. Nothing is recorded when the client has no object. | |
| 150 | + | func (s *Server) beginGesture(c ports.GestureBeginTo) { | |
| 151 | + | s.cancelGesture() | |
| 152 | + | if c.ID != s.seat.pointerFocus || c.Fingers <= 0 { | |
| 153 | + | return | |
| 154 | + | } | |
| 155 | + | surf, _, _, _ := s.pointerSurface(c.ID, 0, 0) | |
| 156 | + | if surf == nil { | |
| 157 | + | return | |
| 158 | + | } | |
| 159 | + | var objs []*gestureObject | |
| 160 | + | for _, o := range s.gestures[surf.Client()] { | |
| 161 | + | if o.kind() == c.Kind && o.alive() { | |
| 162 | + | objs = append(objs, o) | |
| 163 | + | } | |
| 164 | + | } | |
| 165 | + | if len(objs) == 0 { | |
| 166 | + | return | |
| 167 | + | } | |
| 168 | + | s.seat.gesture = &activeGesture{id: c.ID, kind: c.Kind, objs: objs, at: c.Time} | |
| 169 | + | s.serial++ | |
| 170 | + | for _, o := range objs { | |
| 171 | + | o.begin(s.serial, wireMsec(c.Time), surf, uint32(c.Fingers)) | |
| 172 | + | } | |
| 173 | + | } | |
| 174 | + | ||
| 175 | + | // updateGesture moves the running gesture of the window. | |
| 176 | + | func (s *Server) updateGesture(c ports.GestureUpdateTo) { | |
| 177 | + | g := s.seat.gesture | |
| 178 | + | if g == nil || g.id != c.ID || g.kind != c.Kind { | |
| 179 | + | return | |
| 180 | + | } | |
| 181 | + | g.at = c.Time | |
| 182 | + | for _, o := range g.objs { | |
| 183 | + | if o.alive() { | |
| 184 | + | o.update(c) | |
| 185 | + | } | |
| 186 | + | } | |
| 187 | + | } | |
| 188 | + | ||
| 189 | + | // endGesture ends the running gesture of the window. | |
| 190 | + | func (s *Server) endGesture(c ports.GestureEndTo) { | |
| 191 | + | g := s.seat.gesture | |
| 192 | + | if g == nil || g.id != c.ID || g.kind != c.Kind { | |
| 193 | + | return | |
| 194 | + | } | |
| 195 | + | g.at = c.Time | |
| 196 | + | s.finishGesture(c.Cancelled) | |
| 197 | + | } | |
| 198 | + | ||
| 199 | + | // cancelGesture ends the running gesture, if any, as cancelled: the | |
| 200 | + | // pointer left its window or the window went. | |
| 201 | + | func (s *Server) cancelGesture() { | |
| 202 | + | if s.seat.gesture != nil { | |
| 203 | + | s.finishGesture(true) | |
| 204 | + | } | |
| 205 | + | } | |
| 206 | + | ||
| 207 | + | func (s *Server) finishGesture(cancelled bool) { | |
| 208 | + | g := s.seat.gesture | |
| 209 | + | s.seat.gesture = nil | |
| 210 | + | state := int32(0) | |
| 211 | + | if cancelled { | |
| 212 | + | state = 1 | |
| 213 | + | } | |
| 214 | + | s.serial++ | |
| 215 | + | for _, o := range g.objs { | |
| 216 | + | if o.alive() { | |
| 217 | + | o.end(s.serial, wireMsec(g.at), state) | |
| 218 | + | } | |
| 219 | + | } | |
| 220 | + | } | |
| @@ -0,0 +1,220 @@ | |||||
| 1 | + | package wayland | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "time" | |||
| 5 | + | ||||
| 6 | + | "github.com/bnema/go-wayland-bindings/server/pointergestures" | |||
| 7 | + | "github.com/bnema/go-wayland-bindings/server/wayland" | |||
| 8 | + | "github.com/bnema/neferwl/internal/ports" | |||
| 9 | + | "github.com/bnema/purego-libwayland/server" | |||
| 10 | + | ) | |||
| 11 | + | ||||
| 12 | + | // zwp_pointer_gestures_v1: touchpad swipes (other than the compositor's own | |||
| 13 | + | // three and four fingers), pinches and holds go to the client under the | |||
| 14 | + | // pointer. A gesture belongs to the window that had the pointer when it | |||
| 15 | + | // began; it ends cancelled when the pointer leaves that window. | |||
| 16 | + | ||||
| 17 | + | // gestureObject is one swipe, pinch or hold object of a client. Exactly one | |||
| 18 | + | // of the three is set. | |||
| 19 | + | type gestureObject struct { | |||
| 20 | + | swipe *pointergestures.ZwpPointerGestureSwipeV1 | |||
| 21 | + | pinch *pointergestures.ZwpPointerGesturePinchV1 | |||
| 22 | + | hold *pointergestures.ZwpPointerGestureHoldV1 | |||
| 23 | + | } | |||
| 24 | + | ||||
| 25 | + | func (g *gestureObject) alive() bool { | |||
| 26 | + | switch { | |||
| 27 | + | case g.swipe != nil: | |||
| 28 | + | return g.swipe.Resource.Alive() | |||
| 29 | + | case g.pinch != nil: | |||
| 30 | + | return g.pinch.Resource.Alive() | |||
| 31 | + | case g.hold != nil: | |||
| 32 | + | return g.hold.Resource.Alive() | |||
| 33 | + | } | |||
| 34 | + | return false | |||
| 35 | + | } | |||
| 36 | + | ||||
| 37 | + | func (g *gestureObject) kind() ports.GestureKind { | |||
| 38 | + | switch { | |||
| 39 | + | case g.pinch != nil: | |||
| 40 | + | return ports.GesturePinch | |||
| 41 | + | case g.hold != nil: | |||
| 42 | + | return ports.GestureHold | |||
| 43 | + | } | |||
| 44 | + | return ports.GestureSwipe | |||
| 45 | + | } | |||
| 46 | + | ||||
| 47 | + | func (g *gestureObject) begin(serial uint32, at uint32, surf *wayland.Surface, fingers uint32) { | |||
| 48 | + | switch { | |||
| 49 | + | case g.swipe != nil: | |||
| 50 | + | g.swipe.SendBegin(serial, at, surf, fingers) | |||
| 51 | + | case g.pinch != nil: | |||
| 52 | + | g.pinch.SendBegin(serial, at, surf, fingers) | |||
| 53 | + | case g.hold != nil: | |||
| 54 | + | g.hold.SendBegin(serial, at, surf, fingers) | |||
| 55 | + | } | |||
| 56 | + | } | |||
| 57 | + | ||||
| 58 | + | func (g *gestureObject) update(u ports.GestureUpdateTo) { | |||
| 59 | + | at := wireMsec(u.Time) | |||
| 60 | + | dx, dy := server.FixedFromFloat(u.DX), server.FixedFromFloat(u.DY) | |||
| 61 | + | switch { | |||
| 62 | + | case g.swipe != nil: | |||
| 63 | + | g.swipe.SendUpdate(at, dx, dy) | |||
| 64 | + | case g.pinch != nil: | |||
| 65 | + | g.pinch.SendUpdate(at, dx, dy, server.FixedFromFloat(u.Scale), server.FixedFromFloat(u.Rotation)) | |||
| 66 | + | } | |||
| 67 | + | } | |||
| 68 | + | ||||
| 69 | + | func (g *gestureObject) end(serial uint32, at uint32, cancelled int32) { | |||
| 70 | + | switch { | |||
| 71 | + | case g.swipe != nil: | |||
| 72 | + | g.swipe.SendEnd(serial, at, cancelled) | |||
| 73 | + | case g.pinch != nil: | |||
| 74 | + | g.pinch.SendEnd(serial, at, cancelled) | |||
| 75 | + | case g.hold != nil: | |||
| 76 | + | g.hold.SendEnd(serial, at, cancelled) | |||
| 77 | + | } | |||
| 78 | + | } | |||
| 79 | + | ||||
| 80 | + | // activeGesture is the gesture sent to a window: the objects that got its | |||
| 81 | + | // begin are the ones that get the rest, even when a client makes more | |||
| 82 | + | // meanwhile. | |||
| 83 | + | type activeGesture struct { | |||
| 84 | + | id ports.WindowID | |||
| 85 | + | kind ports.GestureKind | |||
| 86 | + | objs []*gestureObject | |||
| 87 | + | // at is the latest device time sent, for a cancellation. | |||
| 88 | + | at time.Duration | |||
| 89 | + | } | |||
| 90 | + | ||||
| 91 | + | func registerPointerGestures(d *server.Display, s *Server) error { | |||
| 92 | + | return pointergestures.NewZwpPointerGesturesV1Global(d, 3, func(c server.Client, v, id uint32) { | |||
| 93 | + | _, _ = pointergestures.NewZwpPointerGesturesV1(c, int32(v), id, gestureManager{s}) | |||
| 94 | + | }) | |||
| 95 | + | } | |||
| 96 | + | ||||
| 97 | + | // gestureManager is zwp_pointer_gestures_v1. Releasing it leaves the | |||
| 98 | + | // gesture objects it made alive, as the protocol says. | |||
| 99 | + | type gestureManager struct{ server *Server } | |||
| 100 | + | ||||
| 101 | + | func (gestureManager) Release(*pointergestures.ZwpPointerGesturesV1) {} | |||
| 102 | + | ||||
| 103 | + | // gestureDestroy handles the destroy request of the three gesture objects; | |||
| 104 | + | // the resource's OnDestroy forgets the object. | |||
| 105 | + | type gestureDestroy struct{} | |||
| 106 | + | ||||
| 107 | + | func (gestureDestroy) Destroy(*pointergestures.ZwpPointerGestureSwipeV1) {} | |||
| 108 | + | ||||
| 109 | + | type pinchDestroy struct{} | |||
| 110 | + | ||||
| 111 | + | func (pinchDestroy) Destroy(*pointergestures.ZwpPointerGesturePinchV1) {} | |||
| 112 | + | ||||
| 113 | + | type holdDestroy struct{} | |||
| 114 | + | ||||
| 115 | + | func (holdDestroy) Destroy(*pointergestures.ZwpPointerGestureHoldV1) {} | |||
| 116 | + | ||||
| 117 | + | func (m gestureManager) GetSwipeGesture(r *pointergestures.ZwpPointerGesturesV1, id uint32, _ *wayland.Pointer) { | |||
| 118 | + | if res, err := pointergestures.NewZwpPointerGestureSwipeV1(r.Client(), r.Version(), id, gestureDestroy{}); err == nil { | |||
| 119 | + | m.server.addGesture(r.Client(), &gestureObject{swipe: res}, res.Resource) | |||
| 120 | + | } | |||
| 121 | + | } | |||
| 122 | + | ||||
| 123 | + | func (m gestureManager) GetPinchGesture(r *pointergestures.ZwpPointerGesturesV1, id uint32, _ *wayland.Pointer) { | |||
| 124 | + | if res, err := pointergestures.NewZwpPointerGesturePinchV1(r.Client(), r.Version(), id, pinchDestroy{}); err == nil { | |||
| 125 | + | m.server.addGesture(r.Client(), &gestureObject{pinch: res}, res.Resource) | |||
| 126 | + | } | |||
| 127 | + | } | |||
| 128 | + | ||||
| 129 | + | func (m gestureManager) GetHoldGesture(r *pointergestures.ZwpPointerGesturesV1, id uint32, _ *wayland.Pointer) { | |||
| 130 | + | if res, err := pointergestures.NewZwpPointerGestureHoldV1(r.Client(), r.Version(), id, holdDestroy{}); err == nil { | |||
| 131 | + | m.server.addGesture(r.Client(), &gestureObject{hold: res}, res.Resource) | |||
| 132 | + | } | |||
| 133 | + | } | |||
| 134 | + | ||||
| 135 | + | // addGesture lists a client's gesture object until its resource dies. | |||
| 136 | + | func (s *Server) addGesture(c server.Client, o *gestureObject, res *server.Resource) { | |||
| 137 | + | s.gestures[c] = append(s.gestures[c], o) | |||
| 138 | + | res.OnDestroy = func() { | |||
| 139 | + | if list := removeItem(s.gestures[c], o); len(list) > 0 { | |||
| 140 | + | s.gestures[c] = list | |||
| 141 | + | } else { | |||
| 142 | + | delete(s.gestures, c) | |||
| 143 | + | } | |||
| 144 | + | } | |||
| 145 | + | } | |||
| 146 | + | ||||
| 147 | + | // beginGesture sends the begin of a gesture to the objects of the kind that | |||
| 148 | + | // the client of the pointer-focus window made. A gesture still running | |||
| 149 | + | // ends cancelled first. Nothing is recorded when the client has no object. | |||
| 150 | + | func (s *Server) beginGesture(c ports.GestureBeginTo) { | |||
| 151 | + | s.cancelGesture() | |||
| 152 | + | if c.ID != s.seat.pointerFocus || c.Fingers <= 0 { | |||
| 153 | + | return | |||
| 154 | + | } | |||
| 155 | + | surf, _, _, _ := s.pointerSurface(c.ID, 0, 0) | |||
| 156 | + | if surf == nil { | |||
| 157 | + | return | |||
| 158 | + | } | |||
| 159 | + | var objs []*gestureObject | |||
| 160 | + | for _, o := range s.gestures[surf.Client()] { | |||
| 161 | + | if o.kind() == c.Kind && o.alive() { | |||
| 162 | + | objs = append(objs, o) | |||
| 163 | + | } | |||
| 164 | + | } | |||
| 165 | + | if len(objs) == 0 { | |||
| 166 | + | return | |||
| 167 | + | } | |||
| 168 | + | s.seat.gesture = &activeGesture{id: c.ID, kind: c.Kind, objs: objs, at: c.Time} | |||
| 169 | + | s.serial++ | |||
| 170 | + | for _, o := range objs { | |||
| 171 | + | o.begin(s.serial, wireMsec(c.Time), surf, uint32(c.Fingers)) | |||
| 172 | + | } | |||
| 173 | + | } | |||
| 174 | + | ||||
| 175 | + | // updateGesture moves the running gesture of the window. | |||
| 176 | + | func (s *Server) updateGesture(c ports.GestureUpdateTo) { | |||
| 177 | + | g := s.seat.gesture | |||
| 178 | + | if g == nil || g.id != c.ID || g.kind != c.Kind { | |||
| 179 | + | return | |||
| 180 | + | } | |||
| 181 | + | g.at = c.Time | |||
| 182 | + | for _, o := range g.objs { | |||
| 183 | + | if o.alive() { | |||
| 184 | + | o.update(c) | |||
| 185 | + | } | |||
| 186 | + | } | |||
| 187 | + | } | |||
| 188 | + | ||||
| 189 | + | // endGesture ends the running gesture of the window. | |||
| 190 | + | func (s *Server) endGesture(c ports.GestureEndTo) { | |||
| 191 | + | g := s.seat.gesture | |||
| 192 | + | if g == nil || g.id != c.ID || g.kind != c.Kind { | |||
| 193 | + | return | |||
| 194 | + | } | |||
| 195 | + | g.at = c.Time | |||
| 196 | + | s.finishGesture(c.Cancelled) | |||
| 197 | + | } | |||
| 198 | + | ||||
| 199 | + | // cancelGesture ends the running gesture, if any, as cancelled: the | |||
| 200 | + | // pointer left its window or the window went. | |||
| 201 | + | func (s *Server) cancelGesture() { | |||
| 202 | + | if s.seat.gesture != nil { | |||
| 203 | + | s.finishGesture(true) | |||
| 204 | + | } | |||
| 205 | + | } | |||
| 206 | + | ||||
| 207 | + | func (s *Server) finishGesture(cancelled bool) { | |||
| 208 | + | g := s.seat.gesture | |||
| 209 | + | s.seat.gesture = nil | |||
| 210 | + | state := int32(0) | |||
| 211 | + | if cancelled { | |||
| 212 | + | state = 1 | |||
| 213 | + | } | |||
| 214 | + | s.serial++ | |||
| 215 | + | for _, o := range g.objs { | |||
| 216 | + | if o.alive() { | |||
| 217 | + | o.end(s.serial, wireMsec(g.at), state) | |||
| 218 | + | } | |||
| 219 | + | } | |||
| 220 | + | } | |||
A internal/adapters/wayland/gestures_test.go +180 -0
| @@ -0,0 +1,180 @@ | |||
| 1 | + | package wayland | |
| 2 | + | ||
| 3 | + | import ( | |
| 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). | |
| 15 | + | type gestureLog struct { | |
| 16 | + | wlturbo.BaseProxy | |
| 17 | + | kind ports.GestureKind | |
| 18 | + | events chan []float64 | |
| 19 | + | } | |
| 20 | + | ||
| 21 | + | func (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 | + | ||
| 50 | + | func 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. | |
| 70 | + | func 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 | + | ||
| 82 | + | func 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. | |
| 144 | + | commands <- ports.GestureBeginTo{ID: w.ID, Kind: ports.GesturePinch, Fingers: 2, Time: 4000 * 1e6} | |
| 145 | + | next(t, c, pinch) | |
| 146 | + | commands <- ports.PointerFocus{} | |
| 147 | + | if ev := next(t, c, pinch); ev[0] != 2 || ev[3] != 1 { | |
| 148 | + | t.Fatalf("pinch not cancelled on leave: %v", ev) | |
| 149 | + | } | |
| 150 | + | commands <- ports.GestureEndTo{ID: w.ID, Kind: ports.GesturePinch, Time: 4008 * 1e6} | |
| 151 | + | quiet(t, c, pinch) | |
| 152 | + | ||
| 153 | + | // Releasing the manager keeps its objects. | |
| 154 | + | commands <- ports.PointerFocus{ID: w.ID, X: 10, Y: 10} | |
| 155 | + | requestProtocol(t, c, manager, pointergestures.ZwpPointerGesturesV1RequestRelease) | |
| 156 | + | commands <- ports.GestureBeginTo{ID: w.ID, Kind: ports.GestureHold, Fingers: 4, Time: 5000 * 1e6} | |
| 157 | + | if ev := next(t, c, hold); ev[0] != 0 || ev[4] != 4 { | |
| 158 | + | t.Fatalf("hold begin after release %v", ev) | |
| 159 | + | } | |
| 160 | + | ||
| 161 | + | // The client without the pointer saw nothing. | |
| 162 | + | quiet(t, other, oSwipe) | |
| 163 | + | quiet(t, other, oPinch) | |
| 164 | + | quiet(t, other, oHold) | |
| 165 | + | } | |
| 166 | + | ||
| 167 | + | // A window with the pointer but no gesture object gets nothing, and a | |
| 168 | + | // gesture on a window that does not have the pointer is refused. | |
| 169 | + | func TestPointerGesturesWithoutObjects(t *testing.T) { | |
| 170 | + | s, events, commands, dir := lifecycleServer(t) | |
| 171 | + | c := protocolClient(t, s, dir) | |
| 172 | + | w, _, _ := surfaceMapper(t, c, events)() | |
| 173 | + | commands <- ports.PointerFocus{ID: w.ID, X: 1, Y: 1} | |
| 174 | + | commands <- ports.GestureBeginTo{ID: w.ID, Kind: ports.GesturePinch, Fingers: 2} | |
| 175 | + | commands <- ports.GestureUpdateTo{ID: w.ID, Kind: ports.GesturePinch, Scale: 2} | |
| 176 | + | commands <- ports.GestureEndTo{ID: w.ID, Kind: ports.GesturePinch} | |
| 177 | + | if err := c.Roundtrip(); err != nil { | |
| 178 | + | t.Fatal(err) | |
| 179 | + | } | |
| 180 | + | } | |
| @@ -0,0 +1,180 @@ | |||||
| 1 | + | package wayland | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 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). | |||
| 15 | + | type gestureLog struct { | |||
| 16 | + | wlturbo.BaseProxy | |||
| 17 | + | kind ports.GestureKind | |||
| 18 | + | events chan []float64 | |||
| 19 | + | } | |||
| 20 | + | ||||
| 21 | + | func (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 | + | ||||
| 50 | + | func 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. | |||
| 70 | + | func 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 | + | ||||
| 82 | + | func 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. | |||
| 144 | + | commands <- ports.GestureBeginTo{ID: w.ID, Kind: ports.GesturePinch, Fingers: 2, Time: 4000 * 1e6} | |||
| 145 | + | next(t, c, pinch) | |||
| 146 | + | commands <- ports.PointerFocus{} | |||
| 147 | + | if ev := next(t, c, pinch); ev[0] != 2 || ev[3] != 1 { | |||
| 148 | + | t.Fatalf("pinch not cancelled on leave: %v", ev) | |||
| 149 | + | } | |||
| 150 | + | commands <- ports.GestureEndTo{ID: w.ID, Kind: ports.GesturePinch, Time: 4008 * 1e6} | |||
| 151 | + | quiet(t, c, pinch) | |||
| 152 | + | ||||
| 153 | + | // Releasing the manager keeps its objects. | |||
| 154 | + | commands <- ports.PointerFocus{ID: w.ID, X: 10, Y: 10} | |||
| 155 | + | requestProtocol(t, c, manager, pointergestures.ZwpPointerGesturesV1RequestRelease) | |||
| 156 | + | commands <- ports.GestureBeginTo{ID: w.ID, Kind: ports.GestureHold, Fingers: 4, Time: 5000 * 1e6} | |||
| 157 | + | if ev := next(t, c, hold); ev[0] != 0 || ev[4] != 4 { | |||
| 158 | + | t.Fatalf("hold begin after release %v", ev) | |||
| 159 | + | } | |||
| 160 | + | ||||
| 161 | + | // The client without the pointer saw nothing. | |||
| 162 | + | quiet(t, other, oSwipe) | |||
| 163 | + | quiet(t, other, oPinch) | |||
| 164 | + | quiet(t, other, oHold) | |||
| 165 | + | } | |||
| 166 | + | ||||
| 167 | + | // A window with the pointer but no gesture object gets nothing, and a | |||
| 168 | + | // gesture on a window that does not have the pointer is refused. | |||
| 169 | + | func TestPointerGesturesWithoutObjects(t *testing.T) { | |||
| 170 | + | s, events, commands, dir := lifecycleServer(t) | |||
| 171 | + | c := protocolClient(t, s, dir) | |||
| 172 | + | w, _, _ := surfaceMapper(t, c, events)() | |||
| 173 | + | commands <- ports.PointerFocus{ID: w.ID, X: 1, Y: 1} | |||
| 174 | + | commands <- ports.GestureBeginTo{ID: w.ID, Kind: ports.GesturePinch, Fingers: 2} | |||
| 175 | + | commands <- ports.GestureUpdateTo{ID: w.ID, Kind: ports.GesturePinch, Scale: 2} | |||
| 176 | + | commands <- ports.GestureEndTo{ID: w.ID, Kind: ports.GesturePinch} | |||
| 177 | + | if err := c.Roundtrip(); err != nil { | |||
| 178 | + | t.Fatal(err) | |||
| 179 | + | } | |||
| 180 | + | } | |||
M internal/adapters/wayland/globals.go +1 -0
| @@ -33,6 +33,7 @@ func registerGlobals(d *server.Display, o Options, s *Server) error { | |||
| 33 | 33 | func() error { return registerDMABuf(d, s, o.DMABuf) }, | |
| 34 | 34 | func() error { return registerSinglePixelBuffer(d, s) }, | |
| 35 | 35 | func() error { return registerPointerConstraints(d, s) }, | |
| 36 | + | func() error { return registerPointerGestures(d, s) }, | |
| 36 | 37 | func() error { return registerTearing(d, s) }, | |
| 37 | 38 | func() error { return registerColorManagement(d, s) }, | |
| 38 | 39 | func() error { return registerColorRepresentation(d, s) }, | |
| @@ -33,6 +33,7 @@ func registerGlobals(d *server.Display, o Options, s *Server) error { | |||||
| 33 | func() error { return registerDMABuf(d, s, o.DMABuf) }, | 33 | func() error { return registerDMABuf(d, s, o.DMABuf) }, | ||
| 34 | func() error { return registerSinglePixelBuffer(d, s) }, | 34 | func() error { return registerSinglePixelBuffer(d, s) }, | ||
| 35 | func() error { return registerPointerConstraints(d, s) }, | 35 | func() error { return registerPointerConstraints(d, s) }, | ||
| 36 | + | func() error { return registerPointerGestures(d, s) }, | |||
| 36 | func() error { return registerTearing(d, s) }, | 37 | func() error { return registerTearing(d, s) }, | ||
| 37 | func() error { return registerColorManagement(d, s) }, | 38 | func() error { return registerColorManagement(d, s) }, | ||
| 38 | func() error { return registerColorRepresentation(d, s) }, | 39 | func() error { return registerColorRepresentation(d, s) }, | ||
M internal/adapters/wayland/seat.go +17 -1
| @@ -23,7 +23,9 @@ type seatState struct { | |||
| 23 | 23 | pointerX, pointerY float64 | |
| 24 | 24 | // enters holds the serial of the enter event each wl_pointer received | |
| 25 | 25 | // for pointerFocus; a pointer warp must name its pointer's serial. | |
| 26 | - | enters map[*server.Resource]uint32 | |
| 26 | + | enters map[*server.Resource]uint32 | |
| 27 | + | // gesture is the touchpad gesture sent to pointerFocus's client. | |
| 28 | + | gesture *activeGesture | |
| 27 | 29 | wheelRest [2]int32 // v120 not yet sent as axis_discrete, per axis | |
| 28 | 30 | wheelHeld [2]float64 // axis value held back with it for pre-v8 clients | |
| 29 | 31 | pointers map[server.Client][]*wayland.Pointer | |
| @@ -64,6 +66,12 @@ func (s *Server) applyInput(cmd ports.ClientCommand) { | |||
| 64 | 66 | target = c.ID | |
| 65 | 67 | case ports.PointerAxisTo: | |
| 66 | 68 | target = c.ID | |
| 69 | + | case ports.GestureBeginTo: | |
| 70 | + | target = c.ID | |
| 71 | + | case ports.GestureUpdateTo: | |
| 72 | + | target = c.ID | |
| 73 | + | case ports.GestureEndTo: | |
| 74 | + | target = c.ID | |
| 67 | 75 | case ports.FocusWindow: | |
| 68 | 76 | target = c.ID | |
| 69 | 77 | case ports.ForwardKey: | |
| @@ -142,6 +150,12 @@ func (s *Server) applyInput(cmd ports.ClientCommand) { | |||
| 142 | 150 | for _, p := range pointers { | |
| 143 | 151 | s.sendAxis(p, c.Axis, steps, values) | |
| 144 | 152 | } | |
| 153 | + | case ports.GestureBeginTo: | |
| 154 | + | s.beginGesture(c) | |
| 155 | + | case ports.GestureUpdateTo: | |
| 156 | + | s.updateGesture(c) | |
| 157 | + | case ports.GestureEndTo: | |
| 158 | + | s.endGesture(c) | |
| 145 | 159 | case ports.SetKeymap: | |
| 146 | 160 | s.setKeymap(c) | |
| 147 | 161 | case ports.FocusWindow: | |
| @@ -486,6 +500,8 @@ func (s *Server) changePointerFocus(id ports.WindowID, x, y float64) { | |||
| 486 | 500 | return | |
| 487 | 501 | } | |
| 488 | 502 | s.seat.wheelRest, s.seat.wheelHeld = [2]int32{}, [2]float64{} | |
| 503 | + | // A gesture does not follow the pointer to another surface. | |
| 504 | + | s.cancelGesture() | |
| 489 | 505 | // The new client sets its own cursor on enter; until then, the arrow. | |
| 490 | 506 | s.cursorSurface = nil | |
| 491 | 507 | s.setCursor(ports.CursorChange{}) | |
| @@ -23,7 +23,9 @@ type seatState struct { | |||||
| 23 | pointerX, pointerY float64 | 23 | pointerX, pointerY float64 | ||
| 24 | // enters holds the serial of the enter event each wl_pointer received | 24 | // enters holds the serial of the enter event each wl_pointer received | ||
| 25 | // for pointerFocus; a pointer warp must name its pointer's serial. | 25 | // for pointerFocus; a pointer warp must name its pointer's serial. | ||
| 26 | - | enters map[*server.Resource]uint32 | 26 | + | enters map[*server.Resource]uint32 |
| 27 | + | // gesture is the touchpad gesture sent to pointerFocus's client. | |||
| 28 | + | gesture *activeGesture | |||
| 27 | wheelRest [2]int32 // v120 not yet sent as axis_discrete, per axis | 29 | wheelRest [2]int32 // v120 not yet sent as axis_discrete, per axis | ||
| 28 | wheelHeld [2]float64 // axis value held back with it for pre-v8 clients | 30 | wheelHeld [2]float64 // axis value held back with it for pre-v8 clients | ||
| 29 | pointers map[server.Client][]*wayland.Pointer | 31 | pointers map[server.Client][]*wayland.Pointer | ||
| @@ -64,6 +66,12 @@ func (s *Server) applyInput(cmd ports.ClientCommand) { | |||||
| 64 | target = c.ID | 66 | target = c.ID | ||
| 65 | case ports.PointerAxisTo: | 67 | case ports.PointerAxisTo: | ||
| 66 | target = c.ID | 68 | target = c.ID | ||
| 69 | + | case ports.GestureBeginTo: | |||
| 70 | + | target = c.ID | |||
| 71 | + | case ports.GestureUpdateTo: | |||
| 72 | + | target = c.ID | |||
| 73 | + | case ports.GestureEndTo: | |||
| 74 | + | target = c.ID | |||
| 67 | case ports.FocusWindow: | 75 | case ports.FocusWindow: | ||
| 68 | target = c.ID | 76 | target = c.ID | ||
| 69 | case ports.ForwardKey: | 77 | case ports.ForwardKey: | ||
| @@ -142,6 +150,12 @@ func (s *Server) applyInput(cmd ports.ClientCommand) { | |||||
| 142 | for _, p := range pointers { | 150 | for _, p := range pointers { | ||
| 143 | s.sendAxis(p, c.Axis, steps, values) | 151 | s.sendAxis(p, c.Axis, steps, values) | ||
| 144 | } | 152 | } | ||
| 153 | + | case ports.GestureBeginTo: | |||
| 154 | + | s.beginGesture(c) | |||
| 155 | + | case ports.GestureUpdateTo: | |||
| 156 | + | s.updateGesture(c) | |||
| 157 | + | case ports.GestureEndTo: | |||
| 158 | + | s.endGesture(c) | |||
| 145 | case ports.SetKeymap: | 159 | case ports.SetKeymap: | ||
| 146 | s.setKeymap(c) | 160 | s.setKeymap(c) | ||
| 147 | case ports.FocusWindow: | 161 | case ports.FocusWindow: | ||
| @@ -486,6 +500,8 @@ func (s *Server) changePointerFocus(id ports.WindowID, x, y float64) { | |||||
| 486 | return | 500 | return | ||
| 487 | } | 501 | } | ||
| 488 | s.seat.wheelRest, s.seat.wheelHeld = [2]int32{}, [2]float64{} | 502 | s.seat.wheelRest, s.seat.wheelHeld = [2]int32{}, [2]float64{} | ||
| 503 | + | // A gesture does not follow the pointer to another surface. | |||
| 504 | + | s.cancelGesture() | |||
| 489 | // The new client sets its own cursor on enter; until then, the arrow. | 505 | // The new client sets its own cursor on enter; until then, the arrow. | ||
| 490 | s.cursorSurface = nil | 506 | s.cursorSurface = nil | ||
| 491 | s.setCursor(ports.CursorChange{}) | 507 | s.setCursor(ports.CursorChange{}) | ||
M internal/adapters/wayland/server.go +7 -4
| @@ -256,8 +256,10 @@ type Server struct { | |||
| 256 | 256 | clipDevices []*clipDevice | |
| 257 | 257 | dataSources, primarySources, controlSources map[*server.Resource]*clipSource | |
| 258 | 258 | // Pointer constraints (constraints.go). | |
| 259 | - | regions map[*server.Resource]*region | |
| 260 | - | relatives map[server.Client][]*relativepointer.ZwpRelativePointerV1 | |
| 259 | + | regions map[*server.Resource]*region | |
| 260 | + | relatives map[server.Client][]*relativepointer.ZwpRelativePointerV1 | |
| 261 | + | // gestures are each client's zwp_pointer_gestures_v1 objects (gestures.go). | |
| 262 | + | gestures map[server.Client][]*gestureObject | |
| 261 | 263 | constraints map[*surface]*constraint | |
| 262 | 264 | constraint *constraint // the active one | |
| 263 | 265 | positioners map[*server.Resource]*positioner | |
| @@ -330,6 +332,7 @@ func New(opts Options, ch Channels, log zerowrap.Logger) (*Server, error) { | |||
| 330 | 332 | contentNotify: make(chan struct{}), | |
| 331 | 333 | regions: map[*server.Resource]*region{}, | |
| 332 | 334 | relatives: map[server.Client][]*relativepointer.ZwpRelativePointerV1{}, | |
| 335 | + | gestures: map[server.Client][]*gestureObject{}, | |
| 333 | 336 | constraints: map[*surface]*constraint{}, | |
| 334 | 337 | positioners: map[*server.Resource]*positioner{}, | |
| 335 | 338 | seat: seatState{keymapFD: -1, keyboards: make(map[server.Client][]*wayland.Keyboard), pointers: make(map[server.Client][]*wayland.Pointer), keyStamps: make(map[*server.Resource][]*inputtimestamps.ZwpInputTimestampsV1), pointerStamps: make(map[*server.Resource][]*inputtimestamps.ZwpInputTimestampsV1), enters: make(map[*server.Resource]uint32), repeatRate: opts.RepeatRate, repeatDelay: opts.RepeatDelay}, | |
| @@ -737,12 +740,12 @@ func (s *Server) apply(cmd ports.ClientCommand) { | |||
| 737 | 740 | // Only input/focus commands require an owner envelope. Config relay | |
| 738 | 741 | // also produces SetKeymap and other epoch-independent commands. | |
| 739 | 742 | switch cmd.(type) { | |
| 740 | - | case ports.PointerFocus, ports.PointerMotionTo, ports.PointerButtonTo, ports.PointerAxisTo, ports.FocusWindow, ports.ForwardKey: | |
| 743 | + | case ports.PointerFocus, ports.PointerMotionTo, ports.PointerButtonTo, ports.PointerAxisTo, ports.GestureBeginTo, ports.GestureUpdateTo, ports.GestureEndTo, ports.FocusWindow, ports.ForwardKey: | |
| 741 | 744 | return | |
| 742 | 745 | } | |
| 743 | 746 | } | |
| 744 | 747 | switch c := cmd.(type) { | |
| 745 | - | case ports.PointerFocus, ports.PointerMotionTo, ports.PointerButtonTo, ports.PointerAxisTo, ports.SetKeymap, ports.FocusWindow, ports.ForwardKey: | |
| 748 | + | case ports.PointerFocus, ports.PointerMotionTo, ports.PointerButtonTo, ports.PointerAxisTo, ports.GestureBeginTo, ports.GestureUpdateTo, ports.GestureEndTo, ports.SetKeymap, ports.FocusWindow, ports.ForwardKey: | |
| 746 | 749 | s.applyInput(c) | |
| 747 | 750 | case ports.ConfigurePopup: | |
| 748 | 751 | if w := s.windows[c.ID]; w != nil && w.popup != nil { | |
| @@ -256,8 +256,10 @@ type Server struct { | |||||
| 256 | clipDevices []*clipDevice | 256 | clipDevices []*clipDevice | ||
| 257 | dataSources, primarySources, controlSources map[*server.Resource]*clipSource | 257 | dataSources, primarySources, controlSources map[*server.Resource]*clipSource | ||
| 258 | // Pointer constraints (constraints.go). | 258 | // Pointer constraints (constraints.go). | ||
| 259 | - | regions map[*server.Resource]*region | 259 | + | regions map[*server.Resource]*region |
| 260 | - | relatives map[server.Client][]*relativepointer.ZwpRelativePointerV1 | 260 | + | relatives map[server.Client][]*relativepointer.ZwpRelativePointerV1 |
| 261 | + | // gestures are each client's zwp_pointer_gestures_v1 objects (gestures.go). | |||
| 262 | + | gestures map[server.Client][]*gestureObject | |||
| 261 | constraints map[*surface]*constraint | 263 | constraints map[*surface]*constraint | ||
| 262 | constraint *constraint // the active one | 264 | constraint *constraint // the active one | ||
| 263 | positioners map[*server.Resource]*positioner | 265 | positioners map[*server.Resource]*positioner | ||
| @@ -330,6 +332,7 @@ func New(opts Options, ch Channels, log zerowrap.Logger) (*Server, error) { | |||||
| 330 | contentNotify: make(chan struct{}), | 332 | contentNotify: make(chan struct{}), | ||
| 331 | regions: map[*server.Resource]*region{}, | 333 | regions: map[*server.Resource]*region{}, | ||
| 332 | relatives: map[server.Client][]*relativepointer.ZwpRelativePointerV1{}, | 334 | relatives: map[server.Client][]*relativepointer.ZwpRelativePointerV1{}, | ||
| 335 | + | gestures: map[server.Client][]*gestureObject{}, | |||
| 333 | constraints: map[*surface]*constraint{}, | 336 | constraints: map[*surface]*constraint{}, | ||
| 334 | positioners: map[*server.Resource]*positioner{}, | 337 | positioners: map[*server.Resource]*positioner{}, | ||
| 335 | seat: seatState{keymapFD: -1, keyboards: make(map[server.Client][]*wayland.Keyboard), pointers: make(map[server.Client][]*wayland.Pointer), keyStamps: make(map[*server.Resource][]*inputtimestamps.ZwpInputTimestampsV1), pointerStamps: make(map[*server.Resource][]*inputtimestamps.ZwpInputTimestampsV1), enters: make(map[*server.Resource]uint32), repeatRate: opts.RepeatRate, repeatDelay: opts.RepeatDelay}, | 338 | seat: seatState{keymapFD: -1, keyboards: make(map[server.Client][]*wayland.Keyboard), pointers: make(map[server.Client][]*wayland.Pointer), keyStamps: make(map[*server.Resource][]*inputtimestamps.ZwpInputTimestampsV1), pointerStamps: make(map[*server.Resource][]*inputtimestamps.ZwpInputTimestampsV1), enters: make(map[*server.Resource]uint32), repeatRate: opts.RepeatRate, repeatDelay: opts.RepeatDelay}, | ||
| @@ -737,12 +740,12 @@ func (s *Server) apply(cmd ports.ClientCommand) { | |||||
| 737 | // Only input/focus commands require an owner envelope. Config relay | 740 | // Only input/focus commands require an owner envelope. Config relay | ||
| 738 | // also produces SetKeymap and other epoch-independent commands. | 741 | // also produces SetKeymap and other epoch-independent commands. | ||
| 739 | switch cmd.(type) { | 742 | switch cmd.(type) { | ||
| 740 | - | case ports.PointerFocus, ports.PointerMotionTo, ports.PointerButtonTo, ports.PointerAxisTo, ports.FocusWindow, ports.ForwardKey: | 743 | + | case ports.PointerFocus, ports.PointerMotionTo, ports.PointerButtonTo, ports.PointerAxisTo, ports.GestureBeginTo, ports.GestureUpdateTo, ports.GestureEndTo, ports.FocusWindow, ports.ForwardKey: |
| 741 | return | 744 | return | ||
| 742 | } | 745 | } | ||
| 743 | } | 746 | } | ||
| 744 | switch c := cmd.(type) { | 747 | switch c := cmd.(type) { | ||
| 745 | - | case ports.PointerFocus, ports.PointerMotionTo, ports.PointerButtonTo, ports.PointerAxisTo, ports.SetKeymap, ports.FocusWindow, ports.ForwardKey: | 748 | + | case ports.PointerFocus, ports.PointerMotionTo, ports.PointerButtonTo, ports.PointerAxisTo, ports.GestureBeginTo, ports.GestureUpdateTo, ports.GestureEndTo, ports.SetKeymap, ports.FocusWindow, ports.ForwardKey: |
| 746 | s.applyInput(c) | 749 | s.applyInput(c) | ||
| 747 | case ports.ConfigurePopup: | 750 | case ports.ConfigurePopup: | ||
| 748 | if w := s.windows[c.ID]; w != nil && w.popup != nil { | 751 | if w := s.windows[c.ID]; w != nil && w.popup != nil { | ||
M internal/adapters/wayland/wire_client_test.go +5 -0
| @@ -32,6 +32,7 @@ import ( | |||
| 32 | 32 | wire_linuxdmabuf "github.com/bnema/go-wayland-bindings/server/linuxdmabuf" | |
| 33 | 33 | wire_linuxdrmsyncobj "github.com/bnema/go-wayland-bindings/server/linuxdrmsyncobj" | |
| 34 | 34 | wire_pointerconstraints "github.com/bnema/go-wayland-bindings/server/pointerconstraints" | |
| 35 | + | wire_pointergestures "github.com/bnema/go-wayland-bindings/server/pointergestures" | |
| 35 | 36 | wire_pointerwarp "github.com/bnema/go-wayland-bindings/server/pointerwarp" | |
| 36 | 37 | wire_presentationtime "github.com/bnema/go-wayland-bindings/server/presentationtime" | |
| 37 | 38 | wire_primaryselection "github.com/bnema/go-wayland-bindings/server/primaryselection" | |
| @@ -239,6 +240,10 @@ var wireSchemas = map[string]wireSchema{ | |||
| 239 | 240 | "zwp_primary_selection_device_v1": {wire_primaryselection.ZwpPrimarySelectionDeviceV1Interface, &client_primaryselection.PrimarySelectionDevice{}, map[uint16]bool{1: true}, nil}, | |
| 240 | 241 | "zwp_primary_selection_offer_v1": {wire_primaryselection.ZwpPrimarySelectionOfferV1Interface, &client_primaryselection.PrimarySelectionOffer{}, map[uint16]bool{1: true}, nil}, | |
| 241 | 242 | "zwp_primary_selection_source_v1": {wire_primaryselection.ZwpPrimarySelectionSourceV1Interface, &client_primaryselection.PrimarySelectionSource{}, map[uint16]bool{1: true}, nil}, | |
| 243 | + | "zwp_pointer_gestures_v1": {wire_pointergestures.ZwpPointerGesturesV1Interface, nil, map[uint16]bool{2: true}, nil}, | |
| 244 | + | "zwp_pointer_gesture_swipe_v1": {wire_pointergestures.ZwpPointerGestureSwipeV1Interface, nil, map[uint16]bool{0: true}, nil}, | |
| 245 | + | "zwp_pointer_gesture_pinch_v1": {wire_pointergestures.ZwpPointerGesturePinchV1Interface, nil, map[uint16]bool{0: true}, nil}, | |
| 246 | + | "zwp_pointer_gesture_hold_v1": {wire_pointergestures.ZwpPointerGestureHoldV1Interface, nil, map[uint16]bool{0: true}, nil}, | |
| 242 | 247 | "zwp_relative_pointer_manager_v1": {wire_relativepointer.ZwpRelativePointerManagerV1Interface, &client_relativepointer.RelativePointerManager{}, map[uint16]bool{0: true}, nil}, | |
| 243 | 248 | "zwp_relative_pointer_v1": {wire_relativepointer.ZwpRelativePointerV1Interface, &client_relativepointer.RelativePointer{}, map[uint16]bool{0: true}, nil}, | |
| 244 | 249 | "zwp_text_input_manager_v3": {wire_textinput.ZwpTextInputManagerV3Interface, &client_textinput.TextInputManagerV3{}, map[uint16]bool{0: true}, nil}, | |
| @@ -32,6 +32,7 @@ import ( | |||||
| 32 | wire_linuxdmabuf "github.com/bnema/go-wayland-bindings/server/linuxdmabuf" | 32 | wire_linuxdmabuf "github.com/bnema/go-wayland-bindings/server/linuxdmabuf" | ||
| 33 | wire_linuxdrmsyncobj "github.com/bnema/go-wayland-bindings/server/linuxdrmsyncobj" | 33 | wire_linuxdrmsyncobj "github.com/bnema/go-wayland-bindings/server/linuxdrmsyncobj" | ||
| 34 | wire_pointerconstraints "github.com/bnema/go-wayland-bindings/server/pointerconstraints" | 34 | wire_pointerconstraints "github.com/bnema/go-wayland-bindings/server/pointerconstraints" | ||
| 35 | + | wire_pointergestures "github.com/bnema/go-wayland-bindings/server/pointergestures" | |||
| 35 | wire_pointerwarp "github.com/bnema/go-wayland-bindings/server/pointerwarp" | 36 | wire_pointerwarp "github.com/bnema/go-wayland-bindings/server/pointerwarp" | ||
| 36 | wire_presentationtime "github.com/bnema/go-wayland-bindings/server/presentationtime" | 37 | wire_presentationtime "github.com/bnema/go-wayland-bindings/server/presentationtime" | ||
| 37 | wire_primaryselection "github.com/bnema/go-wayland-bindings/server/primaryselection" | 38 | wire_primaryselection "github.com/bnema/go-wayland-bindings/server/primaryselection" | ||
| @@ -239,6 +240,10 @@ var wireSchemas = map[string]wireSchema{ | |||||
| 239 | "zwp_primary_selection_device_v1": {wire_primaryselection.ZwpPrimarySelectionDeviceV1Interface, &client_primaryselection.PrimarySelectionDevice{}, map[uint16]bool{1: true}, nil}, | 240 | "zwp_primary_selection_device_v1": {wire_primaryselection.ZwpPrimarySelectionDeviceV1Interface, &client_primaryselection.PrimarySelectionDevice{}, map[uint16]bool{1: true}, nil}, | ||
| 240 | "zwp_primary_selection_offer_v1": {wire_primaryselection.ZwpPrimarySelectionOfferV1Interface, &client_primaryselection.PrimarySelectionOffer{}, map[uint16]bool{1: true}, nil}, | 241 | "zwp_primary_selection_offer_v1": {wire_primaryselection.ZwpPrimarySelectionOfferV1Interface, &client_primaryselection.PrimarySelectionOffer{}, map[uint16]bool{1: true}, nil}, | ||
| 241 | "zwp_primary_selection_source_v1": {wire_primaryselection.ZwpPrimarySelectionSourceV1Interface, &client_primaryselection.PrimarySelectionSource{}, map[uint16]bool{1: true}, nil}, | 242 | "zwp_primary_selection_source_v1": {wire_primaryselection.ZwpPrimarySelectionSourceV1Interface, &client_primaryselection.PrimarySelectionSource{}, map[uint16]bool{1: true}, nil}, | ||
| 243 | + | "zwp_pointer_gestures_v1": {wire_pointergestures.ZwpPointerGesturesV1Interface, nil, map[uint16]bool{2: true}, nil}, | |||
| 244 | + | "zwp_pointer_gesture_swipe_v1": {wire_pointergestures.ZwpPointerGestureSwipeV1Interface, nil, map[uint16]bool{0: true}, nil}, | |||
| 245 | + | "zwp_pointer_gesture_pinch_v1": {wire_pointergestures.ZwpPointerGesturePinchV1Interface, nil, map[uint16]bool{0: true}, nil}, | |||
| 246 | + | "zwp_pointer_gesture_hold_v1": {wire_pointergestures.ZwpPointerGestureHoldV1Interface, nil, map[uint16]bool{0: true}, nil}, | |||
| 242 | "zwp_relative_pointer_manager_v1": {wire_relativepointer.ZwpRelativePointerManagerV1Interface, &client_relativepointer.RelativePointerManager{}, map[uint16]bool{0: true}, nil}, | 247 | "zwp_relative_pointer_manager_v1": {wire_relativepointer.ZwpRelativePointerManagerV1Interface, &client_relativepointer.RelativePointerManager{}, map[uint16]bool{0: true}, nil}, | ||
| 243 | "zwp_relative_pointer_v1": {wire_relativepointer.ZwpRelativePointerV1Interface, &client_relativepointer.RelativePointer{}, map[uint16]bool{0: true}, nil}, | 248 | "zwp_relative_pointer_v1": {wire_relativepointer.ZwpRelativePointerV1Interface, &client_relativepointer.RelativePointer{}, map[uint16]bool{0: true}, nil}, | ||
| 244 | "zwp_text_input_manager_v3": {wire_textinput.ZwpTextInputManagerV3Interface, &client_textinput.TextInputManagerV3{}, map[uint16]bool{0: true}, nil}, | 249 | "zwp_text_input_manager_v3": {wire_textinput.ZwpTextInputManagerV3Interface, &client_textinput.TextInputManagerV3{}, map[uint16]bool{0: true}, nil}, | ||