1package core
2
3import (
4 "context"
5 "time"
6
7 "github.com/bnema/neferwl/internal/ports"
8)
9
10// Data drag: a client drag and drop (wl_data_device.start_drag). Wayland
11// validated the request against the client's press and tells core; core
12// owns the pointer for the length of the drag, as for a window drag
13// (drag.go):
14//
15// - the origin client gets the pointer leave and no pointer event after;
16// nobody else gets one: the surface under the pointer is the drag
17// target, told to wayland as DataDragFocus and DataDragMotion;
18// - releasing the drag button drops (DataDragDrop), Escape cancels
19// (DataDragCancel); neither reaches a client;
20// - scrolling and touchpad gestures go nowhere, compositor swipes
21// included; binds still run, Escape cancels the drag before the
22// switcher; the keyboard keeps its
23// focus, clients read the modifiers that choose the action from it;
24// - the drag icon, a client surface wayland measures (DataDragIcon), is
25// drawn at the cursor over everything.
26//
27// Layout changes under a still cursor reroute the drag (rehit), and the
28// drop re-hits.
29
30// dataDragState is the data drag in progress.
31type dataDragState struct {
32 // button is the pointer button that holds the drag.
33 button uint32
34 // target is the surface the drag was last routed to (0: none), at
35 // at in its coordinates.
36 target WindowID
37 at [2]float64
38 icon dataDragIcon
39}
40
41// dataDragIcon is the icon surface: its top left corner relative to the
42// cursor and its size, logical px. Hidden while W or H is 0.
43type dataDragIcon struct {
44 id WindowID
45 x, y, w, h int
46}
47
48// startDataDrag answers the start_drag wayland validated: it takes the
49// pointer if a client holds a button, else cancels the drag. A drag
50// during a window drag or a locked session is refused.
51func (c *Core) startDataDrag(ctx context.Context, v ports.DataDragStarted) error {
52 button := v.Button
53 if c.dataDrag != nil || c.drag != nil || c.security.Protected || c.grab == 0 || c.held() == 0 || !c.buttons[button] || c.swallow[button] {
54 return c.command(ctx, ports.DataDragCancel{})
55 }
56 c.dataDrag = &dataDragState{button: button, icon: dataDragIcon{id: v.Icon}}
57 // Other held buttons release without a client to tell.
58 for b := range c.buttons {
59 if b != button {
60 c.swallow[b] = true
61 }
62 }
63 c.grab = 0
64 // A compositor swipe in progress lets go of what it moved.
65 if c.swipe != nil {
66 c.dropSwipe()
67 c.swipe = nil
68 }
69 // The origin gets its pointer leave, as the drag takes the pointer.
70 if c.pointer != 0 {
71 c.pointer = 0
72 if err := c.pointerFocus(ctx, ports.PointerFocus{}); err != nil {
73 return err
74 }
75 }
76 // The surface under the pointer, the origin's usually, is the first
77 // target.
78 return c.dataDragRoute(ctx, c.motionTime)
79}
80
81// dataDragRoute sends the surface under the cursor as the drag target: a
82// focus when it changed, else a motion.
83func (c *Core) dataDragRoute(ctx context.Context, at time.Duration) error {
84 d := c.dataDrag
85 id, x, y := c.hit(c.cursorX, c.cursorY)
86 d.at = [2]float64{x, y}
87 switch {
88 case id != d.target:
89 d.target = id
90 return c.command(ctx, ports.DataDragFocus{ID: id, X: x, Y: y, Time: at})
91 case id != 0:
92 return c.command(ctx, ports.DataDragMotion{ID: id, X: x, Y: y, Time: at})
93 }
94 return nil
95}
96
97// dataDragRetarget re-routes a still cursor: the target is told only when
98// it or the point in it changed.
99func (c *Core) dataDragRetarget(ctx context.Context, at time.Duration) error {
100 d := c.dataDrag
101 if id, x, y := c.hit(c.cursorX, c.cursorY); id == d.target && (id == 0 || d.at == [2]float64{x, y}) {
102 return nil
103 }
104 return c.dataDragRoute(ctx, at)
105}
106
107// dataDragMotion follows the cursor: the target gets the motion and the
108// icon moves with it.
109func (c *Core) dataDragMotion(ctx context.Context, at time.Duration) error {
110 if err := c.dataDragRoute(ctx, at); err != nil {
111 return err
112 }
113 if c.dataDrag.icon.visible() {
114 return c.publish(ctx)
115 }
116 return nil
117}
118
119// dataDragButton handles a button event of a data drag: the release of
120// the drag button drops; the other buttons do nothing, their releases
121// neither.
122func (c *Core) dataDragButton(ctx context.Context, v ports.PointerButton) error {
123 if !v.Pressed {
124 if v.Button != c.dataDrag.button {
125 return nil
126 }
127 delete(c.buttons, v.Button)
128 // A bind may have moved the layout under a still cursor: the
129 // drop goes to what lies there now.
130 if err := c.dataDragRetarget(ctx, v.Time); err != nil {
131 return err
132 }
133 c.dataDrag = nil
134 if err := c.command(ctx, ports.DataDragDrop{}); err != nil {
135 return err
136 }
137 return c.finishDataDrag(ctx)
138 }
139 c.buttons[v.Button] = true
140 c.swallow[v.Button] = true
141 return nil
142}
143
144// cancelDataDrag ends the drag on Escape.
145func (c *Core) cancelDataDrag(ctx context.Context) error {
146 c.abortDataDrag()
147 if err := c.command(ctx, ports.DataDragCancel{}); err != nil {
148 return err
149 }
150 return c.finishDataDrag(ctx)
151}
152
153// abortDataDrag ends the drag where it is; the held button's release is
154// dropped.
155func (c *Core) abortDataDrag() {
156 if d := c.dataDrag; d != nil {
157 c.swallow[d.button] = true
158 c.dataDrag = nil
159 }
160}
161
162// finishDataDrag redraws without the icon and points the pointer at what
163// lies under the cursor.
164func (c *Core) finishDataDrag(ctx context.Context) error {
165 if err := c.publish(ctx); err != nil {
166 return err
167 }
168 return c.rehit(ctx)
169}
170
171// dataDragGone drops the target that was unmapped: it gets its leave.
172func (c *Core) dataDragGone(ctx context.Context, id WindowID) error {
173 if d := c.dataDrag; d != nil && d.target == id {
174 d.target = 0
175 return c.command(ctx, ports.DataDragFocus{Time: c.motionTime})
176 }
177 return nil
178}
179
180// setDataDragIcon records the icon geometry wayland measured. It reports
181// whether the scene changes.
182func (c *Core) setDataDragIcon(v ports.DataDragIcon) bool {
183 d := c.dataDrag
184 if d == nil || d.icon.id == 0 || v.ID != d.icon.id {
185 return false
186 }
187 icon := dataDragIcon{id: v.ID, x: v.X, y: v.Y, w: max(v.W, 0), h: max(v.H, 0)}
188 if icon == d.icon {
189 return false
190 }
191 d.icon = icon
192 return true
193}
194
195func (i dataDragIcon) visible() bool { return i.id != 0 && i.w > 0 && i.h > 0 }
196
197// dataDragIconOn is the icon as a window of screen sc (index i) when the
198// cursor is on it.
199func (c *Core) dataDragIconOn(i int, sc *screen) (ports.SceneWindow, bool) {
200 d := c.dataDrag
201 if d == nil || !d.icon.visible() || c.screenAt(c.cursorX, c.cursorY) != i {
202 return ports.SceneWindow{}, false
203 }
204 x, y := int(c.cursorX)-sc.x+d.icon.x, int(c.cursorY)-sc.y+d.icon.y
205 return ports.SceneWindow{ID: d.icon.id, Rect: Rect{X: x, Y: y, W: d.icon.w, H: d.icon.h}, Popup: true, OverLayers: true}, true
206}