fix(input): keep accent picker universal for accessibility
The long-press accent picker must remain available for WebView inputs, not just GTK Entry widgets. Users with motor disabilities may not be able to execute rapid compose sequences (dead key + base character). The visual picker is also more discoverable for users without a dead-key keyboard layout configured. Both RoutePassToWidget and RouteAccentDetection now run accent detection before passing the key to the focused widget.
- commit
- 476d2f270f87ade03fc1babf2c763e60bf95b6b9
- author
- bnema · · 2026-03-10 22:24 UTC
- committer
- brice · · 2026-03-11 04:05 UTC
- parent
- 51b8f8a
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +18 -16
M internal/application/usecase/insert_accent.go +4 -2
| @@ -53,9 +53,11 @@ func NewInsertAccentUseCase( | |||
| 53 | 53 | ||
| 54 | 54 | // OnKeyPressed handles a key press event. | |
| 55 | 55 | // Returns true if the use case is handling this key and the caller should suppress it. | |
| 56 | - | // Note: For GTK Entry widgets, key suppression may not be fully effective. | |
| 56 | + | // Note: Key suppression may not be fully effective for all widget types. | |
| 57 | 57 | // When the picker shows, we delete any repeated characters that slipped through. | |
| 58 | - | // WebView text inputs use native IM compose instead of this accent picker. | |
| 58 | + | // The accent picker is available for both GTK Entry widgets and WebView inputs | |
| 59 | + | // as an accessibility feature: users with motor disabilities or without a | |
| 60 | + | // dead-key keyboard layout can long-press to access accented characters. | |
| 59 | 61 | func (uc *InsertAccentUseCase) OnKeyPressed(ctx context.Context, char rune, shiftHeld bool) bool { | |
| 60 | 62 | log := logging.FromContext(ctx) | |
| 61 | 63 | ||
| @@ -53,9 +53,11 @@ func NewInsertAccentUseCase( | |||||
| 53 | 53 | ||||
| 54 | // OnKeyPressed handles a key press event. | 54 | // OnKeyPressed handles a key press event. | ||
| 55 | // Returns true if the use case is handling this key and the caller should suppress it. | 55 | // Returns true if the use case is handling this key and the caller should suppress it. | ||
| 56 | - | // Note: For GTK Entry widgets, key suppression may not be fully effective. | 56 | + | // Note: Key suppression may not be fully effective for all widget types. |
| 57 | // When the picker shows, we delete any repeated characters that slipped through. | 57 | // When the picker shows, we delete any repeated characters that slipped through. | ||
| 58 | - | // WebView text inputs use native IM compose instead of this accent picker. | 58 | + | // The accent picker is available for both GTK Entry widgets and WebView inputs |
| 59 | + | // as an accessibility feature: users with motor disabilities or without a | |||
| 60 | + | // dead-key keyboard layout can long-press to access accented characters. | |||
| 59 | func (uc *InsertAccentUseCase) OnKeyPressed(ctx context.Context, char rune, shiftHeld bool) bool { | 61 | func (uc *InsertAccentUseCase) OnKeyPressed(ctx context.Context, char rune, shiftHeld bool) bool { | ||
| 60 | log := logging.FromContext(ctx) | 62 | log := logging.FromContext(ctx) | ||
| 61 | 63 | ||||
M internal/ui/input/keyboard.go +14 -14
| @@ -234,17 +234,15 @@ func (h *KeyboardHandler) handleKeyPress(keyval, keycode uint, state gdk.Modifie | |||
| 234 | 234 | } | |
| 235 | 235 | ||
| 236 | 236 | switch route { | |
| 237 | - | case RoutePassToWidget: | |
| 238 | - | // Let the focused widget handle this key (WebView IM, overlay, etc.) | |
| 239 | - | log.Trace().Uint("keyval", keyval).Msg("routing key to focused widget") | |
| 240 | - | return false | |
| 241 | - | ||
| 242 | - | case RouteAccentDetection: | |
| 243 | - | // Try long-press accent detection (for GTK Entry widgets) | |
| 237 | + | case RoutePassToWidget, RouteAccentDetection: | |
| 238 | + | // Try long-press accent detection first. This enables the accent picker | |
| 239 | + | // universally -- for both GTK Entry widgets and WebView text inputs. | |
| 240 | + | // If the accent handler doesn't consume the key, pass it to the widget. | |
| 244 | 241 | if h.tryAccentDetection(accentHandler, keyval, modifiers) { | |
| 245 | 242 | return true | |
| 246 | 243 | } | |
| 247 | - | // If accent handler didn't consume it, let the widget handle it | |
| 244 | + | // Let the focused widget handle this key (WebView IM, GTK Entry, etc.) | |
| 245 | + | log.Trace().Uint("keyval", keyval).Msg("routing key to focused widget") | |
| 248 | 246 | return false | |
| 249 | 247 | ||
| 250 | 248 | case RouteHandleShortcuts: | |
| @@ -268,7 +266,8 @@ func (h *KeyboardHandler) handleKeyPress(keyval, keycode uint, state gdk.Modifie | |||
| 268 | 266 | } | |
| 269 | 267 | ||
| 270 | 268 | // tryAccentDetection starts long-press detection for accent-eligible keys. | |
| 271 | - | // Only called when routeKey returns RouteAccentDetection (GTK Entry context). | |
| 269 | + | // Called for both RoutePassToWidget and RouteAccentDetection routes, so the | |
| 270 | + | // long-press accent picker is available universally (WebView and GTK Entry). | |
| 272 | 271 | // Returns true if the key should be suppressed. | |
| 273 | 272 | func (h *KeyboardHandler) tryAccentDetection(accentHandler AccentHandler, keyval uint, modifiers Modifier) bool { | |
| 274 | 273 | if accentHandler == nil { | |
| @@ -477,14 +476,15 @@ const ( | |||
| 477 | 476 | RouteHandleShortcuts KeyRoute = iota | |
| 478 | 477 | ||
| 479 | 478 | // RoutePassToWidget means the key should propagate to the focused widget. | |
| 480 | - | // Used when overlays (session manager, tab picker) are visible, when the | |
| 481 | - | // omnibox is focused, or when a WebView should receive text input for | |
| 482 | - | // native IM/compose/dead-key processing. | |
| 479 | + | // Used when overlays (session manager, tab picker) are visible, or when a | |
| 480 | + | // WebView should receive text input for native IM/compose/dead-key processing. | |
| 481 | + | // Long-press accent detection still runs first for accessibility: users with | |
| 482 | + | // motor disabilities may not be able to execute rapid compose sequences. | |
| 483 | 483 | RoutePassToWidget | |
| 484 | 484 | ||
| 485 | 485 | // RouteAccentDetection means the key should go through long-press accent | |
| 486 | - | // detection. Used for GTK Entry widgets (omnibox, find bar) where WebKit's | |
| 487 | - | // IM pipeline is not available but we still want accent support. | |
| 486 | + | // detection, then pass to the focused widget. Used for GTK Entry widgets | |
| 487 | + | // (omnibox, find bar) and as a universal accessibility fallback. | |
| 488 | 488 | RouteAccentDetection | |
| 489 | 489 | ) | |
| 490 | 490 | ||
| @@ -234,17 +234,15 @@ func (h *KeyboardHandler) handleKeyPress(keyval, keycode uint, state gdk.Modifie | |||||
| 234 | } | 234 | } | ||
| 235 | 235 | ||||
| 236 | switch route { | 236 | switch route { | ||
| 237 | - | case RoutePassToWidget: | 237 | + | case RoutePassToWidget, RouteAccentDetection: |
| 238 | - | // Let the focused widget handle this key (WebView IM, overlay, etc.) | 238 | + | // Try long-press accent detection first. This enables the accent picker |
| 239 | - | log.Trace().Uint("keyval", keyval).Msg("routing key to focused widget") | 239 | + | // universally -- for both GTK Entry widgets and WebView text inputs. |
| 240 | - | return false | 240 | + | // If the accent handler doesn't consume the key, pass it to the widget. |
| 241 | - | ||||
| 242 | - | case RouteAccentDetection: | |||
| 243 | - | // Try long-press accent detection (for GTK Entry widgets) | |||
| 244 | if h.tryAccentDetection(accentHandler, keyval, modifiers) { | 241 | if h.tryAccentDetection(accentHandler, keyval, modifiers) { | ||
| 245 | return true | 242 | return true | ||
| 246 | } | 243 | } | ||
| 247 | - | // If accent handler didn't consume it, let the widget handle it | 244 | + | // Let the focused widget handle this key (WebView IM, GTK Entry, etc.) |
| 245 | + | log.Trace().Uint("keyval", keyval).Msg("routing key to focused widget") | |||
| 248 | return false | 246 | return false | ||
| 249 | 247 | ||||
| 250 | case RouteHandleShortcuts: | 248 | case RouteHandleShortcuts: | ||
| @@ -268,7 +266,8 @@ func (h *KeyboardHandler) handleKeyPress(keyval, keycode uint, state gdk.Modifie | |||||
| 268 | } | 266 | } | ||
| 269 | 267 | ||||
| 270 | // tryAccentDetection starts long-press detection for accent-eligible keys. | 268 | // tryAccentDetection starts long-press detection for accent-eligible keys. | ||
| 271 | - | // Only called when routeKey returns RouteAccentDetection (GTK Entry context). | 269 | + | // Called for both RoutePassToWidget and RouteAccentDetection routes, so the |
| 270 | + | // long-press accent picker is available universally (WebView and GTK Entry). | |||
| 272 | // Returns true if the key should be suppressed. | 271 | // Returns true if the key should be suppressed. | ||
| 273 | func (h *KeyboardHandler) tryAccentDetection(accentHandler AccentHandler, keyval uint, modifiers Modifier) bool { | 272 | func (h *KeyboardHandler) tryAccentDetection(accentHandler AccentHandler, keyval uint, modifiers Modifier) bool { | ||
| 274 | if accentHandler == nil { | 273 | if accentHandler == nil { | ||
| @@ -477,14 +476,15 @@ const ( | |||||
| 477 | RouteHandleShortcuts KeyRoute = iota | 476 | RouteHandleShortcuts KeyRoute = iota | ||
| 478 | 477 | ||||
| 479 | // RoutePassToWidget means the key should propagate to the focused widget. | 478 | // RoutePassToWidget means the key should propagate to the focused widget. | ||
| 480 | - | // Used when overlays (session manager, tab picker) are visible, when the | 479 | + | // Used when overlays (session manager, tab picker) are visible, or when a |
| 481 | - | // omnibox is focused, or when a WebView should receive text input for | 480 | + | // WebView should receive text input for native IM/compose/dead-key processing. |
| 482 | - | // native IM/compose/dead-key processing. | 481 | + | // Long-press accent detection still runs first for accessibility: users with |
| 482 | + | // motor disabilities may not be able to execute rapid compose sequences. | |||
| 483 | RoutePassToWidget | 483 | RoutePassToWidget | ||
| 484 | 484 | ||||
| 485 | // RouteAccentDetection means the key should go through long-press accent | 485 | // RouteAccentDetection means the key should go through long-press accent | ||
| 486 | - | // detection. Used for GTK Entry widgets (omnibox, find bar) where WebKit's | 486 | + | // detection, then pass to the focused widget. Used for GTK Entry widgets |
| 487 | - | // IM pipeline is not available but we still want accent support. | 487 | + | // (omnibox, find bar) and as a universal accessibility fallback. |
| 488 | RouteAccentDetection | 488 | RouteAccentDetection | ||
| 489 | ) | 489 | ) | ||
| 490 | 490 | ||||