detached 8166fb6

fix: reduce render pipeline log noise + add clipboard debug logging

- Change all per-frame render logs (OnPaint, handlePaint, uploadToPBO,
  onGLRender) from Debug to Trace level — they fire at 60-120fps
- Add comprehensive debug logging to clipboard paste flow:
  init, interception, ReadTextAsync, callback, ImeCommitText
- Add debug logging to clipboard copy flow (/api/clipboard-set)
- Pass context to inputBridge instead of explicit logger
commit
8166fb6cfde2e87c113706be9777dc8e275da8de
author
brice · · 2026-03-25 06:26 UTC
committer
brice · · 2026-03-28 04:39 UTC
parent
6c5dd44
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
5 files · +56 -13
M internal/infrastructure/cef/factory.go +1 -1
@@ -93,7 +93,7 @@ func (f *WebViewFactory) Create(ctx context.Context) (port.WebView, error) {
9393 wv.handlers = handlers
9494 wv.findCtrl = newFindController()
9595
96- input := newInputBridge(f.scale)
96+ input := newInputBridge(ctx, f.scale)
9797 input.attachTo(pipeline.glArea)
9898 wv.input = input
9999
@@ -93,7 +93,7 @@ func (f *WebViewFactory) Create(ctx context.Context) (port.WebView, error) {
93 wv.handlers = handlers93 wv.handlers = handlers
94 wv.findCtrl = newFindController()94 wv.findCtrl = newFindController()
95 95
96- input := newInputBridge(f.scale)96+ input := newInputBridge(ctx, f.scale)
97 input.attachTo(pipeline.glArea)97 input.attachTo(pipeline.glArea)
98 wv.input = input98 wv.input = input
99 99
M internal/infrastructure/cef/input_bridge.go +38 -3
@@ -1,12 +1,15 @@
11 package cef
22
33 import (
4+ "context"
45 "sync"
56
67 purecef "github.com/bnema/purego-cef/cef"
78 "github.com/bnema/puregotk/v4/gdk"
89 "github.com/bnema/puregotk/v4/gio"
910 "github.com/bnema/puregotk/v4/gtk"
11+
12+ "github.com/bnema/dumber/internal/logging"
1013 )
1114
1215 // GDK dead key range boundaries. Dead keys (0xfe50–0xfe8c) are compose
@@ -40,6 +43,7 @@ const (
4043 type inputBridge struct {
4144 host purecef.BrowserHost
4245 scale int32
46+ ctx context.Context
4347 mu sync.Mutex
4448
4549 // Last known pointer position, used for scroll events which don't
@@ -63,8 +67,8 @@ type inputBridge struct {
6367 }
6468
6569 // newInputBridge creates an input bridge with the given HiDPI scale factor.
66-func newInputBridge(scale int32) *inputBridge {
67- return &inputBridge{scale: scale}
70+func newInputBridge(ctx context.Context, scale int32) *inputBridge {
71+ return &inputBridge{ctx: ctx, scale: scale}
6872 }
6973
7074 // setHost is called once the browser is created to enable event dispatch.
@@ -80,6 +84,13 @@ func (ib *inputBridge) attachTo(glArea *gtk.GLArea) {
8084 // Wayland clipboard, so we bridge it via GDK → ImeCommitText.
8185 if display := gdk.DisplayGetDefault(); display != nil {
8286 ib.clipboard = display.GetClipboard()
87+ if ib.clipboard != nil {
88+ logging.FromContext(ib.ctx).Debug().Msg("cef: GDK clipboard acquired for paste bridge")
89+ } else {
90+ logging.FromContext(ib.ctx).Warn().Msg("cef: GDK display found but GetClipboard returned nil")
91+ }
92+ } else {
93+ logging.FromContext(ib.ctx).Warn().Msg("cef: no GDK display — clipboard paste will not work")
8394 }
8495
8596 // Motion (mouse move + enter/leave)
@@ -180,6 +191,10 @@ func (ib *inputBridge) attachFocusAndKeyboard(glArea *gtk.GLArea) {
180191 // ImeCommitText instead of letting CEF handle the paste.
181192 if mods&uint(gdk.ControlMaskValue) != 0 &&
182193 (keyval == gdkKeyLowercaseV || keyval == gdkKeyUppercaseV) {
194+ logging.FromContext(ib.ctx).Debug().
195+ Uint("keyval", keyval).
196+ Uint("mods", mods).
197+ Msg("cef: clipboard paste intercepted (Ctrl+V)")
183198 ib.pasteFromClipboard()
184199 return true
185200 }
@@ -380,23 +395,39 @@ func (ib *inputBridge) onKeyRelease(keyval, keycode, mods uint) {
380395 // injects it into the focused CEF input via ImeCommitText. This bridges the
381396 // gap where CEF OSR mode cannot read the Wayland/X11 system clipboard.
382397 func (ib *inputBridge) pasteFromClipboard() {
398+ log := logging.FromContext(ib.ctx)
399+
383400 cb := ib.clipboard
384401 if cb == nil {
402+ log.Warn().Msg("cef: paste aborted — no GDK clipboard reference")
385403 return
386404 }
387405 ib.mu.Lock()
388406 host := ib.host
389407 ib.mu.Unlock()
390408 if host == nil {
409+ log.Warn().Msg("cef: paste aborted — no browser host")
391410 return
392411 }
393412
413+ log.Debug().Msg("cef: calling ReadTextAsync on GDK clipboard")
414+
394415 asyncCb := gio.AsyncReadyCallback(func(_, resultPtr, _ uintptr) {
416+ log.Debug().Uint64("result_ptr", uint64(resultPtr)).Msg("cef: ReadTextAsync callback fired")
417+
395418 result := &gio.AsyncResultBase{Ptr: resultPtr}
396419 text, err := cb.ReadTextFinish(result)
397- if err != nil || text == "" {
420+ if err != nil {
421+ log.Warn().Err(err).Msg("cef: ReadTextFinish failed")
398422 return
399423 }
424+ if text == "" {
425+ log.Debug().Msg("cef: clipboard text is empty, nothing to paste")
426+ return
427+ }
428+
429+ log.Debug().Int("text_len", len(text)).Msg("cef: injecting clipboard text via ImeCommitText")
430+
400431 // The GLib async callback runs on the main loop (UI thread),
401432 // which is the correct thread for CEF ImeCommitText.
402433 ib.mu.Lock()
@@ -404,10 +435,14 @@ func (ib *inputBridge) pasteFromClipboard() {
404435 ib.mu.Unlock()
405436 if h != nil {
406437 h.ImeCommitText(text, nil, 0)
438+ log.Debug().Msg("cef: ImeCommitText completed")
439+ } else {
440+ log.Warn().Msg("cef: paste — host became nil before ImeCommitText")
407441 }
408442 })
409443
410444 cb.ReadTextAsync(nil, &asyncCb, 0)
445+ log.Debug().Msg("cef: ReadTextAsync dispatched")
411446 }
412447
413448 // ---------------------------------------------------------------------------
@@ -1,12 +1,15 @@
1 package cef1 package cef
2 2
3 import (3 import (
4+ "context"
4 "sync"5 "sync"
5 6
6 purecef "github.com/bnema/purego-cef/cef"7 purecef "github.com/bnema/purego-cef/cef"
7 "github.com/bnema/puregotk/v4/gdk"8 "github.com/bnema/puregotk/v4/gdk"
8 "github.com/bnema/puregotk/v4/gio"9 "github.com/bnema/puregotk/v4/gio"
9 "github.com/bnema/puregotk/v4/gtk"10 "github.com/bnema/puregotk/v4/gtk"
11+
12+ "github.com/bnema/dumber/internal/logging"
10 )13 )
11 14
12 // GDK dead key range boundaries. Dead keys (0xfe50–0xfe8c) are compose15 // GDK dead key range boundaries. Dead keys (0xfe50–0xfe8c) are compose
@@ -40,6 +43,7 @@ const (
40 type inputBridge struct {43 type inputBridge struct {
41 host purecef.BrowserHost44 host purecef.BrowserHost
42 scale int3245 scale int32
46+ ctx context.Context
43 mu sync.Mutex47 mu sync.Mutex
44 48
45 // Last known pointer position, used for scroll events which don't49 // Last known pointer position, used for scroll events which don't
@@ -63,8 +67,8 @@ type inputBridge struct {
63 }67 }
64 68
65 // newInputBridge creates an input bridge with the given HiDPI scale factor.69 // newInputBridge creates an input bridge with the given HiDPI scale factor.
66-func newInputBridge(scale int32) *inputBridge {70+func newInputBridge(ctx context.Context, scale int32) *inputBridge {
67- return &inputBridge{scale: scale}71+ return &inputBridge{ctx: ctx, scale: scale}
68 }72 }
69 73
70 // setHost is called once the browser is created to enable event dispatch.74 // setHost is called once the browser is created to enable event dispatch.
@@ -80,6 +84,13 @@ func (ib *inputBridge) attachTo(glArea *gtk.GLArea) {
80 // Wayland clipboard, so we bridge it via GDK → ImeCommitText.84 // Wayland clipboard, so we bridge it via GDK → ImeCommitText.
81 if display := gdk.DisplayGetDefault(); display != nil {85 if display := gdk.DisplayGetDefault(); display != nil {
82 ib.clipboard = display.GetClipboard()86 ib.clipboard = display.GetClipboard()
87+ if ib.clipboard != nil {
88+ logging.FromContext(ib.ctx).Debug().Msg("cef: GDK clipboard acquired for paste bridge")
89+ } else {
90+ logging.FromContext(ib.ctx).Warn().Msg("cef: GDK display found but GetClipboard returned nil")
91+ }
92+ } else {
93+ logging.FromContext(ib.ctx).Warn().Msg("cef: no GDK display — clipboard paste will not work")
83 }94 }
84 95
85 // Motion (mouse move + enter/leave)96 // Motion (mouse move + enter/leave)
@@ -180,6 +191,10 @@ func (ib *inputBridge) attachFocusAndKeyboard(glArea *gtk.GLArea) {
180 // ImeCommitText instead of letting CEF handle the paste.191 // ImeCommitText instead of letting CEF handle the paste.
181 if mods&uint(gdk.ControlMaskValue) != 0 &&192 if mods&uint(gdk.ControlMaskValue) != 0 &&
182 (keyval == gdkKeyLowercaseV || keyval == gdkKeyUppercaseV) {193 (keyval == gdkKeyLowercaseV || keyval == gdkKeyUppercaseV) {
194+ logging.FromContext(ib.ctx).Debug().
195+ Uint("keyval", keyval).
196+ Uint("mods", mods).
197+ Msg("cef: clipboard paste intercepted (Ctrl+V)")
183 ib.pasteFromClipboard()198 ib.pasteFromClipboard()
184 return true199 return true
185 }200 }
@@ -380,23 +395,39 @@ func (ib *inputBridge) onKeyRelease(keyval, keycode, mods uint) {
380 // injects it into the focused CEF input via ImeCommitText. This bridges the395 // injects it into the focused CEF input via ImeCommitText. This bridges the
381 // gap where CEF OSR mode cannot read the Wayland/X11 system clipboard.396 // gap where CEF OSR mode cannot read the Wayland/X11 system clipboard.
382 func (ib *inputBridge) pasteFromClipboard() {397 func (ib *inputBridge) pasteFromClipboard() {
398+ log := logging.FromContext(ib.ctx)
399+
383 cb := ib.clipboard400 cb := ib.clipboard
384 if cb == nil {401 if cb == nil {
402+ log.Warn().Msg("cef: paste aborted — no GDK clipboard reference")
385 return403 return
386 }404 }
387 ib.mu.Lock()405 ib.mu.Lock()
388 host := ib.host406 host := ib.host
389 ib.mu.Unlock()407 ib.mu.Unlock()
390 if host == nil {408 if host == nil {
409+ log.Warn().Msg("cef: paste aborted — no browser host")
391 return410 return
392 }411 }
393 412
413+ log.Debug().Msg("cef: calling ReadTextAsync on GDK clipboard")
414+
394 asyncCb := gio.AsyncReadyCallback(func(_, resultPtr, _ uintptr) {415 asyncCb := gio.AsyncReadyCallback(func(_, resultPtr, _ uintptr) {
416+ log.Debug().Uint64("result_ptr", uint64(resultPtr)).Msg("cef: ReadTextAsync callback fired")
417+
395 result := &gio.AsyncResultBase{Ptr: resultPtr}418 result := &gio.AsyncResultBase{Ptr: resultPtr}
396 text, err := cb.ReadTextFinish(result)419 text, err := cb.ReadTextFinish(result)
397- if err != nil || text == "" {420+ if err != nil {
421+ log.Warn().Err(err).Msg("cef: ReadTextFinish failed")
398 return422 return
399 }423 }
424+ if text == "" {
425+ log.Debug().Msg("cef: clipboard text is empty, nothing to paste")
426+ return
427+ }
428+
429+ log.Debug().Int("text_len", len(text)).Msg("cef: injecting clipboard text via ImeCommitText")
430+
400 // The GLib async callback runs on the main loop (UI thread),431 // The GLib async callback runs on the main loop (UI thread),
401 // which is the correct thread for CEF ImeCommitText.432 // which is the correct thread for CEF ImeCommitText.
402 ib.mu.Lock()433 ib.mu.Lock()
@@ -404,10 +435,14 @@ func (ib *inputBridge) pasteFromClipboard() {
404 ib.mu.Unlock()435 ib.mu.Unlock()
405 if h != nil {436 if h != nil {
406 h.ImeCommitText(text, nil, 0)437 h.ImeCommitText(text, nil, 0)
438+ log.Debug().Msg("cef: ImeCommitText completed")
439+ } else {
440+ log.Warn().Msg("cef: paste — host became nil before ImeCommitText")
407 }441 }
408 })442 })
409 443
410 cb.ReadTextAsync(nil, &asyncCb, 0)444 cb.ReadTextAsync(nil, &asyncCb, 0)
445+ log.Debug().Msg("cef: ReadTextAsync dispatched")
411 }446 }
412 447
413 // ---------------------------------------------------------------------------448 // ---------------------------------------------------------------------------
M internal/infrastructure/cef/render_pipeline.go +6 -6
@@ -179,7 +179,7 @@ func (rp *renderPipeline) handlePaint(buffer []byte, width, height int32, rects
179179 startedAt := time.Now()
180180 rp.lastQueuedPaintSeq.Store(paintSeq)
181181 if rp.ctx != nil {
182- logging.FromContext(rp.ctx).Debug().
182+ logging.FromContext(rp.ctx).Trace().
183183 Uint64("paint_seq", paintSeq).
184184 Int32("width", width).
185185 Int32("height", height).
@@ -253,7 +253,7 @@ func (rp *renderPipeline) handlePaint(buffer []byte, width, height int32, rects
253253 Int("expected_len", bufSize).
254254 Msg("cef: truncated full paint buffer on resize")
255255 }
256- logging.FromContext(rp.ctx).Debug().
256+ logging.FromContext(rp.ctx).Trace().
257257 Uint64("paint_seq", paintSeq).
258258 Uint64("copied_bytes", copiedBytes).
259259 Bool("size_changed", sizeChanged).
@@ -269,7 +269,7 @@ func (rp *renderPipeline) onGLRender() bool {
269269 paintSeq := rp.lastQueuedPaintSeq.Load()
270270 renderStartedAt := time.Now()
271271 if rp.ctx != nil {
272- logging.FromContext(rp.ctx).Debug().
272+ logging.FromContext(rp.ctx).Trace().
273273 Uint64("render_seq", renderSeq).
274274 Uint64("paint_seq", paintSeq).
275275 Msg("cef: onGLRender begin")
@@ -341,7 +341,7 @@ func (rp *renderPipeline) onGLRender() bool {
341341 }
342342 rp.maybeLogDiagnostics()
343343 if rp.ctx != nil {
344- logging.FromContext(rp.ctx).Debug().
344+ logging.FromContext(rp.ctx).Trace().
345345 Uint64("render_seq", renderSeq).
346346 Uint64("paint_seq", paintSeq).
347347 Dur("render_duration", renderDuration).
@@ -374,7 +374,7 @@ func (rp *renderPipeline) uploadToPBO(dirtyRects []rect, fullUpload bool, render
374374
375375 w, h, _ := rp.viewRectSize()
376376 if rp.ctx != nil {
377- logging.FromContext(rp.ctx).Debug().
377+ logging.FromContext(rp.ctx).Trace().
378378 Uint64("render_seq", renderSeq).
379379 Uint64("paint_seq", paintSeq).
380380 Int32("width", w).
@@ -463,7 +463,7 @@ func (rp *renderPipeline) uploadToPBO(dirtyRects []rect, fullUpload bool, render
463463
464464 gl.bindBuffer(glPixelUnpackBuffer, 0)
465465 if rp.ctx != nil {
466- logging.FromContext(rp.ctx).Debug().
466+ logging.FromContext(rp.ctx).Trace().
467467 Uint64("render_seq", renderSeq).
468468 Uint64("paint_seq", paintSeq).
469469 Uint64("uploaded_bytes", uploadedBytes).
@@ -179,7 +179,7 @@ func (rp *renderPipeline) handlePaint(buffer []byte, width, height int32, rects
179 startedAt := time.Now()179 startedAt := time.Now()
180 rp.lastQueuedPaintSeq.Store(paintSeq)180 rp.lastQueuedPaintSeq.Store(paintSeq)
181 if rp.ctx != nil {181 if rp.ctx != nil {
182- logging.FromContext(rp.ctx).Debug().182+ logging.FromContext(rp.ctx).Trace().
183 Uint64("paint_seq", paintSeq).183 Uint64("paint_seq", paintSeq).
184 Int32("width", width).184 Int32("width", width).
185 Int32("height", height).185 Int32("height", height).
@@ -253,7 +253,7 @@ func (rp *renderPipeline) handlePaint(buffer []byte, width, height int32, rects
253 Int("expected_len", bufSize).253 Int("expected_len", bufSize).
254 Msg("cef: truncated full paint buffer on resize")254 Msg("cef: truncated full paint buffer on resize")
255 }255 }
256- logging.FromContext(rp.ctx).Debug().256+ logging.FromContext(rp.ctx).Trace().
257 Uint64("paint_seq", paintSeq).257 Uint64("paint_seq", paintSeq).
258 Uint64("copied_bytes", copiedBytes).258 Uint64("copied_bytes", copiedBytes).
259 Bool("size_changed", sizeChanged).259 Bool("size_changed", sizeChanged).
@@ -269,7 +269,7 @@ func (rp *renderPipeline) onGLRender() bool {
269 paintSeq := rp.lastQueuedPaintSeq.Load()269 paintSeq := rp.lastQueuedPaintSeq.Load()
270 renderStartedAt := time.Now()270 renderStartedAt := time.Now()
271 if rp.ctx != nil {271 if rp.ctx != nil {
272- logging.FromContext(rp.ctx).Debug().272+ logging.FromContext(rp.ctx).Trace().
273 Uint64("render_seq", renderSeq).273 Uint64("render_seq", renderSeq).
274 Uint64("paint_seq", paintSeq).274 Uint64("paint_seq", paintSeq).
275 Msg("cef: onGLRender begin")275 Msg("cef: onGLRender begin")
@@ -341,7 +341,7 @@ func (rp *renderPipeline) onGLRender() bool {
341 }341 }
342 rp.maybeLogDiagnostics()342 rp.maybeLogDiagnostics()
343 if rp.ctx != nil {343 if rp.ctx != nil {
344- logging.FromContext(rp.ctx).Debug().344+ logging.FromContext(rp.ctx).Trace().
345 Uint64("render_seq", renderSeq).345 Uint64("render_seq", renderSeq).
346 Uint64("paint_seq", paintSeq).346 Uint64("paint_seq", paintSeq).
347 Dur("render_duration", renderDuration).347 Dur("render_duration", renderDuration).
@@ -374,7 +374,7 @@ func (rp *renderPipeline) uploadToPBO(dirtyRects []rect, fullUpload bool, render
374 374
375 w, h, _ := rp.viewRectSize()375 w, h, _ := rp.viewRectSize()
376 if rp.ctx != nil {376 if rp.ctx != nil {
377- logging.FromContext(rp.ctx).Debug().377+ logging.FromContext(rp.ctx).Trace().
378 Uint64("render_seq", renderSeq).378 Uint64("render_seq", renderSeq).
379 Uint64("paint_seq", paintSeq).379 Uint64("paint_seq", paintSeq).
380 Int32("width", w).380 Int32("width", w).
@@ -463,7 +463,7 @@ func (rp *renderPipeline) uploadToPBO(dirtyRects []rect, fullUpload bool, render
463 463
464 gl.bindBuffer(glPixelUnpackBuffer, 0)464 gl.bindBuffer(glPixelUnpackBuffer, 0)
465 if rp.ctx != nil {465 if rp.ctx != nil {
466- logging.FromContext(rp.ctx).Debug().466+ logging.FromContext(rp.ctx).Trace().
467 Uint64("render_seq", renderSeq).467 Uint64("render_seq", renderSeq).
468 Uint64("paint_seq", paintSeq).468 Uint64("paint_seq", paintSeq).
469 Uint64("uploaded_bytes", uploadedBytes).469 Uint64("uploaded_bytes", uploadedBytes).
M internal/infrastructure/cef/scheme_handler.go +8 -0
@@ -217,8 +217,11 @@ func (h *dumbSchemeHandler) handleConfigAPI(cfg *config.Config) purecef.Resource
217217 // handleClipboardSet receives copied text from JS copy/cut events and writes
218218 // it to the system clipboard via the engine callback.
219219 func (h *dumbSchemeHandler) handleClipboardSet(request purecef.Request) purecef.ResourceHandler {
220+ h.logger.Debug().Msg("cef: /api/clipboard-set request received")
221+
220222 body := readBodyFromHeader(request)
221223 if body == nil {
224+ h.logger.Debug().Msg("cef: clipboard-set — empty body (no X-Dumber-Body header)")
222225 return h.newJSONResourceHandler(http.StatusBadRequest, map[string]string{"error": "empty body"})
223226 }
224227
@@ -226,11 +229,16 @@ func (h *dumbSchemeHandler) handleClipboardSet(request purecef.Request) purecef.
226229 Text string `json:"text"`
227230 }
228231 if err := json.Unmarshal(body, &payload); err != nil || payload.Text == "" {
232+ h.logger.Debug().Int("body_len", len(body)).Msg("cef: clipboard-set — invalid or empty payload")
229233 return h.newJSONResourceHandler(http.StatusBadRequest, map[string]string{"error": "invalid payload"})
230234 }
231235
236+ h.logger.Debug().Int("text_len", len(payload.Text)).Msg("cef: clipboard-set — forwarding to GDK clipboard")
237+
232238 if h.onClipboardSet != nil {
233239 h.onClipboardSet(payload.Text)
240+ } else {
241+ h.logger.Warn().Msg("cef: clipboard-set — onClipboardSet callback not wired")
234242 }
235243
236244 return h.newJSONResourceHandler(http.StatusOK, map[string]any{"ok": true})
@@ -217,8 +217,11 @@ func (h *dumbSchemeHandler) handleConfigAPI(cfg *config.Config) purecef.Resource
217 // handleClipboardSet receives copied text from JS copy/cut events and writes217 // handleClipboardSet receives copied text from JS copy/cut events and writes
218 // it to the system clipboard via the engine callback.218 // it to the system clipboard via the engine callback.
219 func (h *dumbSchemeHandler) handleClipboardSet(request purecef.Request) purecef.ResourceHandler {219 func (h *dumbSchemeHandler) handleClipboardSet(request purecef.Request) purecef.ResourceHandler {
220+ h.logger.Debug().Msg("cef: /api/clipboard-set request received")
221+
220 body := readBodyFromHeader(request)222 body := readBodyFromHeader(request)
221 if body == nil {223 if body == nil {
224+ h.logger.Debug().Msg("cef: clipboard-set — empty body (no X-Dumber-Body header)")
222 return h.newJSONResourceHandler(http.StatusBadRequest, map[string]string{"error": "empty body"})225 return h.newJSONResourceHandler(http.StatusBadRequest, map[string]string{"error": "empty body"})
223 }226 }
224 227
@@ -226,11 +229,16 @@ func (h *dumbSchemeHandler) handleClipboardSet(request purecef.Request) purecef.
226 Text string `json:"text"`229 Text string `json:"text"`
227 }230 }
228 if err := json.Unmarshal(body, &payload); err != nil || payload.Text == "" {231 if err := json.Unmarshal(body, &payload); err != nil || payload.Text == "" {
232+ h.logger.Debug().Int("body_len", len(body)).Msg("cef: clipboard-set — invalid or empty payload")
229 return h.newJSONResourceHandler(http.StatusBadRequest, map[string]string{"error": "invalid payload"})233 return h.newJSONResourceHandler(http.StatusBadRequest, map[string]string{"error": "invalid payload"})
230 }234 }
231 235
236+ h.logger.Debug().Int("text_len", len(payload.Text)).Msg("cef: clipboard-set — forwarding to GDK clipboard")
237+
232 if h.onClipboardSet != nil {238 if h.onClipboardSet != nil {
233 h.onClipboardSet(payload.Text)239 h.onClipboardSet(payload.Text)
240+ } else {
241+ h.logger.Warn().Msg("cef: clipboard-set — onClipboardSet callback not wired")
234 }242 }
235 243
236 return h.newJSONResourceHandler(http.StatusOK, map[string]any{"ok": true})244 return h.newJSONResourceHandler(http.StatusOK, map[string]any{"ok": true})
M internal/infrastructure/cef/webview_handlers.go +3 -3
@@ -193,7 +193,7 @@ func (h *handlerSet) OnPaint(
193193 }
194194 paintSeq := h.wv.pipeline.nextPaintSeq()
195195 if h.wv != nil && h.wv.ctx != nil {
196- logging.FromContext(h.wv.ctx).Debug().
196+ logging.FromContext(h.wv.ctx).Trace().
197197 Uint64("paint_seq", paintSeq).
198198 Int32("width", width).
199199 Int32("height", height).
@@ -216,7 +216,7 @@ func (h *handlerSet) OnPaint(
216216 h.wv.pipeline.handlePaint(pixels, width, height, rects, paintSeq)
217217 })
218218 if h.wv != nil && h.wv.ctx != nil {
219- logging.FromContext(h.wv.ctx).Debug().
219+ logging.FromContext(h.wv.ctx).Trace().
220220 Uint64("paint_seq", paintSeq).
221221 Msg("cef: OnPaint queued to GTK")
222222 }
@@ -228,7 +228,7 @@ func (h *handlerSet) OnPaint(
228228 }
229229 h.wv.pipeline.handlePaint(buffer, width, height, rects, paintSeq)
230230 if h.wv != nil && h.wv.ctx != nil {
231- logging.FromContext(h.wv.ctx).Debug().
231+ logging.FromContext(h.wv.ctx).Trace().
232232 Uint64("paint_seq", paintSeq).
233233 Msg("cef: OnPaint handled inline")
234234 }
@@ -193,7 +193,7 @@ func (h *handlerSet) OnPaint(
193 }193 }
194 paintSeq := h.wv.pipeline.nextPaintSeq()194 paintSeq := h.wv.pipeline.nextPaintSeq()
195 if h.wv != nil && h.wv.ctx != nil {195 if h.wv != nil && h.wv.ctx != nil {
196- logging.FromContext(h.wv.ctx).Debug().196+ logging.FromContext(h.wv.ctx).Trace().
197 Uint64("paint_seq", paintSeq).197 Uint64("paint_seq", paintSeq).
198 Int32("width", width).198 Int32("width", width).
199 Int32("height", height).199 Int32("height", height).
@@ -216,7 +216,7 @@ func (h *handlerSet) OnPaint(
216 h.wv.pipeline.handlePaint(pixels, width, height, rects, paintSeq)216 h.wv.pipeline.handlePaint(pixels, width, height, rects, paintSeq)
217 })217 })
218 if h.wv != nil && h.wv.ctx != nil {218 if h.wv != nil && h.wv.ctx != nil {
219- logging.FromContext(h.wv.ctx).Debug().219+ logging.FromContext(h.wv.ctx).Trace().
220 Uint64("paint_seq", paintSeq).220 Uint64("paint_seq", paintSeq).
221 Msg("cef: OnPaint queued to GTK")221 Msg("cef: OnPaint queued to GTK")
222 }222 }
@@ -228,7 +228,7 @@ func (h *handlerSet) OnPaint(
228 }228 }
229 h.wv.pipeline.handlePaint(buffer, width, height, rects, paintSeq)229 h.wv.pipeline.handlePaint(buffer, width, height, rects, paintSeq)
230 if h.wv != nil && h.wv.ctx != nil {230 if h.wv != nil && h.wv.ctx != nil {
231- logging.FromContext(h.wv.ctx).Debug().231+ logging.FromContext(h.wv.ctx).Trace().
232 Uint64("paint_seq", paintSeq).232 Uint64("paint_seq", paintSeq).
233 Msg("cef: OnPaint handled inline")233 Msg("cef: OnPaint handled inline")
234 }234 }

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