1package core
2
3import (
4 "context"
5 "time"
6
7 "github.com/bnema/neferwl/internal/ports"
8)
9
10// Touchpad gestures the compositor does not use go to the client under the
11// pointer (zwp_pointer_gestures_v1): swipes of any finger count but three
12// and four, every pinch and every hold. Like scrolling, they follow the
13// pointer focus. Three- and four-finger swipes stay with the compositor
14// (gesture.go).
15
16// clientGesture is the gesture being forwarded to a client. Input streams
17// one gesture at a time. A gesture that begins with no pointer focus, or
18// whose focus moved, still takes its stream (on stays set) so the rest of
19// it never reaches the compositor or another window.
20type clientGesture struct {
21 on bool
22 kind ports.GestureKind
23 // id is the window that got the begin; 0 once cancelled or when none had
24 // the pointer.
25 id WindowID
26 // at is the device time of the latest event, for a cancellation.
27 at time.Duration
28}
29
30// forwardsSwipe reports whether the compositor leaves a swipe of that many
31// fingers to the client.
32func forwardsSwipe(fingers int) bool { return fingers != 3 && fingers != 4 }
33
34// gestureBegin starts forwarding a gesture to the window with the pointer
35// focus. One still running (its end was lost) ends cancelled first.
36func (c *Core) gestureBegin(ctx context.Context, kind ports.GestureKind, fingers int, at time.Duration) error {
37 if err := c.gestureAbort(ctx, at); err != nil {
38 return err
39 }
40 c.gesture = clientGesture{on: true, kind: kind, at: at}
41 if c.pointer == 0 || c.drag != nil {
42 return nil
43 }
44 c.gesture.id = c.pointer
45 return c.command(ctx, ports.GestureBeginTo{ID: c.pointer, Kind: kind, Fingers: fingers, Time: at})
46}
47
48// gestureUpdate moves the forwarded gesture of that kind. A window that
49// lost the pointer meanwhile gets its gesture cancelled.
50func (c *Core) gestureUpdate(ctx context.Context, u ports.GestureUpdateTo) error {
51 g := &c.gesture
52 if !g.on || g.kind != u.Kind {
53 return nil
54 }
55 g.at = u.Time
56 if g.id == 0 {
57 return nil
58 }
59 if g.id != c.pointer {
60 return c.gestureCancel(ctx)
61 }
62 u.ID = g.id
63 return c.command(ctx, u)
64}
65
66// gestureEnd ends the forwarded gesture of that kind.
67func (c *Core) gestureEnd(ctx context.Context, kind ports.GestureKind, cancelled bool, at time.Duration) error {
68 g := c.gesture
69 if !g.on || g.kind != kind {
70 return nil
71 }
72 c.gesture = clientGesture{}
73 if g.id == 0 {
74 return nil
75 }
76 if at == 0 {
77 // A removed device's end has no time: the client's clock must
78 // not run backwards.
79 at = g.at
80 }
81 return c.command(ctx, ports.GestureEndTo{ID: g.id, Kind: kind, Cancelled: cancelled || g.id != c.pointer, Time: at})
82}
83
84// gestureAbort ends whatever gesture is forwarded, cancelled.
85func (c *Core) gestureAbort(ctx context.Context, at time.Duration) error {
86 if !c.gesture.on {
87 return nil
88 }
89 return c.gestureEnd(ctx, c.gesture.kind, true, at)
90}
91
92// gestureCancel ends the gesture of the window that lost the pointer, as
93// cancelled. The rest of the device's gesture is swallowed.
94func (c *Core) gestureCancel(ctx context.Context) error {
95 g := &c.gesture
96 if !g.on || g.id == 0 {
97 return nil
98 }
99 id := g.id
100 g.id = 0
101 return c.command(ctx, ports.GestureEndTo{ID: id, Kind: g.kind, Cancelled: true, Time: g.at})
102}
103
104// pointerFocus sends a pointer focus change; the window leaving the
105// pointer first gets its gesture cancelled, so its client sees the end
106// before the leave.
107func (c *Core) pointerFocus(ctx context.Context, f ports.PointerFocus) error {
108 if c.gesture.id != 0 && c.gesture.id != f.ID {
109 if err := c.gestureCancel(ctx); err != nil {
110 return err
111 }
112 }
113 return c.command(ctx, f)
114}