1package core
2
3import (
4 "context"
5 "time"
6
7 "github.com/bnema/neferwl/internal/ports"
8)
9
10// A held key repeats the moves it runs in the compositor: a navigation bind
11// (focus, move, resize) or an overview key (h/j/k/l, arrows). Clients repeat
12// their own keys; these never reach a client. The pace is the compositor's,
13// slower than typing so each step can be followed: the first repeat after
14// repeatDelay, then one every repeatInterval. With animations on, each step
15// retargets the running motion, so a held key slides on smoothly.
16const (
17 repeatDelay = 250 * time.Millisecond
18 repeatInterval = 125 * time.Millisecond
19)
20
21// keyRepeat is the held key that repeats, if any.
22type keyRepeat struct {
23 // held is the physical key (heldKey); mods the modifiers it was
24 // pressed with: a change stops the repeat.
25 held string
26 mods ports.Mods
27 // action is the bind it repeats; key the overview key, when action is
28 // empty.
29 action Action
30 key ports.KeyEvent
31 // overview is whether the overview was open at the press: opening or
32 // closing it stops the repeat.
33 overview bool
34 timer ports.Timer
35}
36
37// repeats reports whether a held key repeats bind a: moves only, never an
38// action that toggles, opens, closes or spawns.
39func repeats(a Action) bool {
40 if _, _, ok := ResizeArg(a); ok {
41 return true
42 }
43 switch a {
44 case ActionFocusColumnLeft, ActionFocusColumnRight,
45 ActionFocusWindowUp, ActionFocusWindowDown,
46 ActionMoveColumnLeft, ActionMoveColumnRight,
47 ActionMoveWindowUp, ActionMoveWindowDown,
48 ActionConsumeOrExpelLeft, ActionConsumeOrExpelRight,
49 ActionFocusWorkspacePrev, ActionFocusWorkspaceNext,
50 ActionMoveWorkspacePrev, ActionMoveWorkspaceNext,
51 ActionMoveColumnToWorkspacePrev, ActionMoveColumnToWorkspaceNext,
52 ActionMoveWindowToWorkspacePrev, ActionMoveWindowToWorkspaceNext:
53 return true
54 }
55 return false
56}
57
58// overviewRepeats reports whether a held overview key repeats: the moves,
59// not Return or Escape.
60func overviewRepeats(key ports.KeyEvent) bool {
61 switch keyName(key.Keysym) {
62 case "h", "j", "k", "l", "Left", "Right", "Up", "Down":
63 return true
64 }
65 return false
66}
67
68// startRepeat repeats action, or the overview key when action is empty,
69// while key stays held with its modifiers.
70func (c *Core) startRepeat(key ports.KeyEvent, action Action) {
71 c.stopRepeat()
72 r := &c.repeat
73 r.held, r.mods, r.action, r.key = heldKey(key), key.Mods, action, key
74 r.overview = c.cur().mon.ov.open
75 r.timer = newPortTimer(c.opts.Clock, repeatDelay)
76 c.repeatC = r.timer.C()
77}
78
79// stopRepeat ends the repeat, if any.
80func (c *Core) stopRepeat() {
81 if c.repeat.timer != nil {
82 c.repeat.timer.Stop()
83 }
84 c.repeat, c.repeatC = keyRepeat{}, nil
85}
86
87// repeatKey stops the repeat on a key event that ends it: another key
88// pressed, the held key released, or the modifiers changed.
89func (c *Core) repeatKey(key ports.KeyEvent) {
90 r := &c.repeat
91 if r.timer == nil {
92 return
93 }
94 if key.Pressed || heldKey(key) == r.held || key.Mods != r.mods {
95 c.stopRepeat()
96 }
97}
98
99// repeatLive reports whether the held key still runs what it ran, and stops
100// the repeat when not: the overview opened or closed, or a launcher or a
101// menu took the keyboard. A bind also stops when a window inhibits the
102// shortcuts; overview keys run before inhibitors, as on a press.
103func (c *Core) repeatLive() bool {
104 r := c.repeat
105 if r.timer == nil {
106 return false
107 }
108 if c.cur().mon.ov.open != r.overview || c.overviewKeyboardTaken() ||
109 r.action != "" && c.keyboard.inhibited(c.keyboardFocus()) {
110 c.stopRepeat()
111 return false
112 }
113 return true
114}
115
116// repeatTick runs one repeat of the held key, if still live, and arms the
117// next.
118func (c *Core) repeatTick(ctx context.Context) error {
119 if !c.repeatLive() {
120 return nil
121 }
122 r, mon := c.repeat, c.cur().mon
123 c.inputActive = true
124 defer func() { c.inputActive = false }()
125 if r.action == "" {
126 now := c.now()
127 shots := c.snapshot(now)
128 mon.overviewKey(r.key)
129 c.transition(shots, now)
130 if err := c.workspaceVisible(ctx, true); err != nil {
131 return err
132 }
133 c.repeat.timer.Reset(repeatInterval)
134 return c.publish(ctx)
135 }
136 c.repeat.timer.Reset(repeatInterval)
137 return c.runBind(ctx, r.action)
138}