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.pointerBusy() || 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// dataDragHitChanged excludes notifications that only change metadata,
98// capture state or the drag icon. The type switch allocates nothing.
99func dataDragHitChanged(ev ports.ClientEvent) bool {
100 switch ev.(type) {
101 case ports.LayerChanged, ports.InputRegionChanged, ports.WindowMapped,
102 ports.WindowResized, ports.PopupRequest, ports.PopupMapped,
103 ports.OutputPower, ports.WindowParent, ports.WindowModal,
104 ports.WindowUnmapped, ports.PointerWarp, ports.WindowMoveRequest,
105 ports.WindowFullscreenRequest, ports.WorkspaceActivate, ports.WindowActivate:
106 return true
107 default:
108 return false
109 }
110}
111
112// dataDragRetarget re-routes a still cursor: the target is told only when
113// it or the point in it changed.
114func (c *Core) dataDragRetarget(ctx context.Context, at time.Duration) error {
115 d := c.dataDrag
116 if id, x, y := c.hit(c.cursorX, c.cursorY); id == d.target && (id == 0 || d.at == [2]float64{x, y}) {
117 return nil
118 }
119 return c.dataDragRoute(ctx, at)
120}
121
122// dataDragMotion follows the cursor: the target gets the motion and the
123// icon moves with it.
124func (c *Core) dataDragMotion(ctx context.Context, at time.Duration) error {
125 if err := c.dataDragRoute(ctx, at); err != nil {
126 return err
127 }
128 if c.dataDrag.icon.visible() {
129 return c.publish(ctx)
130 }
131 return nil
132}
133
134// dataDragButton handles a button event of a data drag: the release of
135// the drag button drops; the other buttons do nothing, their releases
136// neither.
137func (c *Core) dataDragButton(ctx context.Context, v ports.PointerButton) error {
138 if !v.Pressed {
139 if v.Button != c.dataDrag.button {
140 return nil
141 }
142 delete(c.buttons, v.Button)
143 // A bind may have moved the layout under a still cursor: the
144 // drop goes to what lies there now.
145 if err := c.dataDragRetarget(ctx, v.Time); err != nil {
146 return err
147 }
148 c.dataDrag = nil
149 if err := c.command(ctx, ports.DataDragDrop{}); err != nil {
150 return err
151 }
152 return c.finishDataDrag(ctx)
153 }
154 c.buttons[v.Button] = true
155 c.swallow[v.Button] = true
156 return nil
157}
158
159// cancelDataDrag ends the drag on Escape.
160func (c *Core) cancelDataDrag(ctx context.Context) error {
161 c.abortDataDrag()
162 if err := c.command(ctx, ports.DataDragCancel{}); err != nil {
163 return err
164 }
165 return c.finishDataDrag(ctx)
166}
167
168// abortDataDrag ends the drag where it is; the held button's release is
169// dropped.
170func (c *Core) abortDataDrag() {
171 if d := c.dataDrag; d != nil {
172 c.swallow[d.button] = true
173 c.dataDrag = nil
174 }
175}
176
177// finishDataDrag redraws without the icon and points the pointer at what
178// lies under the cursor.
179func (c *Core) finishDataDrag(ctx context.Context) error {
180 if err := c.publish(ctx); err != nil {
181 return err
182 }
183 return c.rehit(ctx)
184}
185
186// dataDragGone drops the target that was unmapped: it gets its leave.
187func (c *Core) dataDragGone(ctx context.Context, id WindowID) error {
188 if d := c.dataDrag; d != nil && d.target == id {
189 d.target = 0
190 return c.command(ctx, ports.DataDragFocus{Time: c.motionTime})
191 }
192 return nil
193}
194
195// setDataDragIcon records the icon geometry wayland measured. It reports
196// whether the scene changes.
197func (c *Core) setDataDragIcon(v ports.DataDragIcon) bool {
198 d := c.dataDrag
199 if d == nil || d.icon.id == 0 || v.ID != d.icon.id {
200 return false
201 }
202 icon := dataDragIcon{id: v.ID, x: v.X, y: v.Y, w: max(v.W, 0), h: max(v.H, 0)}
203 if icon == d.icon {
204 return false
205 }
206 d.icon = icon
207 return true
208}
209
210func (i dataDragIcon) visible() bool { return i.id != 0 && i.w > 0 && i.h > 0 }
211
212// dataDragIconOn is the icon as a window of screen sc (index i) when the
213// cursor is on it.
214func (c *Core) dataDragIconOn(i int, sc *screen) (ports.SceneWindow, bool) {
215 d := c.dataDrag
216 if d == nil || !d.icon.visible() || c.screenAt(c.cursorX, c.cursorY) != i {
217 return ports.SceneWindow{}, false
218 }
219 x, y := int(c.cursorX)-sc.x+d.icon.x, int(c.cursorY)-sc.y+d.icon.y
220 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
221}