1package core
2
3import (
4 "context"
5 "time"
6
7 "github.com/bnema/neferwl/internal/ports"
8)
9
10// pointerGrab is a compositor-owned pointer interaction started by cmd+button.
11// Clients get no pointer events while it runs. Implementations: borderResize
12// (border_resize.go); planned: column move within a workspace, and edge-hold
13// transfer of a dragged column to the next workspace or the adjacent monitor.
14type pointerGrab interface {
15 // valid reports whether the grab still applies to the current layout; the core aborts it otherwise.
16 valid() bool
17 button() uint32
18 motion(ctx context.Context, at time.Duration) error
19 release(ctx context.Context) error
20 cancel() // Escape: restore the state from before the grab
21 cursor() string // CSS cursor name; "" = the client's cursor
22}
23
24func (c *Core) pointerBusy() bool { return c.drag != nil || c.pgrab != nil }
25
26func (c *Core) startPointerGrab(ctx context.Context, v ports.PointerButton) (bool, error) {
27 if c.synthetic || !v.Pressed || c.pointerBusy() || c.held() != 0 || c.grab != 0 || c.cmdMod == 0 || c.mods&c.cmdMod == 0 || c.keyboard.inhibited(c.keyboardFocus()) {
28 return false, nil
29 }
30 if v.Button == btnRight {
31 if i := c.screenAt(c.cursorX, c.cursorY); i >= 0 {
32 s := c.screens[i]
33 w := s.mon.Current()
34 lx, ly := int(c.cursorX)-s.x, int(c.cursorY)-s.y
35 if !s.mon.previewing() && !s.mon.ov.open && !w.pinned() && s.mon.frameHas(float64(lx), float64(ly)) {
36 if b, ok := w.borderAt(lx, ly, max(w.gap()/2, gapPad)); ok {
37 last := c.lastBorderClick
38 if last.ws == w && last.b.kind == b.kind && last.b.col == b.col && last.b.row == b.row && v.Time >= last.at && v.Time-last.at <= 400*time.Millisecond {
39 c.lastBorderClick.ws = nil
40 now := c.now()
41 shots := c.snapshot(now)
42 w.resetBorder(b)
43 c.transition(shots, now)
44 c.buttons[v.Button], c.swallow[v.Button] = true, true
45 return true, c.publish(ctx)
46 }
47 c.lastBorderClick.at, c.lastBorderClick.ws, c.lastBorderClick.b = v.Time, w, b
48 g := newBorderResize(c, s, w, b, v.Button)
49 return true, c.beginPointerGrab(ctx, g)
50 }
51 }
52 }
53 }
54 c.lastBorderClick.ws = nil
55 return c.startDrag(ctx, v)
56}
57
58func (c *Core) beginPointerGrab(ctx context.Context, g pointerGrab) error {
59 if err := c.dismissGrabs(ctx, 0); err != nil {
60 return err
61 }
62 c.pgrab = g
63 c.buttons[g.button()] = true
64 c.grab = 0
65 if c.pointer != 0 {
66 c.pointer = 0
67 if err := c.pointerFocus(ctx, ports.PointerFocus{}); err != nil {
68 return err
69 }
70 }
71 if err := c.command(ctx, ports.CursorOverride{Shape: g.cursor()}); err != nil {
72 return err
73 }
74 return c.publish(ctx)
75}
76
77func (c *Core) endPointerGrab(ctx context.Context) error {
78 g := c.pgrab
79 c.pgrab = nil
80 if err := c.command(ctx, ports.CursorOverride{}); err != nil {
81 return err
82 }
83 return g.release(ctx)
84}
85
86// abortPointerGrab keeps the current layout and swallows the held release.
87// The caller restores state first when this is an Escape cancellation.
88func (c *Core) abortPointerGrab(ctx context.Context) error {
89 if c.pgrab == nil {
90 return nil
91 }
92 c.swallow[c.pgrab.button()] = true
93 c.pgrab = nil
94 return c.command(ctx, ports.CursorOverride{})
95}