1package core
2
3import (
4 "context"
5 "strconv"
6
7 "github.com/bnema/neferwl/internal/ports"
8)
9
10func (c *Core) handleInput(ctx context.Context, ev ports.InputEvent) error {
11 c.inputActive = true
12 defer func() { c.inputActive = false }()
13 if c.security.Protected {
14 if err := c.protectedInput(ctx, ev); err != nil {
15 return err
16 }
17 return nil
18 }
19 switch v := ev.(type) {
20 case ports.PointerMotion:
21 c.motionTime = v.Time
22 // A locked pointer stays still; relative motion still flows.
23 if c.constraint.Mode != ports.ConstraintLock {
24 c.cursorX, c.cursorY = c.constraint.Clamp(c.clampPointer(c.cursorX, c.cursorY, v.X, v.Y))
25 }
26 // The focused screen follows the pointer, so new windows
27 // and launchers open where the user is.
28 // Only a pointer entering another output switches: keyboard
29 // moves to another screen stick until then. Not mid-drag of a
30 // client; a window drag (drag.go) follows the pointer.
31 if i := c.screenAt(c.cursorX, c.cursorY); i >= 0 && c.screens[i].name() != c.pointerOutput && c.grab == 0 {
32 name := c.screens[i].name()
33 c.pointerOutput = name
34 if name != c.cur().name() {
35 c.focusScreen = i
36 if err := c.publish(ctx); err != nil {
37 return err
38 }
39 }
40 }
41 if c.drag != nil {
42 // The dragged window and the others get no pointer events.
43 return c.dragMotion(ctx, v.Time)
44 }
45 id, x, y := c.hit(c.cursorX, c.cursorY)
46 // Layout changes are intentionally re-hit-tested only on motion.
47 if id != c.pointer {
48 c.pointer = id
49 if err := c.command(ctx, ports.PointerFocus{ID: id, X: x, Y: y}); err != nil {
50 return err
51 }
52 }
53 c.pointerAt = [2]float64{x, y}
54 if id != 0 {
55 if err := c.command(ctx, ports.PointerMotionTo{ID: id, X: x, Y: y, DX: v.DX, DY: v.DY, UnaccelDX: v.UnaccelDX, UnaccelDY: v.UnaccelDY, Time: v.Time}); err != nil {
56 return err
57 }
58 }
59 return nil
60 case ports.PointerButton:
61 // Buttons a drag took stay in c.buttons until released, so the
62 // security gate admits their release; no client sees it.
63 if !v.Pressed && c.swallow[v.Button] {
64 delete(c.swallow, v.Button)
65 delete(c.buttons, v.Button)
66 return nil
67 }
68 if d := c.drag; d != nil {
69 if !v.Pressed && v.Button == d.button {
70 delete(c.buttons, v.Button)
71 return c.endDrag(ctx)
72 }
73 // Other buttons do nothing mid-drag; their releases neither.
74 if v.Pressed {
75 c.buttons[v.Button] = true
76 c.swallow[v.Button] = true
77 }
78 return nil
79 }
80 if started, err := c.startDrag(ctx, v); err != nil || started {
81 return err
82 }
83 if v.Pressed && c.pointer == 0 && c.grab == 0 && c.held() == 0 {
84 if picked, err := c.overviewClick(ctx); err != nil {
85 return err
86 } else if picked {
87 return nil
88 }
89 }
90 id := c.pointer
91 if c.grab != 0 {
92 id = c.grab
93 }
94 if v.Pressed && c.held() == 0 {
95 // A click outside an open menu closes it.
96 if err := c.dismissGrabs(ctx, id); err != nil {
97 return err
98 }
99 }
100 if v.Pressed {
101 if c.held() == 0 {
102 c.grab = id
103 }
104 c.buttons[v.Button] = true
105 c.lastButton = v.Button
106 } else {
107 delete(c.buttons, v.Button)
108 }
109 if id != 0 {
110 if err := c.command(ctx, ports.PointerButtonTo{ID: id, Button: v.Button, Pressed: v.Pressed, Time: v.Time}); err != nil {
111 return err
112 }
113 // A click on an on-demand layer gives it the keyboard.
114 if v.Pressed && c.onDemand(id) && c.keyboard.clickLayer(id, c.windowFocus()) {
115 if err := c.publish(ctx); err != nil {
116 return err
117 }
118 } else if l := c.keyboard.layer; v.Pressed && l != 0 && id != l && c.popupRoot(id) != l {
119 // A click anywhere else takes the keyboard back.
120 c.keyboard.takeBack()
121 if err := c.publish(ctx); err != nil {
122 return err
123 }
124 }
125 // A click focuses the window and its output.
126 s, w := c.screenOf(id)
127 if v.Pressed && s != nil && w == s.mon.Current() && (c.keyboard.sent != id || s != c.cur()) {
128 now := c.now()
129 before := c.snapshot(now)
130 w.Click(id)
131 c.focusScreen = c.screenIndex(s.name())
132 c.transition(before, now)
133 if err := c.publish(ctx); err != nil {
134 return err
135 }
136 }
137 }
138 if c.held() == 0 {
139 c.grab = 0
140 }
141 return nil
142 case ports.PointerAxis:
143 c.scrollStop(v)
144 // Only wheel/continuous scrolling navigates the overview.
145 // Two-finger scrolling belongs to clients, never to navigation.
146 if v.Source != ports.AxisFinger && c.cur().mon.ov.open && !c.overviewKeyboardTaken() {
147 // overviewScroll owns which scrolls step; the shot costs no
148 // allocation once warm.
149 now := c.now()
150 shots := c.snapshot(now)
151 if c.cur().mon.overviewScroll(v) {
152 c.transition(shots, now)
153 if err := c.workspaceVisible(ctx, true); err != nil {
154 return err
155 }
156 if err := c.publish(ctx); err != nil {
157 return err
158 }
159 }
160 return nil
161 }
162 // Scroll goes to the window under the pointer, which has the
163 // pointer focus even mid-drag. None during a window drag.
164 if c.pointer != 0 && c.drag == nil {
165 if err := c.command(ctx, ports.PointerAxisTo{ID: c.pointer, Axis: v}); err != nil {
166 return err
167 }
168 }
169 return nil
170 case ports.SwipeBegin:
171 if c.swipeBegin(v) {
172 return c.slid(ctx, false)
173 }
174 return nil
175 case ports.SwipeUpdate:
176 before := c.cur().mon.Current()
177 if c.swipeUpdate(v) {
178 return c.slid(ctx, c.cur().mon.Current() != before)
179 }
180 return nil
181 case ports.SwipeEnd:
182 return c.slid(ctx, c.swipeEnd(v))
183 }
184 key, ok := ev.(ports.KeyEvent)
185 if !ok {
186 return nil
187 }
188 c.mods = key.Mods
189 if c.drag != nil && key.Pressed && key.Keysym == "Escape" {
190 // Escape cancels the drag; its release goes nowhere either.
191 c.cancelDrag()
192 c.pressed[heldKey(key)] = true
193 if err := c.publish(ctx); err != nil {
194 return err
195 }
196 return c.rehit(ctx)
197 }
198 // The overview takes its keys before any window; others still
199 // run binds, and are not forwarded.
200 // A launcher or a menu holding the keyboard gets them first.
201 if mon := c.cur().mon; mon.ov.open && !c.overviewKeyboardTaken() {
202 now := c.now()
203 var shots []viewShot
204 if key.Pressed {
205 shots = c.snapshot(now)
206 }
207 if key.Pressed && mon.overviewKey(key) {
208 c.pressed[heldKey(key)] = true
209 c.transition(shots, now)
210 if err := c.workspaceVisible(ctx, true); err != nil {
211 return err
212 }
213 if err := c.publish(ctx); err != nil {
214 return err
215 }
216 return nil
217 }
218 }
219 // A focused window inhibiting shortcuts gets every key:
220 // no bind runs (emergency quit and VT switch are handled by
221 // input before core).
222 if c.keyboard.inhibited(c.keyboardFocus()) {
223 if held := heldKey(key); !key.Pressed && c.pressed[held] {
224 // Its press ran a bind before inhibiting began: the
225 // window never saw it.
226 delete(c.pressed, held)
227 return nil
228 }
229 if err := c.command(ctx, ports.ForwardKey{ID: c.keyboard.inhibiting, Key: key}); err != nil {
230 return err
231 }
232 return nil
233 }
234 name := keyName(key.Keysym)
235 // A bind on the keysym wins; then the unshifted keysym (Cmd+Shift+1
236 // prints exclam on US); then the physical key (code:N).
237 action, bound := c.binds[binding{key: name, mods: key.Mods}]
238 if !bound && key.Base != "" {
239 action, bound = c.binds[binding{key: keyName(key.Base), mods: key.Mods}]
240 }
241 if !bound && key.Keycode != 0 {
242 action, bound = c.binds[binding{key: "code:" + strconv.FormatUint(uint64(key.Keycode), 10), mods: key.Mods}]
243 }
244 // Track presses by physical key: Shift may be released before the key.
245 held := heldKey(key)
246 if !key.Pressed {
247 consumed := c.pressed[held]
248 delete(c.pressed, held)
249 if consumed {
250 return nil
251 }
252 }
253 if key.Pressed {
254 if bound {
255 c.pressed[held] = true
256 if action == ActionScaleUp || action == ActionScaleDown {
257 dir := 1
258 if action == ActionScaleDown {
259 dir = -1
260 }
261 sc := c.cur()
262 prev := sc.scale
263 sc.setScale(StepScale(sc.info.Width, sc.info.Height, sc.scale, dir))
264 c.order()
265 if c.ch.Scales != nil && sc.scale != prev && sc.name() != "" {
266 // Never wait on persistence: a stalled save drops the change.
267 select {
268 case c.ch.Scales <- ports.ScaleChanged{Output: sc.name(), Scale: sc.scale}:
269 default:
270 }
271 }
272 if err := c.publish(ctx); err != nil {
273 return err
274 }
275 return nil
276 }
277 before := c.cur().mon.Current()
278 swiped := c.swipedWorkspace()
279 now := c.now()
280 shots := c.snapshot(now)
281 c.keyboard.takeBack() // a bind acts on the windows
282 effect := c.applyAction(action)
283 if effect.Quit {
284 return ErrQuit
285 }
286 if effect.Spawn {
287 argv := effect.Argv
288 if argv == nil {
289 argv = append([]string(nil), c.cfg.Terminal.Command...)
290 }
291 // Explicit actions are suppressed on a full launcher queue;
292 // never keep the input owner waiting across an epoch change.
293 c.trySpawn(ctx, ports.SpawnRequest{Argv: argv})
294 if c.inputEpochChanged() {
295 return errSecurityChanged
296 }
297 }
298 if c.swipedWorkspace() != swiped {
299 c.dropSwipe()
300 }
301 c.transition(shots, now)
302 if err := c.workspaceVisible(ctx, c.cur().mon.Current() != before); err != nil {
303 return err
304 }
305 if effect.Close != 0 {
306 if err := c.command(ctx, ports.CloseWindow{ID: effect.Close}); err != nil {
307 return err
308 }
309 }
310 if err := c.publish(ctx); err != nil {
311 return err
312 }
313 return nil
314 }
315 c.pressed[held] = false
316 }
317 if id := c.keyboardFocus(); id != 0 && (!c.cur().mon.ov.open || c.overviewKeyboardTaken()) {
318 if err := c.command(ctx, ports.ForwardKey{ID: id, Key: key}); err != nil {
319 return err
320 }
321 }
322 return nil
323}