refactor(input): add KeyRoute enum and replace shouldBypass with routeKey
Introduce KeyRoute enum (RouteHandleShortcuts, RoutePassToWidget, RouteAccentDetection), KeyContext struct, and exported helpers IsShortcutModified() and IsTextInputKey() for unified keyboard routing. Replace the boolean shouldBypass callback with a routeKey callback that returns a KeyRoute enum, consolidating routing policy into a single decision point.
- commit
- 801a8a3e7122d7acfbac070dd3b96c0c1fe1ed65
- author
- bnema · · 2026-03-10 21:59 UTC
- committer
- brice · · 2026-03-11 04:05 UTC
- parent
- 6660b83
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +162 -10
M internal/ui/input/keyboard.go +87 -10
| @@ -40,8 +40,10 @@ type KeyboardHandler struct { | |||
| 40 | 40 | ||
| 41 | 41 | // Action handler callback | |
| 42 | 42 | onAction ActionHandler | |
| 43 | - | // Optional bypass check (e.g., omnibox visible) | |
| 44 | - | shouldBypass func(modifiers Modifier) bool | |
| 43 | + | // Optional routing callback that determines how a key should be handled. | |
| 44 | + | // Returns RouteHandleShortcuts (default), RoutePassToWidget (let focused | |
| 45 | + | // widget handle it), or RouteAccentDetection (long-press accent for GTK entries). | |
| 46 | + | routeKey func(KeyContext) KeyRoute | |
| 45 | 47 | // Optional accent handler for dead keys support | |
| 46 | 48 | accentHandler AccentHandler | |
| 47 | 49 | // Optional escape hook for app-level overlays | |
| @@ -99,12 +101,16 @@ func (h *KeyboardHandler) SetOnModeChange(fn func(from, to Mode)) { | |||
| 99 | 101 | h.modal.SetOnModeChange(fn) | |
| 100 | 102 | } | |
| 101 | 103 | ||
| 102 | - | // SetShouldBypassInput sets a hook to bypass keyboard handling entirely. | |
| 103 | - | // When true, events propagate to focused widgets instead. | |
| 104 | - | func (h *KeyboardHandler) SetShouldBypassInput(fn func(modifiers Modifier) bool) { | |
| 104 | + | // SetRouteKey sets the callback that determines how each key event should be routed. | |
| 105 | + | // The callback receives key context and returns a KeyRoute indicating whether the | |
| 106 | + | // key should be handled by the shortcut system, passed to the focused widget | |
| 107 | + | // (for IM/compose support), or routed through accent detection. | |
| 108 | + | // | |
| 109 | + | // If not set, all keys are routed through the shortcut system (RouteHandleShortcuts). | |
| 110 | + | func (h *KeyboardHandler) SetRouteKey(fn func(KeyContext) KeyRoute) { | |
| 105 | 111 | h.mu.Lock() | |
| 106 | 112 | defer h.mu.Unlock() | |
| 107 | - | h.shouldBypass = fn | |
| 113 | + | h.routeKey = fn | |
| 108 | 114 | } | |
| 109 | 115 | ||
| 110 | 116 | // SetAccentHandler sets the handler for long-press accent detection. | |
| @@ -184,7 +190,7 @@ func (h *KeyboardHandler) handleKeyPress(keyval, keycode uint, state gdk.Modifie | |||
| 184 | 190 | log := logging.FromContext(h.ctx) | |
| 185 | 191 | ||
| 186 | 192 | h.mu.RLock() | |
| 187 | - | shouldBypass := h.shouldBypass | |
| 193 | + | routeKey := h.routeKey | |
| 188 | 194 | accentHandler := h.accentHandler | |
| 189 | 195 | onEscape := h.onEscape | |
| 190 | 196 | h.mu.RUnlock() | |
| @@ -207,9 +213,18 @@ func (h *KeyboardHandler) handleKeyPress(keyval, keycode uint, state gdk.Modifie | |||
| 207 | 213 | return true | |
| 208 | 214 | } | |
| 209 | 215 | ||
| 210 | - | if shouldBypass != nil && shouldBypass(modifiers) { | |
| 211 | - | log.Debug().Uint("keyval", keyval).Uint("keycode", keycode).Uint("state", uint(state)).Msg("keyboard handler bypassed") | |
| 212 | - | return false | |
| 216 | + | // Temporary: routeKey replaces shouldBypass - full routing logic comes in Task 3. | |
| 217 | + | // For now, we just check if route says to pass to widget. | |
| 218 | + | if routeKey != nil { | |
| 219 | + | route := routeKey(KeyContext{ | |
| 220 | + | Keyval: keyval, | |
| 221 | + | Keycode: keycode, | |
| 222 | + | Modifiers: modifiers, | |
| 223 | + | }) | |
| 224 | + | if route == RoutePassToWidget { | |
| 225 | + | log.Debug().Uint("keyval", keyval).Uint("keycode", keycode).Msg("routing key to focused widget") | |
| 226 | + | return false | |
| 227 | + | } | |
| 213 | 228 | } | |
| 214 | 229 | ||
| 215 | 230 | // Normalize uppercase letters for consistent binding lookup | |
| @@ -425,3 +440,65 @@ func keyvalToRune(keyval uint) rune { | |||
| 425 | 440 | } | |
| 426 | 441 | return 0 | |
| 427 | 442 | } | |
| 443 | + | ||
| 444 | + | // KeyRoute determines how a key event should be routed. | |
| 445 | + | type KeyRoute int | |
| 446 | + | ||
| 447 | + | const ( | |
| 448 | + | // RouteHandleShortcuts means the key should be processed by the shortcut system. | |
| 449 | + | // This is the default route -- app-level shortcuts, modal mode keys, etc. | |
| 450 | + | RouteHandleShortcuts KeyRoute = iota | |
| 451 | + | ||
| 452 | + | // RoutePassToWidget means the key should propagate to the focused widget. | |
| 453 | + | // Used when overlays (session manager, tab picker) are visible, when the | |
| 454 | + | // omnibox is focused, or when a WebView should receive text input for | |
| 455 | + | // native IM/compose/dead-key processing. | |
| 456 | + | RoutePassToWidget | |
| 457 | + | ||
| 458 | + | // RouteAccentDetection means the key should go through long-press accent | |
| 459 | + | // detection. Used for GTK Entry widgets (omnibox, find bar) where WebKit's | |
| 460 | + | // IM pipeline is not available but we still want accent support. | |
| 461 | + | RouteAccentDetection | |
| 462 | + | ) | |
| 463 | + | ||
| 464 | + | // KeyContext provides key event information for routing decisions. | |
| 465 | + | type KeyContext struct { | |
| 466 | + | Keyval uint // Translated key value | |
| 467 | + | Keycode uint // Hardware keycode | |
| 468 | + | Modifiers Modifier // Active modifiers (masked) | |
| 469 | + | } | |
| 470 | + | ||
| 471 | + | // IsShortcutModified returns true if the modifiers indicate a shortcut combination | |
| 472 | + | // (Ctrl or Alt pressed), as opposed to plain text input (no modifier or Shift-only). | |
| 473 | + | // Dead keys and compose sequences only use no-modifier or Shift, so this correctly | |
| 474 | + | // identifies keys that should be intercepted vs passed through to the IM pipeline. | |
| 475 | + | func IsShortcutModified(modifiers Modifier) bool { | |
| 476 | + | return modifiers&(ModCtrl|ModAlt) != 0 | |
| 477 | + | } | |
| 478 | + | ||
| 479 | + | // IsTextInputKey returns true if the keyval represents a text input key | |
| 480 | + | // (printable character or dead key) as opposed to a function/navigation key. | |
| 481 | + | // This is used to determine whether a key should be passed through to the | |
| 482 | + | // WebKit IM pipeline for compose/dead-key processing. | |
| 483 | + | func IsTextInputKey(keyval uint) bool { | |
| 484 | + | // Dead keys used by compose sequences (US International, etc.) | |
| 485 | + | // GDK dead key range: KEY_dead_grave (0xfe50) to KEY_dead_greek (0xfe8b) | |
| 486 | + | if keyval >= 0xfe50 && keyval <= 0xfe8b { | |
| 487 | + | return true | |
| 488 | + | } | |
| 489 | + | ||
| 490 | + | // Printable ASCII range (space through tilde) | |
| 491 | + | if keyval >= 0x020 && keyval <= 0x07e { | |
| 492 | + | return true | |
| 493 | + | } | |
| 494 | + | // Latin-1 supplement (non-breaking space through ÿ) | |
| 495 | + | if keyval >= 0x0a0 && keyval <= 0x0ff { | |
| 496 | + | return true | |
| 497 | + | } | |
| 498 | + | // Extended Latin and other Unicode characters mapped in GDK keyval space | |
| 499 | + | if keyval >= 0x100 && keyval <= 0x20ff { | |
| 500 | + | return true | |
| 501 | + | } | |
| 502 | + | ||
| 503 | + | return false | |
| 504 | + | } | |
| @@ -40,8 +40,10 @@ type KeyboardHandler struct { | |||||
| 40 | 40 | ||||
| 41 | // Action handler callback | 41 | // Action handler callback | ||
| 42 | onAction ActionHandler | 42 | onAction ActionHandler | ||
| 43 | - | // Optional bypass check (e.g., omnibox visible) | 43 | + | // Optional routing callback that determines how a key should be handled. |
| 44 | - | shouldBypass func(modifiers Modifier) bool | 44 | + | // Returns RouteHandleShortcuts (default), RoutePassToWidget (let focused |
| 45 | + | // widget handle it), or RouteAccentDetection (long-press accent for GTK entries). | |||
| 46 | + | routeKey func(KeyContext) KeyRoute | |||
| 45 | // Optional accent handler for dead keys support | 47 | // Optional accent handler for dead keys support | ||
| 46 | accentHandler AccentHandler | 48 | accentHandler AccentHandler | ||
| 47 | // Optional escape hook for app-level overlays | 49 | // Optional escape hook for app-level overlays | ||
| @@ -99,12 +101,16 @@ func (h *KeyboardHandler) SetOnModeChange(fn func(from, to Mode)) { | |||||
| 99 | h.modal.SetOnModeChange(fn) | 101 | h.modal.SetOnModeChange(fn) | ||
| 100 | } | 102 | } | ||
| 101 | 103 | ||||
| 102 | - | // SetShouldBypassInput sets a hook to bypass keyboard handling entirely. | 104 | + | // SetRouteKey sets the callback that determines how each key event should be routed. |
| 103 | - | // When true, events propagate to focused widgets instead. | 105 | + | // The callback receives key context and returns a KeyRoute indicating whether the |
| 104 | - | func (h *KeyboardHandler) SetShouldBypassInput(fn func(modifiers Modifier) bool) { | 106 | + | // key should be handled by the shortcut system, passed to the focused widget |
| 107 | + | // (for IM/compose support), or routed through accent detection. | |||
| 108 | + | // | |||
| 109 | + | // If not set, all keys are routed through the shortcut system (RouteHandleShortcuts). | |||
| 110 | + | func (h *KeyboardHandler) SetRouteKey(fn func(KeyContext) KeyRoute) { | |||
| 105 | h.mu.Lock() | 111 | h.mu.Lock() | ||
| 106 | defer h.mu.Unlock() | 112 | defer h.mu.Unlock() | ||
| 107 | - | h.shouldBypass = fn | 113 | + | h.routeKey = fn |
| 108 | } | 114 | } | ||
| 109 | 115 | ||||
| 110 | // SetAccentHandler sets the handler for long-press accent detection. | 116 | // SetAccentHandler sets the handler for long-press accent detection. | ||
| @@ -184,7 +190,7 @@ func (h *KeyboardHandler) handleKeyPress(keyval, keycode uint, state gdk.Modifie | |||||
| 184 | log := logging.FromContext(h.ctx) | 190 | log := logging.FromContext(h.ctx) | ||
| 185 | 191 | ||||
| 186 | h.mu.RLock() | 192 | h.mu.RLock() | ||
| 187 | - | shouldBypass := h.shouldBypass | 193 | + | routeKey := h.routeKey |
| 188 | accentHandler := h.accentHandler | 194 | accentHandler := h.accentHandler | ||
| 189 | onEscape := h.onEscape | 195 | onEscape := h.onEscape | ||
| 190 | h.mu.RUnlock() | 196 | h.mu.RUnlock() | ||
| @@ -207,9 +213,18 @@ func (h *KeyboardHandler) handleKeyPress(keyval, keycode uint, state gdk.Modifie | |||||
| 207 | return true | 213 | return true | ||
| 208 | } | 214 | } | ||
| 209 | 215 | ||||
| 210 | - | if shouldBypass != nil && shouldBypass(modifiers) { | 216 | + | // Temporary: routeKey replaces shouldBypass - full routing logic comes in Task 3. |
| 211 | - | log.Debug().Uint("keyval", keyval).Uint("keycode", keycode).Uint("state", uint(state)).Msg("keyboard handler bypassed") | 217 | + | // For now, we just check if route says to pass to widget. |
| 212 | - | return false | 218 | + | if routeKey != nil { |
| 219 | + | route := routeKey(KeyContext{ | |||
| 220 | + | Keyval: keyval, | |||
| 221 | + | Keycode: keycode, | |||
| 222 | + | Modifiers: modifiers, | |||
| 223 | + | }) | |||
| 224 | + | if route == RoutePassToWidget { | |||
| 225 | + | log.Debug().Uint("keyval", keyval).Uint("keycode", keycode).Msg("routing key to focused widget") | |||
| 226 | + | return false | |||
| 227 | + | } | |||
| 213 | } | 228 | } | ||
| 214 | 229 | ||||
| 215 | // Normalize uppercase letters for consistent binding lookup | 230 | // Normalize uppercase letters for consistent binding lookup | ||
| @@ -425,3 +440,65 @@ func keyvalToRune(keyval uint) rune { | |||||
| 425 | } | 440 | } | ||
| 426 | return 0 | 441 | return 0 | ||
| 427 | } | 442 | } | ||
| 443 | + | ||||
| 444 | + | // KeyRoute determines how a key event should be routed. | |||
| 445 | + | type KeyRoute int | |||
| 446 | + | ||||
| 447 | + | const ( | |||
| 448 | + | // RouteHandleShortcuts means the key should be processed by the shortcut system. | |||
| 449 | + | // This is the default route -- app-level shortcuts, modal mode keys, etc. | |||
| 450 | + | RouteHandleShortcuts KeyRoute = iota | |||
| 451 | + | ||||
| 452 | + | // RoutePassToWidget means the key should propagate to the focused widget. | |||
| 453 | + | // Used when overlays (session manager, tab picker) are visible, when the | |||
| 454 | + | // omnibox is focused, or when a WebView should receive text input for | |||
| 455 | + | // native IM/compose/dead-key processing. | |||
| 456 | + | RoutePassToWidget | |||
| 457 | + | ||||
| 458 | + | // RouteAccentDetection means the key should go through long-press accent | |||
| 459 | + | // detection. Used for GTK Entry widgets (omnibox, find bar) where WebKit's | |||
| 460 | + | // IM pipeline is not available but we still want accent support. | |||
| 461 | + | RouteAccentDetection | |||
| 462 | + | ) | |||
| 463 | + | ||||
| 464 | + | // KeyContext provides key event information for routing decisions. | |||
| 465 | + | type KeyContext struct { | |||
| 466 | + | Keyval uint // Translated key value | |||
| 467 | + | Keycode uint // Hardware keycode | |||
| 468 | + | Modifiers Modifier // Active modifiers (masked) | |||
| 469 | + | } | |||
| 470 | + | ||||
| 471 | + | // IsShortcutModified returns true if the modifiers indicate a shortcut combination | |||
| 472 | + | // (Ctrl or Alt pressed), as opposed to plain text input (no modifier or Shift-only). | |||
| 473 | + | // Dead keys and compose sequences only use no-modifier or Shift, so this correctly | |||
| 474 | + | // identifies keys that should be intercepted vs passed through to the IM pipeline. | |||
| 475 | + | func IsShortcutModified(modifiers Modifier) bool { | |||
| 476 | + | return modifiers&(ModCtrl|ModAlt) != 0 | |||
| 477 | + | } | |||
| 478 | + | ||||
| 479 | + | // IsTextInputKey returns true if the keyval represents a text input key | |||
| 480 | + | // (printable character or dead key) as opposed to a function/navigation key. | |||
| 481 | + | // This is used to determine whether a key should be passed through to the | |||
| 482 | + | // WebKit IM pipeline for compose/dead-key processing. | |||
| 483 | + | func IsTextInputKey(keyval uint) bool { | |||
| 484 | + | // Dead keys used by compose sequences (US International, etc.) | |||
| 485 | + | // GDK dead key range: KEY_dead_grave (0xfe50) to KEY_dead_greek (0xfe8b) | |||
| 486 | + | if keyval >= 0xfe50 && keyval <= 0xfe8b { | |||
| 487 | + | return true | |||
| 488 | + | } | |||
| 489 | + | ||||
| 490 | + | // Printable ASCII range (space through tilde) | |||
| 491 | + | if keyval >= 0x020 && keyval <= 0x07e { | |||
| 492 | + | return true | |||
| 493 | + | } | |||
| 494 | + | // Latin-1 supplement (non-breaking space through ÿ) | |||
| 495 | + | if keyval >= 0x0a0 && keyval <= 0x0ff { | |||
| 496 | + | return true | |||
| 497 | + | } | |||
| 498 | + | // Extended Latin and other Unicode characters mapped in GDK keyval space | |||
| 499 | + | if keyval >= 0x100 && keyval <= 0x20ff { | |||
| 500 | + | return true | |||
| 501 | + | } | |||
| 502 | + | ||||
| 503 | + | return false | |||
| 504 | + | } | |||
A internal/ui/input/keyboard_test.go +75 -0
| @@ -0,0 +1,75 @@ | |||
| 1 | + | package input | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "testing" | |
| 5 | + | ||
| 6 | + | "github.com/stretchr/testify/assert" | |
| 7 | + | ) | |
| 8 | + | ||
| 9 | + | func TestIsShortcutModified(t *testing.T) { | |
| 10 | + | tests := []struct { | |
| 11 | + | name string | |
| 12 | + | mods Modifier | |
| 13 | + | expected bool | |
| 14 | + | }{ | |
| 15 | + | {"no modifier", ModNone, false}, | |
| 16 | + | {"shift only", ModShift, false}, | |
| 17 | + | {"ctrl", ModCtrl, true}, | |
| 18 | + | {"alt", ModAlt, true}, | |
| 19 | + | {"ctrl+shift", ModCtrl | ModShift, true}, | |
| 20 | + | {"alt+shift", ModAlt | ModShift, true}, | |
| 21 | + | {"ctrl+alt", ModCtrl | ModAlt, true}, | |
| 22 | + | } | |
| 23 | + | ||
| 24 | + | for _, tt := range tests { | |
| 25 | + | t.Run(tt.name, func(t *testing.T) { | |
| 26 | + | assert.Equal(t, tt.expected, IsShortcutModified(tt.mods)) | |
| 27 | + | }) | |
| 28 | + | } | |
| 29 | + | } | |
| 30 | + | ||
| 31 | + | func TestIsTextInputKey(t *testing.T) { | |
| 32 | + | tests := []struct { | |
| 33 | + | name string | |
| 34 | + | keyval uint | |
| 35 | + | expected bool | |
| 36 | + | }{ | |
| 37 | + | // Printable characters | |
| 38 | + | {"lowercase a", uint('a'), true}, | |
| 39 | + | {"lowercase z", uint('z'), true}, | |
| 40 | + | {"uppercase A", uint('A'), true}, | |
| 41 | + | {"digit 1", uint('1'), true}, | |
| 42 | + | {"space", 0x020, true}, | |
| 43 | + | {"period", uint('.'), true}, | |
| 44 | + | {"slash", uint('/'), true}, | |
| 45 | + | {"apostrophe", 0x027, true}, | |
| 46 | + | {"grave accent", 0x060, true}, | |
| 47 | + | {"tilde", 0x07e, true}, | |
| 48 | + | ||
| 49 | + | // Dead keys (used by US International layout) | |
| 50 | + | {"dead acute", 0xfe51, true}, | |
| 51 | + | {"dead grave", 0xfe50, true}, | |
| 52 | + | {"dead circumflex", 0xfe52, true}, | |
| 53 | + | {"dead tilde", 0xfe53, true}, | |
| 54 | + | {"dead diaeresis", 0xfe57, true}, | |
| 55 | + | ||
| 56 | + | // Non-text keys (should NOT be treated as text) | |
| 57 | + | {"escape", 0xff1b, false}, | |
| 58 | + | {"return", 0xff0d, false}, | |
| 59 | + | {"tab", 0xff09, false}, | |
| 60 | + | {"F1", 0xffbe, false}, | |
| 61 | + | {"F12", 0xffc9, false}, | |
| 62 | + | {"left arrow", 0xff51, false}, | |
| 63 | + | {"right arrow", 0xff53, false}, | |
| 64 | + | {"backspace", 0xff08, false}, | |
| 65 | + | {"delete", 0xffff, false}, | |
| 66 | + | {"home", 0xff50, false}, | |
| 67 | + | {"end", 0xff57, false}, | |
| 68 | + | } | |
| 69 | + | ||
| 70 | + | for _, tt := range tests { | |
| 71 | + | t.Run(tt.name, func(t *testing.T) { | |
| 72 | + | assert.Equal(t, tt.expected, IsTextInputKey(tt.keyval), "keyval 0x%x", tt.keyval) | |
| 73 | + | }) | |
| 74 | + | } | |
| 75 | + | } | |
| @@ -0,0 +1,75 @@ | |||||
| 1 | + | package input | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "testing" | |||
| 5 | + | ||||
| 6 | + | "github.com/stretchr/testify/assert" | |||
| 7 | + | ) | |||
| 8 | + | ||||
| 9 | + | func TestIsShortcutModified(t *testing.T) { | |||
| 10 | + | tests := []struct { | |||
| 11 | + | name string | |||
| 12 | + | mods Modifier | |||
| 13 | + | expected bool | |||
| 14 | + | }{ | |||
| 15 | + | {"no modifier", ModNone, false}, | |||
| 16 | + | {"shift only", ModShift, false}, | |||
| 17 | + | {"ctrl", ModCtrl, true}, | |||
| 18 | + | {"alt", ModAlt, true}, | |||
| 19 | + | {"ctrl+shift", ModCtrl | ModShift, true}, | |||
| 20 | + | {"alt+shift", ModAlt | ModShift, true}, | |||
| 21 | + | {"ctrl+alt", ModCtrl | ModAlt, true}, | |||
| 22 | + | } | |||
| 23 | + | ||||
| 24 | + | for _, tt := range tests { | |||
| 25 | + | t.Run(tt.name, func(t *testing.T) { | |||
| 26 | + | assert.Equal(t, tt.expected, IsShortcutModified(tt.mods)) | |||
| 27 | + | }) | |||
| 28 | + | } | |||
| 29 | + | } | |||
| 30 | + | ||||
| 31 | + | func TestIsTextInputKey(t *testing.T) { | |||
| 32 | + | tests := []struct { | |||
| 33 | + | name string | |||
| 34 | + | keyval uint | |||
| 35 | + | expected bool | |||
| 36 | + | }{ | |||
| 37 | + | // Printable characters | |||
| 38 | + | {"lowercase a", uint('a'), true}, | |||
| 39 | + | {"lowercase z", uint('z'), true}, | |||
| 40 | + | {"uppercase A", uint('A'), true}, | |||
| 41 | + | {"digit 1", uint('1'), true}, | |||
| 42 | + | {"space", 0x020, true}, | |||
| 43 | + | {"period", uint('.'), true}, | |||
| 44 | + | {"slash", uint('/'), true}, | |||
| 45 | + | {"apostrophe", 0x027, true}, | |||
| 46 | + | {"grave accent", 0x060, true}, | |||
| 47 | + | {"tilde", 0x07e, true}, | |||
| 48 | + | ||||
| 49 | + | // Dead keys (used by US International layout) | |||
| 50 | + | {"dead acute", 0xfe51, true}, | |||
| 51 | + | {"dead grave", 0xfe50, true}, | |||
| 52 | + | {"dead circumflex", 0xfe52, true}, | |||
| 53 | + | {"dead tilde", 0xfe53, true}, | |||
| 54 | + | {"dead diaeresis", 0xfe57, true}, | |||
| 55 | + | ||||
| 56 | + | // Non-text keys (should NOT be treated as text) | |||
| 57 | + | {"escape", 0xff1b, false}, | |||
| 58 | + | {"return", 0xff0d, false}, | |||
| 59 | + | {"tab", 0xff09, false}, | |||
| 60 | + | {"F1", 0xffbe, false}, | |||
| 61 | + | {"F12", 0xffc9, false}, | |||
| 62 | + | {"left arrow", 0xff51, false}, | |||
| 63 | + | {"right arrow", 0xff53, false}, | |||
| 64 | + | {"backspace", 0xff08, false}, | |||
| 65 | + | {"delete", 0xffff, false}, | |||
| 66 | + | {"home", 0xff50, false}, | |||
| 67 | + | {"end", 0xff57, false}, | |||
| 68 | + | } | |||
| 69 | + | ||||
| 70 | + | for _, tt := range tests { | |||
| 71 | + | t.Run(tt.name, func(t *testing.T) { | |||
| 72 | + | assert.Equal(t, tt.expected, IsTextInputKey(tt.keyval), "keyval 0x%x", tt.keyval) | |||
| 73 | + | }) | |||
| 74 | + | } | |||
| 75 | + | } | |||