detached 476d2f2

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 VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · 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(
5353
5454 // OnKeyPressed handles a key press event.
5555 // 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.
5757 // 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.
5961 func (uc *InsertAccentUseCase) OnKeyPressed(ctx context.Context, char rune, shiftHeld bool) bool {
6062 log := logging.FromContext(ctx)
6163
@@ -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
234234 }
235235
236236 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.
244241 if h.tryAccentDetection(accentHandler, keyval, modifiers) {
245242 return true
246243 }
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")
248246 return false
249247
250248 case RouteHandleShortcuts:
@@ -268,7 +266,8 @@ func (h *KeyboardHandler) handleKeyPress(keyval, keycode uint, state gdk.Modifie
268266 }
269267
270268 // 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).
272271 // Returns true if the key should be suppressed.
273272 func (h *KeyboardHandler) tryAccentDetection(accentHandler AccentHandler, keyval uint, modifiers Modifier) bool {
274273 if accentHandler == nil {
@@ -477,14 +476,15 @@ const (
477476 RouteHandleShortcuts KeyRoute = iota
478477
479478 // 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.
483483 RoutePassToWidget
484484
485485 // 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.
488488 RouteAccentDetection
489489 )
490490
@@ -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 false240+ // 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 true242 return true
246 }243 }
247- // If accent handler didn't consume it, let the widget handle it244+ // 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 false246 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 = iota476 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 the479+ // Used when overlays (session manager, tab picker) are visible, or when a
481- // omnibox is focused, or when a WebView should receive text input for480+ // 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 RoutePassToWidget483 RoutePassToWidget
484 484
485 // RouteAccentDetection means the key should go through long-press accent485 // RouteAccentDetection means the key should go through long-press accent
486- // detection. Used for GTK Entry widgets (omnibox, find bar) where WebKit's486+ // 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 RouteAccentDetection488 RouteAccentDetection
489 )489 )
490 490

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close