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