detached a5e0073

fix(cef): enable IMContext for dead key compose support

Re-enable the GtkIMContextSimple integration that was disabled during
debugging. The previous crash was caused by stale bindings, not the
IMContext itself. Dead key sequences (e.g. '+e → é) now work correctly
on US International and other compose-capable keyboard layouts.
commit
a5e0073b7a24e65eccab42d51c3059e91429c664
author
brice · · 2026-03-20 19:16 UTC
committer
brice · · 2026-03-28 04:37 UTC
parent
2f401f2
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
1 file · +13 -19
M internal/infrastructure/cef/input_bridge.go +13 -19
@@ -139,25 +139,19 @@ func (ib *inputBridge) attachFocusAndKeyboard(glArea *gtk.GLArea) {
139139 // Keyboard + IMContext for dead key / compose support.
140140 key := gtk.NewEventControllerKey()
141141
142- // Set up IMContext: when set on the controller, GTK calls
143- // FilterKeypress on every key event. If the IM absorbs the key
144- // (dead key, compose in-progress), key-pressed is NOT emitted;
145- // instead the IM emits "commit" with the composed string.
146- // TODO: re-enable once ConnectCommit callback crash is resolved.
147- // The IMContext integration causes a stack corruption crash at startup,
148- // likely due to a purego callback ABI mismatch in the generated signal
149- // bindings. The VK code fixes and gdk.KeyvalToUnicode() still work
150- // without IMContext — XKB handles dead key composition at the layout level.
151- //
152- // ib.imContext = gtk.NewIMContextSimple()
153- // if ib.imContext != nil {
154- // ib.commitCb = func(_ gtk.IMContext, text string) {
155- // ib.onIMCommit(text)
156- // }
157- // ib.imContext.ConnectCommit(&ib.commitCb)
158- // key.SetImContext(&ib.imContext.IMContext)
159- // ib.imContext.SetClientWidget(&glArea.Widget)
160- // }
142+ // Set up IMContext for dead key / compose support.
143+ // When set on the controller, GTK calls FilterKeypress on every key
144+ // event. If the IM absorbs the key (dead key, compose in-progress),
145+ // key-pressed is NOT emitted; the IM emits "commit" with the result.
146+ ib.imContext = gtk.NewIMContextSimple()
147+ if ib.imContext != nil {
148+ ib.commitCb = func(_ gtk.IMContext, text string) {
149+ ib.onIMCommit(text)
150+ }
151+ ib.imContext.ConnectCommit(&ib.commitCb)
152+ key.SetImContext(&ib.imContext.IMContext)
153+ ib.imContext.SetClientWidget(&glArea.Widget)
154+ }
161155
162156 keyPressCb := func(_ gtk.EventControllerKey, keyval, keycode uint, state gdk.ModifierType) bool {
163157 ib.onKeyPress(keyval, keycode, uint(state))
@@ -139,25 +139,19 @@ func (ib *inputBridge) attachFocusAndKeyboard(glArea *gtk.GLArea) {
139 // Keyboard + IMContext for dead key / compose support.139 // Keyboard + IMContext for dead key / compose support.
140 key := gtk.NewEventControllerKey()140 key := gtk.NewEventControllerKey()
141 141
142- // Set up IMContext: when set on the controller, GTK calls142+ // Set up IMContext for dead key / compose support.
143- // FilterKeypress on every key event. If the IM absorbs the key143+ // When set on the controller, GTK calls FilterKeypress on every key
144- // (dead key, compose in-progress), key-pressed is NOT emitted;144+ // event. If the IM absorbs the key (dead key, compose in-progress),
145- // instead the IM emits "commit" with the composed string.145+ // key-pressed is NOT emitted; the IM emits "commit" with the result.
146- // TODO: re-enable once ConnectCommit callback crash is resolved.146+ ib.imContext = gtk.NewIMContextSimple()
147- // The IMContext integration causes a stack corruption crash at startup,147+ if ib.imContext != nil {
148- // likely due to a purego callback ABI mismatch in the generated signal148+ ib.commitCb = func(_ gtk.IMContext, text string) {
149- // bindings. The VK code fixes and gdk.KeyvalToUnicode() still work149+ ib.onIMCommit(text)
150- // without IMContext — XKB handles dead key composition at the layout level.150+ }
151- //151+ ib.imContext.ConnectCommit(&ib.commitCb)
152- // ib.imContext = gtk.NewIMContextSimple()152+ key.SetImContext(&ib.imContext.IMContext)
153- // if ib.imContext != nil {153+ ib.imContext.SetClientWidget(&glArea.Widget)
154- // ib.commitCb = func(_ gtk.IMContext, text string) {154+ }
155- // ib.onIMCommit(text)
156- // }
157- // ib.imContext.ConnectCommit(&ib.commitCb)
158- // key.SetImContext(&ib.imContext.IMContext)
159- // ib.imContext.SetClientWidget(&glArea.Widget)
160- // }
161 155
162 keyPressCb := func(_ gtk.EventControllerKey, keyval, keycode uint, state gdk.ModifierType) bool {156 keyPressCb := func(_ gtk.EventControllerKey, keyval, keycode uint, state gdk.ModifierType) bool {
163 ib.onKeyPress(keyval, keycode, uint(state))157 ib.onKeyPress(keyval, keycode, uint(state))

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