fix(input): store held shortcuts as set
- commit
- 36f1195f67c9ed7b85c52e6f4f316fc451ad5232
- author
- brice · · 2026-05-24 19:52 UTC
- parent
- bd18fcb
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +17 -17
M internal/ui/input/global_shortcuts.go +6 -6
| @@ -27,7 +27,7 @@ type GlobalShortcutHandler struct { | |||
| 27 | 27 | ctx context.Context | |
| 28 | 28 | registered map[KeyBinding]Action | |
| 29 | 29 | lastDispatchAt map[Action]time.Time | |
| 30 | - | heldShortcuts map[globalShortcutHoldKey]time.Time | |
| 30 | + | heldShortcuts map[globalShortcutHoldKey]struct{} | |
| 31 | 31 | keyReleasedCb func(gtk.EventControllerKey, uint, uint, gdk.ModifierType) | |
| 32 | 32 | keyReleasedHandlerID uint | |
| 33 | 33 | // generation is mutated only from the GTK main thread alongside controller replacement. | |
| @@ -60,7 +60,7 @@ func NewGlobalShortcutHandler( | |||
| 60 | 60 | callbacks: make([]gtk.ShortcutFunc, 0), | |
| 61 | 61 | registered: make(map[KeyBinding]Action), | |
| 62 | 62 | lastDispatchAt: make(map[Action]time.Time), | |
| 63 | - | heldShortcuts: make(map[globalShortcutHoldKey]time.Time), | |
| 63 | + | heldShortcuts: make(map[globalShortcutHoldKey]struct{}), | |
| 64 | 64 | } | |
| 65 | 65 | ||
| 66 | 66 | if h.controller == nil { | |
| @@ -240,7 +240,7 @@ func (h *GlobalShortcutHandler) registerShortcut(keyval uint, modifiers gdk.Modi | |||
| 240 | 240 | Msg("global shortcut ignored without current key event") | |
| 241 | 241 | return true | |
| 242 | 242 | } | |
| 243 | - | if h.suppressHeldShortcut(actionToDispatch, eventInfo, time.Now()) { | |
| 243 | + | if h.suppressHeldShortcut(actionToDispatch, eventInfo) { | |
| 244 | 244 | log.Trace(). | |
| 245 | 245 | Str("action", string(actionToDispatch)). | |
| 246 | 246 | Str("shortcut", formatBinding(bindingForLog)). | |
| @@ -467,7 +467,7 @@ func (h *GlobalShortcutHandler) ReloadShortcuts(ctx context.Context, workspace * | |||
| 467 | 467 | h.callbacks = make([]gtk.ShortcutFunc, 0) | |
| 468 | 468 | h.registered = make(map[KeyBinding]Action) | |
| 469 | 469 | h.lastDispatchAt = make(map[Action]time.Time) | |
| 470 | - | h.heldShortcuts = make(map[globalShortcutHoldKey]time.Time) | |
| 470 | + | h.heldShortcuts = make(map[globalShortcutHoldKey]struct{}) | |
| 471 | 471 | ||
| 472 | 472 | h.registerDefaultGlobalShortcuts(log) | |
| 473 | 473 | ||
| @@ -552,7 +552,7 @@ func (h *GlobalShortcutHandler) isActiveWindowShortcutHandler() bool { | |||
| 552 | 552 | return h != nil && h.controller != nil && h.window != nil && h.window.IsActive() | |
| 553 | 553 | } | |
| 554 | 554 | ||
| 555 | - | func (h *GlobalShortcutHandler) suppressHeldShortcut(action Action, info globalShortcutEventInfo, now time.Time) bool { | |
| 555 | + | func (h *GlobalShortcutHandler) suppressHeldShortcut(action Action, info globalShortcutEventInfo) bool { | |
| 556 | 556 | if h == nil || !isHeldGlobalShortcutSuppressed(action) || !info.hasCurrentEvent { | |
| 557 | 557 | return false | |
| 558 | 558 | } | |
| @@ -568,7 +568,7 @@ func (h *GlobalShortcutHandler) suppressHeldShortcut(action Action, info globalS | |||
| 568 | 568 | if _, ok := h.heldShortcuts[key]; ok { | |
| 569 | 569 | return true | |
| 570 | 570 | } | |
| 571 | - | h.heldShortcuts[key] = now | |
| 571 | + | h.heldShortcuts[key] = struct{}{} | |
| 572 | 572 | return false | |
| 573 | 573 | } | |
| 574 | 574 | ||
| @@ -27,7 +27,7 @@ type GlobalShortcutHandler struct { | |||||
| 27 | ctx context.Context | 27 | ctx context.Context | ||
| 28 | registered map[KeyBinding]Action | 28 | registered map[KeyBinding]Action | ||
| 29 | lastDispatchAt map[Action]time.Time | 29 | lastDispatchAt map[Action]time.Time | ||
| 30 | - | heldShortcuts map[globalShortcutHoldKey]time.Time | 30 | + | heldShortcuts map[globalShortcutHoldKey]struct{} |
| 31 | keyReleasedCb func(gtk.EventControllerKey, uint, uint, gdk.ModifierType) | 31 | keyReleasedCb func(gtk.EventControllerKey, uint, uint, gdk.ModifierType) | ||
| 32 | keyReleasedHandlerID uint | 32 | keyReleasedHandlerID uint | ||
| 33 | // generation is mutated only from the GTK main thread alongside controller replacement. | 33 | // generation is mutated only from the GTK main thread alongside controller replacement. | ||
| @@ -60,7 +60,7 @@ func NewGlobalShortcutHandler( | |||||
| 60 | callbacks: make([]gtk.ShortcutFunc, 0), | 60 | callbacks: make([]gtk.ShortcutFunc, 0), | ||
| 61 | registered: make(map[KeyBinding]Action), | 61 | registered: make(map[KeyBinding]Action), | ||
| 62 | lastDispatchAt: make(map[Action]time.Time), | 62 | lastDispatchAt: make(map[Action]time.Time), | ||
| 63 | - | heldShortcuts: make(map[globalShortcutHoldKey]time.Time), | 63 | + | heldShortcuts: make(map[globalShortcutHoldKey]struct{}), |
| 64 | } | 64 | } | ||
| 65 | 65 | ||||
| 66 | if h.controller == nil { | 66 | if h.controller == nil { | ||
| @@ -240,7 +240,7 @@ func (h *GlobalShortcutHandler) registerShortcut(keyval uint, modifiers gdk.Modi | |||||
| 240 | Msg("global shortcut ignored without current key event") | 240 | Msg("global shortcut ignored without current key event") | ||
| 241 | return true | 241 | return true | ||
| 242 | } | 242 | } | ||
| 243 | - | if h.suppressHeldShortcut(actionToDispatch, eventInfo, time.Now()) { | 243 | + | if h.suppressHeldShortcut(actionToDispatch, eventInfo) { |
| 244 | log.Trace(). | 244 | log.Trace(). | ||
| 245 | Str("action", string(actionToDispatch)). | 245 | Str("action", string(actionToDispatch)). | ||
| 246 | Str("shortcut", formatBinding(bindingForLog)). | 246 | Str("shortcut", formatBinding(bindingForLog)). | ||
| @@ -467,7 +467,7 @@ func (h *GlobalShortcutHandler) ReloadShortcuts(ctx context.Context, workspace * | |||||
| 467 | h.callbacks = make([]gtk.ShortcutFunc, 0) | 467 | h.callbacks = make([]gtk.ShortcutFunc, 0) | ||
| 468 | h.registered = make(map[KeyBinding]Action) | 468 | h.registered = make(map[KeyBinding]Action) | ||
| 469 | h.lastDispatchAt = make(map[Action]time.Time) | 469 | h.lastDispatchAt = make(map[Action]time.Time) | ||
| 470 | - | h.heldShortcuts = make(map[globalShortcutHoldKey]time.Time) | 470 | + | h.heldShortcuts = make(map[globalShortcutHoldKey]struct{}) |
| 471 | 471 | ||||
| 472 | h.registerDefaultGlobalShortcuts(log) | 472 | h.registerDefaultGlobalShortcuts(log) | ||
| 473 | 473 | ||||
| @@ -552,7 +552,7 @@ func (h *GlobalShortcutHandler) isActiveWindowShortcutHandler() bool { | |||||
| 552 | return h != nil && h.controller != nil && h.window != nil && h.window.IsActive() | 552 | return h != nil && h.controller != nil && h.window != nil && h.window.IsActive() | ||
| 553 | } | 553 | } | ||
| 554 | 554 | ||||
| 555 | - | func (h *GlobalShortcutHandler) suppressHeldShortcut(action Action, info globalShortcutEventInfo, now time.Time) bool { | 555 | + | func (h *GlobalShortcutHandler) suppressHeldShortcut(action Action, info globalShortcutEventInfo) bool { |
| 556 | if h == nil || !isHeldGlobalShortcutSuppressed(action) || !info.hasCurrentEvent { | 556 | if h == nil || !isHeldGlobalShortcutSuppressed(action) || !info.hasCurrentEvent { | ||
| 557 | return false | 557 | return false | ||
| 558 | } | 558 | } | ||
| @@ -568,7 +568,7 @@ func (h *GlobalShortcutHandler) suppressHeldShortcut(action Action, info globalS | |||||
| 568 | if _, ok := h.heldShortcuts[key]; ok { | 568 | if _, ok := h.heldShortcuts[key]; ok { | ||
| 569 | return true | 569 | return true | ||
| 570 | } | 570 | } | ||
| 571 | - | h.heldShortcuts[key] = now | 571 | + | h.heldShortcuts[key] = struct{}{} |
| 572 | return false | 572 | return false | ||
| 573 | } | 573 | } | ||
| 574 | 574 | ||||
M internal/ui/input/global_shortcuts_test.go +11 -11
| @@ -189,48 +189,48 @@ func TestGlobalShortcutHandlerSuppressesActionSwitchLastTab(t *testing.T) { | |||
| 189 | 189 | } | |
| 190 | 190 | ||
| 191 | 191 | func TestGlobalShortcutHandlerSuppressesHeldGlobalShortcutUntilRelease(t *testing.T) { | |
| 192 | - | h := &GlobalShortcutHandler{heldShortcuts: make(map[globalShortcutHoldKey]time.Time)} | |
| 192 | + | h := &GlobalShortcutHandler{heldShortcuts: make(map[globalShortcutHoldKey]struct{})} | |
| 193 | 193 | info := globalShortcutEventInfo{hasCurrentEvent: true, eventType: gdk.KeyPressValue, eventKeyval: uint('f'), eventKeycode: 41} | |
| 194 | 194 | ||
| 195 | - | if h.suppressHeldShortcut(ActionToggleFloatingPane, info, time.Unix(100, 0)) { | |
| 195 | + | if h.suppressHeldShortcut(ActionToggleFloatingPane, info) { | |
| 196 | 196 | t.Fatal("first held global shortcut dispatch was suppressed") | |
| 197 | 197 | } | |
| 198 | - | if !h.suppressHeldShortcut(ActionToggleFloatingPane, info, time.Unix(101, 0)) { | |
| 198 | + | if !h.suppressHeldShortcut(ActionToggleFloatingPane, info) { | |
| 199 | 199 | t.Fatal("held global shortcut repeat after cooldown was not suppressed") | |
| 200 | 200 | } | |
| 201 | 201 | ||
| 202 | 202 | h.releaseHeldGlobalShortcuts(uint('x'), 0) | |
| 203 | - | if !h.suppressHeldShortcut(ActionToggleFloatingPane, info, time.Unix(101, 0)) { | |
| 203 | + | if !h.suppressHeldShortcut(ActionToggleFloatingPane, info) { | |
| 204 | 204 | t.Fatal("unrelated key release re-armed held global shortcut") | |
| 205 | 205 | } | |
| 206 | 206 | ||
| 207 | 207 | h.releaseHeldGlobalShortcuts(uint('f'), 0) | |
| 208 | - | if h.suppressHeldShortcut(ActionToggleFloatingPane, info, time.Unix(103, 0)) { | |
| 208 | + | if h.suppressHeldShortcut(ActionToggleFloatingPane, info) { | |
| 209 | 209 | t.Fatal("matching key release did not re-arm held global shortcut") | |
| 210 | 210 | } | |
| 211 | 211 | } | |
| 212 | 212 | ||
| 213 | 213 | func TestGlobalShortcutHandlerSuppressesHeldModeAction(t *testing.T) { | |
| 214 | - | h := &GlobalShortcutHandler{heldShortcuts: make(map[globalShortcutHoldKey]time.Time)} | |
| 214 | + | h := &GlobalShortcutHandler{heldShortcuts: make(map[globalShortcutHoldKey]struct{})} | |
| 215 | 215 | info := globalShortcutEventInfo{hasCurrentEvent: true, eventType: gdk.KeyPressValue, eventKeyval: uint('p'), eventKeycode: 33} | |
| 216 | 216 | ||
| 217 | - | if h.suppressHeldShortcut(ActionEnterPaneMode, info, time.Unix(100, 0)) { | |
| 217 | + | if h.suppressHeldShortcut(ActionEnterPaneMode, info) { | |
| 218 | 218 | t.Fatal("first mode shortcut dispatch was suppressed") | |
| 219 | 219 | } | |
| 220 | - | if !h.suppressHeldShortcut(ActionEnterPaneMode, info, time.Unix(101, 0)) { | |
| 220 | + | if !h.suppressHeldShortcut(ActionEnterPaneMode, info) { | |
| 221 | 221 | t.Fatal("held mode shortcut repeat was not suppressed") | |
| 222 | 222 | } | |
| 223 | 223 | } | |
| 224 | 224 | ||
| 225 | 225 | func TestGlobalShortcutHandlerSuppressesHeldFloatingProfileAction(t *testing.T) { | |
| 226 | - | h := &GlobalShortcutHandler{heldShortcuts: make(map[globalShortcutHoldKey]time.Time)} | |
| 226 | + | h := &GlobalShortcutHandler{heldShortcuts: make(map[globalShortcutHoldKey]struct{})} | |
| 227 | 227 | info := globalShortcutEventInfo{hasCurrentEvent: true, eventType: gdk.KeyPressValue, eventKeyval: uint('o'), eventKeycode: 32} | |
| 228 | 228 | action := NewFloatingProfileAction("work", "https://example.com") | |
| 229 | 229 | ||
| 230 | - | if h.suppressHeldShortcut(action, info, time.Unix(100, 0)) { | |
| 230 | + | if h.suppressHeldShortcut(action, info) { | |
| 231 | 231 | t.Fatal("first floating-profile shortcut dispatch was suppressed") | |
| 232 | 232 | } | |
| 233 | - | if !h.suppressHeldShortcut(action, info, time.Unix(101, 0)) { | |
| 233 | + | if !h.suppressHeldShortcut(action, info) { | |
| 234 | 234 | t.Fatal("held floating-profile shortcut repeat was not suppressed") | |
| 235 | 235 | } | |
| 236 | 236 | } | |
| @@ -189,48 +189,48 @@ func TestGlobalShortcutHandlerSuppressesActionSwitchLastTab(t *testing.T) { | |||||
| 189 | } | 189 | } | ||
| 190 | 190 | ||||
| 191 | func TestGlobalShortcutHandlerSuppressesHeldGlobalShortcutUntilRelease(t *testing.T) { | 191 | func TestGlobalShortcutHandlerSuppressesHeldGlobalShortcutUntilRelease(t *testing.T) { | ||
| 192 | - | h := &GlobalShortcutHandler{heldShortcuts: make(map[globalShortcutHoldKey]time.Time)} | 192 | + | h := &GlobalShortcutHandler{heldShortcuts: make(map[globalShortcutHoldKey]struct{})} |
| 193 | info := globalShortcutEventInfo{hasCurrentEvent: true, eventType: gdk.KeyPressValue, eventKeyval: uint('f'), eventKeycode: 41} | 193 | info := globalShortcutEventInfo{hasCurrentEvent: true, eventType: gdk.KeyPressValue, eventKeyval: uint('f'), eventKeycode: 41} | ||
| 194 | 194 | ||||
| 195 | - | if h.suppressHeldShortcut(ActionToggleFloatingPane, info, time.Unix(100, 0)) { | 195 | + | if h.suppressHeldShortcut(ActionToggleFloatingPane, info) { |
| 196 | t.Fatal("first held global shortcut dispatch was suppressed") | 196 | t.Fatal("first held global shortcut dispatch was suppressed") | ||
| 197 | } | 197 | } | ||
| 198 | - | if !h.suppressHeldShortcut(ActionToggleFloatingPane, info, time.Unix(101, 0)) { | 198 | + | if !h.suppressHeldShortcut(ActionToggleFloatingPane, info) { |
| 199 | t.Fatal("held global shortcut repeat after cooldown was not suppressed") | 199 | t.Fatal("held global shortcut repeat after cooldown was not suppressed") | ||
| 200 | } | 200 | } | ||
| 201 | 201 | ||||
| 202 | h.releaseHeldGlobalShortcuts(uint('x'), 0) | 202 | h.releaseHeldGlobalShortcuts(uint('x'), 0) | ||
| 203 | - | if !h.suppressHeldShortcut(ActionToggleFloatingPane, info, time.Unix(101, 0)) { | 203 | + | if !h.suppressHeldShortcut(ActionToggleFloatingPane, info) { |
| 204 | t.Fatal("unrelated key release re-armed held global shortcut") | 204 | t.Fatal("unrelated key release re-armed held global shortcut") | ||
| 205 | } | 205 | } | ||
| 206 | 206 | ||||
| 207 | h.releaseHeldGlobalShortcuts(uint('f'), 0) | 207 | h.releaseHeldGlobalShortcuts(uint('f'), 0) | ||
| 208 | - | if h.suppressHeldShortcut(ActionToggleFloatingPane, info, time.Unix(103, 0)) { | 208 | + | if h.suppressHeldShortcut(ActionToggleFloatingPane, info) { |
| 209 | t.Fatal("matching key release did not re-arm held global shortcut") | 209 | t.Fatal("matching key release did not re-arm held global shortcut") | ||
| 210 | } | 210 | } | ||
| 211 | } | 211 | } | ||
| 212 | 212 | ||||
| 213 | func TestGlobalShortcutHandlerSuppressesHeldModeAction(t *testing.T) { | 213 | func TestGlobalShortcutHandlerSuppressesHeldModeAction(t *testing.T) { | ||
| 214 | - | h := &GlobalShortcutHandler{heldShortcuts: make(map[globalShortcutHoldKey]time.Time)} | 214 | + | h := &GlobalShortcutHandler{heldShortcuts: make(map[globalShortcutHoldKey]struct{})} |
| 215 | info := globalShortcutEventInfo{hasCurrentEvent: true, eventType: gdk.KeyPressValue, eventKeyval: uint('p'), eventKeycode: 33} | 215 | info := globalShortcutEventInfo{hasCurrentEvent: true, eventType: gdk.KeyPressValue, eventKeyval: uint('p'), eventKeycode: 33} | ||
| 216 | 216 | ||||
| 217 | - | if h.suppressHeldShortcut(ActionEnterPaneMode, info, time.Unix(100, 0)) { | 217 | + | if h.suppressHeldShortcut(ActionEnterPaneMode, info) { |
| 218 | t.Fatal("first mode shortcut dispatch was suppressed") | 218 | t.Fatal("first mode shortcut dispatch was suppressed") | ||
| 219 | } | 219 | } | ||
| 220 | - | if !h.suppressHeldShortcut(ActionEnterPaneMode, info, time.Unix(101, 0)) { | 220 | + | if !h.suppressHeldShortcut(ActionEnterPaneMode, info) { |
| 221 | t.Fatal("held mode shortcut repeat was not suppressed") | 221 | t.Fatal("held mode shortcut repeat was not suppressed") | ||
| 222 | } | 222 | } | ||
| 223 | } | 223 | } | ||
| 224 | 224 | ||||
| 225 | func TestGlobalShortcutHandlerSuppressesHeldFloatingProfileAction(t *testing.T) { | 225 | func TestGlobalShortcutHandlerSuppressesHeldFloatingProfileAction(t *testing.T) { | ||
| 226 | - | h := &GlobalShortcutHandler{heldShortcuts: make(map[globalShortcutHoldKey]time.Time)} | 226 | + | h := &GlobalShortcutHandler{heldShortcuts: make(map[globalShortcutHoldKey]struct{})} |
| 227 | info := globalShortcutEventInfo{hasCurrentEvent: true, eventType: gdk.KeyPressValue, eventKeyval: uint('o'), eventKeycode: 32} | 227 | info := globalShortcutEventInfo{hasCurrentEvent: true, eventType: gdk.KeyPressValue, eventKeyval: uint('o'), eventKeycode: 32} | ||
| 228 | action := NewFloatingProfileAction("work", "https://example.com") | 228 | action := NewFloatingProfileAction("work", "https://example.com") | ||
| 229 | 229 | ||||
| 230 | - | if h.suppressHeldShortcut(action, info, time.Unix(100, 0)) { | 230 | + | if h.suppressHeldShortcut(action, info) { |
| 231 | t.Fatal("first floating-profile shortcut dispatch was suppressed") | 231 | t.Fatal("first floating-profile shortcut dispatch was suppressed") | ||
| 232 | } | 232 | } | ||
| 233 | - | if !h.suppressHeldShortcut(action, info, time.Unix(101, 0)) { | 233 | + | if !h.suppressHeldShortcut(action, info) { |
| 234 | t.Fatal("held floating-profile shortcut repeat was not suppressed") | 234 | t.Fatal("held floating-profile shortcut repeat was not suppressed") | ||
| 235 | } | 235 | } | ||
| 236 | } | 236 | } | ||