detached 05dede8

wip(cef): refactor touchpad input handling

commit
05dede819ad3c0cb05c4899f3ce6b947e271ddc3
author
brice · · 2026-06-08 20:21 UTC
parent
1b29d2c
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
15 files · +119 -163
M docs/config/index.md +2 -2
@@ -195,12 +195,12 @@ border = "#363636"
195195 | `engine.cef.windowless_frame_rate` | int32 | `0` | >= 0 | Explicit CEF OSR FPS cap; 0 uses adaptive mode if enabled, otherwise CEF default |
196196 | `engine.cef.windowless_frame_rate_max` | int32 | `240` | >= 0 | Hard cap for adaptive CEF OSR FPS; 0 falls back to the built-in default cap selected by Dumber/CEF |
197197 | `engine.cef.input.scroll_wheel_multiplier` | float | `1.0` | > 0 | Mouse wheel scroll sensitivity multiplier |
198-| `engine.cef.input.scroll_touchpad_multiplier` | float | `0.35` | > 0 | Touchpad/smooth scroll sensitivity multiplier |
198+| `engine.cef.input.scroll_precise_multiplier` | float | `2.5` | > 0 | Precise/surface scroll sensitivity multiplier |
199199 | `engine.cef.input.scroll_horizontal_multiplier` | float | `1.0` | > 0 | Horizontal scroll sensitivity multiplier |
200200 | `engine.cef.input.scroll_vertical_multiplier` | float | `1.0` | > 0 | Vertical scroll sensitivity multiplier |
201201 | `engine.cef.input.scroll_max_delta` | int32 | `0` | >= 0 | Maximum absolute scroll delta after scaling; 0 disables clamping |
202202 | `engine.cef.input.touchpad_navigation_enabled` | bool | `true` | - | Enable two-finger touchpad swipe back/forward navigation |
203-| `engine.cef.input.touchpad_navigation_min_delta` | float | `80.0` | > 0 | Minimum accumulated horizontal swipe delta required for navigation |
203+| `engine.cef.input.touchpad_navigation_min_delta` | float | `15.0` | > 0 | Minimum accumulated horizontal swipe delta required for navigation |
204204 | `engine.cef.input.touchpad_navigation_max_vertical_ratio` | float | `0.5` | > 0 | Maximum vertical-to-horizontal delta ratio allowed for navigation swipes |
205205 | `default_ui_scale` | float | `1.0` | > 0 | GTK widget UI scale (1.0=100%, 2.0=200%) |
206206 | `default_webpage_zoom` | float | `1.2` | > 0 | Default page zoom (1.0=100%, 1.2=120%) |
@@ -195,12 +195,12 @@ border = "#363636"
195 | `engine.cef.windowless_frame_rate` | int32 | `0` | >= 0 | Explicit CEF OSR FPS cap; 0 uses adaptive mode if enabled, otherwise CEF default |195 | `engine.cef.windowless_frame_rate` | int32 | `0` | >= 0 | Explicit CEF OSR FPS cap; 0 uses adaptive mode if enabled, otherwise CEF default |
196 | `engine.cef.windowless_frame_rate_max` | int32 | `240` | >= 0 | Hard cap for adaptive CEF OSR FPS; 0 falls back to the built-in default cap selected by Dumber/CEF |196 | `engine.cef.windowless_frame_rate_max` | int32 | `240` | >= 0 | Hard cap for adaptive CEF OSR FPS; 0 falls back to the built-in default cap selected by Dumber/CEF |
197 | `engine.cef.input.scroll_wheel_multiplier` | float | `1.0` | > 0 | Mouse wheel scroll sensitivity multiplier |197 | `engine.cef.input.scroll_wheel_multiplier` | float | `1.0` | > 0 | Mouse wheel scroll sensitivity multiplier |
198-| `engine.cef.input.scroll_touchpad_multiplier` | float | `0.35` | > 0 | Touchpad/smooth scroll sensitivity multiplier |198+| `engine.cef.input.scroll_precise_multiplier` | float | `2.5` | > 0 | Precise/surface scroll sensitivity multiplier |
199 | `engine.cef.input.scroll_horizontal_multiplier` | float | `1.0` | > 0 | Horizontal scroll sensitivity multiplier |199 | `engine.cef.input.scroll_horizontal_multiplier` | float | `1.0` | > 0 | Horizontal scroll sensitivity multiplier |
200 | `engine.cef.input.scroll_vertical_multiplier` | float | `1.0` | > 0 | Vertical scroll sensitivity multiplier |200 | `engine.cef.input.scroll_vertical_multiplier` | float | `1.0` | > 0 | Vertical scroll sensitivity multiplier |
201 | `engine.cef.input.scroll_max_delta` | int32 | `0` | >= 0 | Maximum absolute scroll delta after scaling; 0 disables clamping |201 | `engine.cef.input.scroll_max_delta` | int32 | `0` | >= 0 | Maximum absolute scroll delta after scaling; 0 disables clamping |
202 | `engine.cef.input.touchpad_navigation_enabled` | bool | `true` | - | Enable two-finger touchpad swipe back/forward navigation |202 | `engine.cef.input.touchpad_navigation_enabled` | bool | `true` | - | Enable two-finger touchpad swipe back/forward navigation |
203-| `engine.cef.input.touchpad_navigation_min_delta` | float | `80.0` | > 0 | Minimum accumulated horizontal swipe delta required for navigation |203+| `engine.cef.input.touchpad_navigation_min_delta` | float | `15.0` | > 0 | Minimum accumulated horizontal swipe delta required for navigation |
204 | `engine.cef.input.touchpad_navigation_max_vertical_ratio` | float | `0.5` | > 0 | Maximum vertical-to-horizontal delta ratio allowed for navigation swipes |204 | `engine.cef.input.touchpad_navigation_max_vertical_ratio` | float | `0.5` | > 0 | Maximum vertical-to-horizontal delta ratio allowed for navigation swipes |
205 | `default_ui_scale` | float | `1.0` | > 0 | GTK widget UI scale (1.0=100%, 2.0=200%) |205 | `default_ui_scale` | float | `1.0` | > 0 | GTK widget UI scale (1.0=100%, 2.0=200%) |
206 | `default_webpage_zoom` | float | `1.2` | > 0 | Default page zoom (1.0=100%, 1.2=120%) |206 | `default_webpage_zoom` | float | `1.2` | > 0 | Default page zoom (1.0=100%, 1.2=120%) |
M docs/reference/configuration.md +2 -2
@@ -65,12 +65,12 @@
6565 | `engine.cef.windowless_frame_rate` | int32 | `0` | >= 0 |
6666 | `engine.cef.windowless_frame_rate_max` | int32 | `240` | >= 0 |
6767 | `engine.cef.input.scroll_wheel_multiplier` | float | `1.0` | > 0 |
68-| `engine.cef.input.scroll_touchpad_multiplier` | float | `0.35` | > 0 |
68+| `engine.cef.input.scroll_precise_multiplier` | float | `2.5` | > 0 |
6969 | `engine.cef.input.scroll_horizontal_multiplier` | float | `1.0` | > 0 |
7070 | `engine.cef.input.scroll_vertical_multiplier` | float | `1.0` | > 0 |
7171 | `engine.cef.input.scroll_max_delta` | int32 | `0` | >= 0 |
7272 | `engine.cef.input.touchpad_navigation_enabled` | bool | `true` | |
73-| `engine.cef.input.touchpad_navigation_min_delta` | float | `80.0` | > 0 |
73+| `engine.cef.input.touchpad_navigation_min_delta` | float | `15.0` | > 0 |
7474 | `engine.cef.input.touchpad_navigation_max_vertical_ratio` | float | `0.5` | > 0 |
7575 | `default_ui_scale` | float | `1.0` | > 0 |
7676 | `default_webpage_zoom` | float | `1.2` | > 0 |
@@ -65,12 +65,12 @@
65 | `engine.cef.windowless_frame_rate` | int32 | `0` | >= 0 |65 | `engine.cef.windowless_frame_rate` | int32 | `0` | >= 0 |
66 | `engine.cef.windowless_frame_rate_max` | int32 | `240` | >= 0 |66 | `engine.cef.windowless_frame_rate_max` | int32 | `240` | >= 0 |
67 | `engine.cef.input.scroll_wheel_multiplier` | float | `1.0` | > 0 |67 | `engine.cef.input.scroll_wheel_multiplier` | float | `1.0` | > 0 |
68-| `engine.cef.input.scroll_touchpad_multiplier` | float | `0.35` | > 0 |68+| `engine.cef.input.scroll_precise_multiplier` | float | `2.5` | > 0 |
69 | `engine.cef.input.scroll_horizontal_multiplier` | float | `1.0` | > 0 |69 | `engine.cef.input.scroll_horizontal_multiplier` | float | `1.0` | > 0 |
70 | `engine.cef.input.scroll_vertical_multiplier` | float | `1.0` | > 0 |70 | `engine.cef.input.scroll_vertical_multiplier` | float | `1.0` | > 0 |
71 | `engine.cef.input.scroll_max_delta` | int32 | `0` | >= 0 |71 | `engine.cef.input.scroll_max_delta` | int32 | `0` | >= 0 |
72 | `engine.cef.input.touchpad_navigation_enabled` | bool | `true` | |72 | `engine.cef.input.touchpad_navigation_enabled` | bool | `true` | |
73-| `engine.cef.input.touchpad_navigation_min_delta` | float | `80.0` | > 0 |73+| `engine.cef.input.touchpad_navigation_min_delta` | float | `15.0` | > 0 |
74 | `engine.cef.input.touchpad_navigation_max_vertical_ratio` | float | `0.5` | > 0 |74 | `engine.cef.input.touchpad_navigation_max_vertical_ratio` | float | `0.5` | > 0 |
75 | `default_ui_scale` | float | `1.0` | > 0 |75 | `default_ui_scale` | float | `1.0` | > 0 |
76 | `default_webpage_zoom` | float | `1.2` | > 0 |76 | `default_webpage_zoom` | float | `1.2` | > 0 |
M go.mod +2 -0
@@ -83,3 +83,5 @@ require (
8383 )
8484
8585 tool github.com/a-h/templ/cmd/templ
86+
87+replace github.com/bnema/purego-cef2gtk => ../purego-cef2gtk
@@ -83,3 +83,5 @@ require (
83 )83 )
84 84
85 tool github.com/a-h/templ/cmd/templ85 tool github.com/a-h/templ/cmd/templ
86+
87+replace github.com/bnema/purego-cef2gtk => ../purego-cef2gtk
M internal/bootstrap/engine.go +1 -1
@@ -86,7 +86,7 @@ func BuildEngine(input EngineInput) (port.Engine, error) {
8686 WindowlessFrameRateMax: cfg.Engine.CEF.CEFWindowlessFrameRateMax(),
8787 Input: cef.RuntimeInputConfig{
8888 ScrollWheelMultiplier: cfg.Engine.CEF.Input.ScrollWheelMultiplier,
89- ScrollTouchpadMultiplier: cfg.Engine.CEF.Input.ScrollTouchpadMultiplier,
89+ ScrollPreciseMultiplier: cfg.Engine.CEF.Input.ScrollPreciseMultiplier,
9090 ScrollHorizontalMultiplier: cfg.Engine.CEF.Input.ScrollHorizontalMultiplier,
9191 ScrollVerticalMultiplier: cfg.Engine.CEF.Input.ScrollVerticalMultiplier,
9292 ScrollMaxDelta: cfg.Engine.CEF.Input.ScrollMaxDelta,
@@ -86,7 +86,7 @@ func BuildEngine(input EngineInput) (port.Engine, error) {
86 WindowlessFrameRateMax: cfg.Engine.CEF.CEFWindowlessFrameRateMax(),86 WindowlessFrameRateMax: cfg.Engine.CEF.CEFWindowlessFrameRateMax(),
87 Input: cef.RuntimeInputConfig{87 Input: cef.RuntimeInputConfig{
88 ScrollWheelMultiplier: cfg.Engine.CEF.Input.ScrollWheelMultiplier,88 ScrollWheelMultiplier: cfg.Engine.CEF.Input.ScrollWheelMultiplier,
89- ScrollTouchpadMultiplier: cfg.Engine.CEF.Input.ScrollTouchpadMultiplier,89+ ScrollPreciseMultiplier: cfg.Engine.CEF.Input.ScrollPreciseMultiplier,
90 ScrollHorizontalMultiplier: cfg.Engine.CEF.Input.ScrollHorizontalMultiplier,90 ScrollHorizontalMultiplier: cfg.Engine.CEF.Input.ScrollHorizontalMultiplier,
91 ScrollVerticalMultiplier: cfg.Engine.CEF.Input.ScrollVerticalMultiplier,91 ScrollVerticalMultiplier: cfg.Engine.CEF.Input.ScrollVerticalMultiplier,
92 ScrollMaxDelta: cfg.Engine.CEF.Input.ScrollMaxDelta,92 ScrollMaxDelta: cfg.Engine.CEF.Input.ScrollMaxDelta,
M internal/infrastructure/cef/runtime_config.go +1 -1
@@ -30,7 +30,7 @@ type RuntimeConfig struct {
3030
3131 type RuntimeInputConfig struct {
3232 ScrollWheelMultiplier float64
33- ScrollTouchpadMultiplier float64
33+ ScrollPreciseMultiplier float64
3434 ScrollHorizontalMultiplier float64
3535 ScrollVerticalMultiplier float64
3636 ScrollMaxDelta int32
@@ -30,7 +30,7 @@ type RuntimeConfig struct {
30 30
31 type RuntimeInputConfig struct {31 type RuntimeInputConfig struct {
32 ScrollWheelMultiplier float6432 ScrollWheelMultiplier float64
33- ScrollTouchpadMultiplier float6433+ ScrollPreciseMultiplier float64
34 ScrollHorizontalMultiplier float6434 ScrollHorizontalMultiplier float64
35 ScrollVerticalMultiplier float6435 ScrollVerticalMultiplier float64
36 ScrollMaxDelta int3236 ScrollMaxDelta int32
M internal/infrastructure/cef/webview.go +79 -94
@@ -225,8 +225,16 @@ type WebView struct {
225225 windowlessFrameRate int32
226226 backgroundColor uint32
227227 inputConfig RuntimeInputConfig
228- touchpadSwipeDX float64
229- touchpadSwipeDY float64
228+
229+ // Touchpad input diagnostics are debounced to avoid flooding logs while
230+ // debugging continuous scroll streams.
231+ inputDiagMu sync.Mutex
232+ inputDiagLastLog time.Time
233+ inputDiagEvents int
234+ inputDiagDX float64
235+ inputDiagDY float64
236+ inputDiagDeltaXSum int64
237+ inputDiagDeltaYSum int64
230238
231239 // Audio output factory and active stream.
232240 audioOutputFactory port.AudioOutputFactory
@@ -1462,7 +1470,7 @@ func (wv *WebView) bridgeInputOptions() cef2gtk.InputOptions {
14621470 Scale: 0,
14631471 Scroll: cef2gtk.ScrollOptions{
14641472 WheelMultiplier: wv.inputConfig.ScrollWheelMultiplier,
1465- TouchpadMultiplier: wv.inputConfig.ScrollTouchpadMultiplier,
1473+ PreciseMultiplier: wv.inputConfig.ScrollPreciseMultiplier,
14661474 HorizontalMultiplier: wv.inputConfig.ScrollHorizontalMultiplier,
14671475 VerticalMultiplier: wv.inputConfig.ScrollVerticalMultiplier,
14681476 MaxDelta: wv.inputConfig.ScrollMaxDelta,
@@ -1470,13 +1478,16 @@ func (wv *WebView) bridgeInputOptions() cef2gtk.InputOptions {
14701478 OnMiddleClick: func(_, _ float64) bool {
14711479 return wv.handleMiddleClickFromBridge()
14721480 },
1473- OnTouchpadSwipe: func(event cef2gtk.TouchpadSwipeEvent) cef2gtk.TouchpadSwipeDecision {
1474- if wv.handleTouchpadNavigationSwipe(event) {
1475- return cef2gtk.TouchpadSwipeConsume
1476- }
1477- return cef2gtk.TouchpadSwipePassthrough
1481+ OnScroll: wv.handleScrollInputDiagnostic,
1482+ NavigationSwipe: cef2gtk.NavigationSwipeOptions{
1483+ Enabled: wv.inputConfig.TouchpadNavigationEnabled,
1484+ MinDelta: wv.inputConfig.TouchpadNavigationMinDelta,
1485+ MaxVerticalRatio: wv.inputConfig.TouchpadNavigationMaxVerticalRatio,
14781486 },
1479- SelectionText: wv.selectedTextSnapshot,
1487+ CanNavigateBack: wv.CanGoBack,
1488+ CanNavigateForward: wv.CanGoForward,
1489+ OnNavigateSwipe: wv.handleNavigationSwipeAction,
1490+ SelectionText: wv.selectedTextSnapshot,
14801491 OnClipboardShortcut: func(action, text string) {
14811492 if wv.engine != nil {
14821493 wv.engine.handleExplicitClipboardBridgeText(wv.id, action, text)
@@ -1485,95 +1496,69 @@ func (wv *WebView) bridgeInputOptions() cef2gtk.InputOptions {
14851496 }
14861497 }
14871498
1488-type touchpadNavigationAction int
1489-
1490-const (
1491- touchpadNavigationNone touchpadNavigationAction = iota
1492- touchpadNavigationBack
1493- touchpadNavigationForward
1494-)
1495-
1496-func (wv *WebView) handleTouchpadNavigationSwipe(event cef2gtk.TouchpadSwipeEvent) bool {
1497- cfg := wv.inputConfig
1498- if !cfg.TouchpadNavigationEnabled || event.Fingers != 2 {
1499- wv.resetTouchpadNavigationSwipe()
1500- return false
1499+func (wv *WebView) handleScrollInputDiagnostic(event cef2gtk.ScrollEvent) cef2gtk.ScrollDecision {
1500+ if event.Phase != cef2gtk.ScrollPhaseUpdate || math.Abs(event.DX) == 0 {
1501+ return cef2gtk.ScrollForwardToCEF
15011502 }
15021503
1503- switch event.Phase {
1504- case cef2gtk.TouchpadGesturePhaseBegin:
1505- wv.resetTouchpadNavigationSwipe()
1506- return false
1507- case cef2gtk.TouchpadGesturePhaseUpdate:
1508- wv.accumulateTouchpadNavigationSwipe(event.DX, event.DY)
1509- return false
1510- case cef2gtk.TouchpadGesturePhaseEnd:
1511- dx, dy := wv.finishTouchpadNavigationSwipe()
1512- switch chooseTouchpadNavigationAction(cfg, dx, dy, wv.CanGoBack(), wv.CanGoForward()) {
1513- case touchpadNavigationBack:
1514- if err := wv.GoBack(wv.ctx); err != nil {
1515- logging.FromContext(wv.ctx).Warn().Err(err).Msg("touchpad back navigation failed")
1516- return false
1517- }
1518- return true
1519- case touchpadNavigationForward:
1520- if err := wv.GoForward(wv.ctx); err != nil {
1521- logging.FromContext(wv.ctx).Warn().Err(err).Msg("touchpad forward navigation failed")
1522- return false
1523- }
1524- return true
1525- default:
1526- return false
1527- }
1528- case cef2gtk.TouchpadGesturePhaseCancel:
1529- wv.resetTouchpadNavigationSwipe()
1530- return false
1531- default:
1532- return false
1533- }
1534-}
1535-
1536-func (wv *WebView) resetTouchpadNavigationSwipe() {
1537- wv.mu.Lock()
1538- wv.touchpadSwipeDX = 0
1539- wv.touchpadSwipeDY = 0
1540- wv.mu.Unlock()
1541-}
1542-
1543-func (wv *WebView) accumulateTouchpadNavigationSwipe(dx, dy float64) {
1544- wv.mu.Lock()
1545- wv.touchpadSwipeDX += dx
1546- wv.touchpadSwipeDY += dy
1547- wv.mu.Unlock()
1548-}
1549-
1550-func (wv *WebView) finishTouchpadNavigationSwipe() (float64, float64) {
1551- wv.mu.Lock()
1552- defer wv.mu.Unlock()
1553- dx, dy := wv.touchpadSwipeDX, wv.touchpadSwipeDY
1554- wv.touchpadSwipeDX = 0
1555- wv.touchpadSwipeDY = 0
1556- return dx, dy
1557-}
1504+ wv.inputDiagMu.Lock()
1505+ wv.inputDiagEvents++
1506+ wv.inputDiagDX += event.DX
1507+ wv.inputDiagDY += event.DY
1508+ wv.inputDiagDeltaXSum += int64(event.DeltaX)
1509+ wv.inputDiagDeltaYSum += int64(event.DeltaY)
1510+ now := time.Now()
1511+ if !wv.inputDiagLastLog.IsZero() && now.Sub(wv.inputDiagLastLog) < time.Second {
1512+ wv.inputDiagMu.Unlock()
1513+ return cef2gtk.ScrollForwardToCEF
1514+ }
1515+ count := wv.inputDiagEvents
1516+ dx, dy := wv.inputDiagDX, wv.inputDiagDY
1517+ deltaX, deltaY := wv.inputDiagDeltaXSum, wv.inputDiagDeltaYSum
1518+ wv.inputDiagEvents = 0
1519+ wv.inputDiagDX = 0
1520+ wv.inputDiagDY = 0
1521+ wv.inputDiagDeltaXSum = 0
1522+ wv.inputDiagDeltaYSum = 0
1523+ wv.inputDiagLastLog = now
1524+ wv.inputDiagMu.Unlock()
15581525
1559-func chooseTouchpadNavigationAction(cfg RuntimeInputConfig, dx, dy float64, canGoBack, canGoForward bool) touchpadNavigationAction {
1560- if !cfg.TouchpadNavigationEnabled {
1561- return touchpadNavigationNone
1562- }
1563- absDX, absDY := math.Abs(dx), math.Abs(dy)
1564- if absDX < cfg.TouchpadNavigationMinDelta {
1565- return touchpadNavigationNone
1566- }
1567- if absDY > absDX*cfg.TouchpadNavigationMaxVerticalRatio {
1568- return touchpadNavigationNone
1569- }
1570- if dx > 0 && canGoBack {
1571- return touchpadNavigationBack
1572- }
1573- if dx < 0 && canGoForward {
1574- return touchpadNavigationForward
1526+ logging.FromContext(wv.ctx).Debug().
1527+ Uint64("webview_id", uint64(wv.id)).
1528+ Int("events", count).
1529+ Str("phase", fmt.Sprint(event.Phase)).
1530+ Str("unit", fmt.Sprint(event.Unit)).
1531+ Bool("unit_known", event.UnitKnown).
1532+ Float64("dx_sum", dx).
1533+ Float64("dy_sum", dy).
1534+ Int64("cef_delta_x_sum", deltaX).
1535+ Int64("cef_delta_y_sum", deltaY).
1536+ Bool("nav_enabled", wv.inputConfig.TouchpadNavigationEnabled).
1537+ Bool("can_go_back", wv.CanGoBack()).
1538+ Bool("can_go_forward", wv.CanGoForward()).
1539+ Msg("cef: touchpad horizontal scroll diagnostic")
1540+
1541+ return cef2gtk.ScrollForwardToCEF
1542+}
1543+
1544+func (wv *WebView) handleNavigationSwipeAction(action cef2gtk.NavigationSwipeAction) {
1545+ logging.FromContext(wv.ctx).Debug().
1546+ Uint64("webview_id", uint64(wv.id)).
1547+ Str("action", fmt.Sprint(action)).
1548+ Bool("can_go_back", wv.CanGoBack()).
1549+ Bool("can_go_forward", wv.CanGoForward()).
1550+ Msg("cef: touchpad navigation swipe recognized")
1551+
1552+ switch action {
1553+ case cef2gtk.NavigationSwipeBack:
1554+ if err := wv.GoBack(wv.ctx); err != nil {
1555+ logging.FromContext(wv.ctx).Warn().Err(err).Msg("touchpad back navigation failed")
1556+ }
1557+ case cef2gtk.NavigationSwipeForward:
1558+ if err := wv.GoForward(wv.ctx); err != nil {
1559+ logging.FromContext(wv.ctx).Warn().Err(err).Msg("touchpad forward navigation failed")
1560+ }
15751561 }
1576- return touchpadNavigationNone
15771562 }
15781563
15791564 func (wv *WebView) viewBridgeScale() float64 {
@@ -225,8 +225,16 @@ type WebView struct {
225 windowlessFrameRate int32225 windowlessFrameRate int32
226 backgroundColor uint32226 backgroundColor uint32
227 inputConfig RuntimeInputConfig227 inputConfig RuntimeInputConfig
228- touchpadSwipeDX float64228+
229- touchpadSwipeDY float64229+ // Touchpad input diagnostics are debounced to avoid flooding logs while
230+ // debugging continuous scroll streams.
231+ inputDiagMu sync.Mutex
232+ inputDiagLastLog time.Time
233+ inputDiagEvents int
234+ inputDiagDX float64
235+ inputDiagDY float64
236+ inputDiagDeltaXSum int64
237+ inputDiagDeltaYSum int64
230 238
231 // Audio output factory and active stream.239 // Audio output factory and active stream.
232 audioOutputFactory port.AudioOutputFactory240 audioOutputFactory port.AudioOutputFactory
@@ -1462,7 +1470,7 @@ func (wv *WebView) bridgeInputOptions() cef2gtk.InputOptions {
1462 Scale: 0,1470 Scale: 0,
1463 Scroll: cef2gtk.ScrollOptions{1471 Scroll: cef2gtk.ScrollOptions{
1464 WheelMultiplier: wv.inputConfig.ScrollWheelMultiplier,1472 WheelMultiplier: wv.inputConfig.ScrollWheelMultiplier,
1465- TouchpadMultiplier: wv.inputConfig.ScrollTouchpadMultiplier,1473+ PreciseMultiplier: wv.inputConfig.ScrollPreciseMultiplier,
1466 HorizontalMultiplier: wv.inputConfig.ScrollHorizontalMultiplier,1474 HorizontalMultiplier: wv.inputConfig.ScrollHorizontalMultiplier,
1467 VerticalMultiplier: wv.inputConfig.ScrollVerticalMultiplier,1475 VerticalMultiplier: wv.inputConfig.ScrollVerticalMultiplier,
1468 MaxDelta: wv.inputConfig.ScrollMaxDelta,1476 MaxDelta: wv.inputConfig.ScrollMaxDelta,
@@ -1470,13 +1478,16 @@ func (wv *WebView) bridgeInputOptions() cef2gtk.InputOptions {
1470 OnMiddleClick: func(_, _ float64) bool {1478 OnMiddleClick: func(_, _ float64) bool {
1471 return wv.handleMiddleClickFromBridge()1479 return wv.handleMiddleClickFromBridge()
1472 },1480 },
1473- OnTouchpadSwipe: func(event cef2gtk.TouchpadSwipeEvent) cef2gtk.TouchpadSwipeDecision {1481+ OnScroll: wv.handleScrollInputDiagnostic,
1474- if wv.handleTouchpadNavigationSwipe(event) {1482+ NavigationSwipe: cef2gtk.NavigationSwipeOptions{
1475- return cef2gtk.TouchpadSwipeConsume1483+ Enabled: wv.inputConfig.TouchpadNavigationEnabled,
1476- }1484+ MinDelta: wv.inputConfig.TouchpadNavigationMinDelta,
1477- return cef2gtk.TouchpadSwipePassthrough1485+ MaxVerticalRatio: wv.inputConfig.TouchpadNavigationMaxVerticalRatio,
1478 },1486 },
1479- SelectionText: wv.selectedTextSnapshot,1487+ CanNavigateBack: wv.CanGoBack,
1488+ CanNavigateForward: wv.CanGoForward,
1489+ OnNavigateSwipe: wv.handleNavigationSwipeAction,
1490+ SelectionText: wv.selectedTextSnapshot,
1480 OnClipboardShortcut: func(action, text string) {1491 OnClipboardShortcut: func(action, text string) {
1481 if wv.engine != nil {1492 if wv.engine != nil {
1482 wv.engine.handleExplicitClipboardBridgeText(wv.id, action, text)1493 wv.engine.handleExplicitClipboardBridgeText(wv.id, action, text)
@@ -1485,95 +1496,69 @@ func (wv *WebView) bridgeInputOptions() cef2gtk.InputOptions {
1485 }1496 }
1486 }1497 }
1487 1498
1488-type touchpadNavigationAction int1499+func (wv *WebView) handleScrollInputDiagnostic(event cef2gtk.ScrollEvent) cef2gtk.ScrollDecision {
1489-1500+ if event.Phase != cef2gtk.ScrollPhaseUpdate || math.Abs(event.DX) == 0 {
1490-const (1501+ return cef2gtk.ScrollForwardToCEF
1491- touchpadNavigationNone touchpadNavigationAction = iota
1492- touchpadNavigationBack
1493- touchpadNavigationForward
1494-)
1495-
1496-func (wv *WebView) handleTouchpadNavigationSwipe(event cef2gtk.TouchpadSwipeEvent) bool {
1497- cfg := wv.inputConfig
1498- if !cfg.TouchpadNavigationEnabled || event.Fingers != 2 {
1499- wv.resetTouchpadNavigationSwipe()
1500- return false
1501 }1502 }
1502 1503
1503- switch event.Phase {1504+ wv.inputDiagMu.Lock()
1504- case cef2gtk.TouchpadGesturePhaseBegin:1505+ wv.inputDiagEvents++
1505- wv.resetTouchpadNavigationSwipe()1506+ wv.inputDiagDX += event.DX
1506- return false1507+ wv.inputDiagDY += event.DY
1507- case cef2gtk.TouchpadGesturePhaseUpdate:1508+ wv.inputDiagDeltaXSum += int64(event.DeltaX)
1508- wv.accumulateTouchpadNavigationSwipe(event.DX, event.DY)1509+ wv.inputDiagDeltaYSum += int64(event.DeltaY)
1509- return false1510+ now := time.Now()
1510- case cef2gtk.TouchpadGesturePhaseEnd:1511+ if !wv.inputDiagLastLog.IsZero() && now.Sub(wv.inputDiagLastLog) < time.Second {
1511- dx, dy := wv.finishTouchpadNavigationSwipe()1512+ wv.inputDiagMu.Unlock()
1512- switch chooseTouchpadNavigationAction(cfg, dx, dy, wv.CanGoBack(), wv.CanGoForward()) {1513+ return cef2gtk.ScrollForwardToCEF
1513- case touchpadNavigationBack:1514+ }
1514- if err := wv.GoBack(wv.ctx); err != nil {1515+ count := wv.inputDiagEvents
1515- logging.FromContext(wv.ctx).Warn().Err(err).Msg("touchpad back navigation failed")1516+ dx, dy := wv.inputDiagDX, wv.inputDiagDY
1516- return false1517+ deltaX, deltaY := wv.inputDiagDeltaXSum, wv.inputDiagDeltaYSum
1517- }1518+ wv.inputDiagEvents = 0
1518- return true1519+ wv.inputDiagDX = 0
1519- case touchpadNavigationForward:1520+ wv.inputDiagDY = 0
1520- if err := wv.GoForward(wv.ctx); err != nil {1521+ wv.inputDiagDeltaXSum = 0
1521- logging.FromContext(wv.ctx).Warn().Err(err).Msg("touchpad forward navigation failed")1522+ wv.inputDiagDeltaYSum = 0
1522- return false1523+ wv.inputDiagLastLog = now
1523- }1524+ wv.inputDiagMu.Unlock()
1524- return true
1525- default:
1526- return false
1527- }
1528- case cef2gtk.TouchpadGesturePhaseCancel:
1529- wv.resetTouchpadNavigationSwipe()
1530- return false
1531- default:
1532- return false
1533- }
1534-}
1535-
1536-func (wv *WebView) resetTouchpadNavigationSwipe() {
1537- wv.mu.Lock()
1538- wv.touchpadSwipeDX = 0
1539- wv.touchpadSwipeDY = 0
1540- wv.mu.Unlock()
1541-}
1542-
1543-func (wv *WebView) accumulateTouchpadNavigationSwipe(dx, dy float64) {
1544- wv.mu.Lock()
1545- wv.touchpadSwipeDX += dx
1546- wv.touchpadSwipeDY += dy
1547- wv.mu.Unlock()
1548-}
1549-
1550-func (wv *WebView) finishTouchpadNavigationSwipe() (float64, float64) {
1551- wv.mu.Lock()
1552- defer wv.mu.Unlock()
1553- dx, dy := wv.touchpadSwipeDX, wv.touchpadSwipeDY
1554- wv.touchpadSwipeDX = 0
1555- wv.touchpadSwipeDY = 0
1556- return dx, dy
1557-}
1558 1525
1559-func chooseTouchpadNavigationAction(cfg RuntimeInputConfig, dx, dy float64, canGoBack, canGoForward bool) touchpadNavigationAction {1526+ logging.FromContext(wv.ctx).Debug().
1560- if !cfg.TouchpadNavigationEnabled {1527+ Uint64("webview_id", uint64(wv.id)).
1561- return touchpadNavigationNone1528+ Int("events", count).
1562- }1529+ Str("phase", fmt.Sprint(event.Phase)).
1563- absDX, absDY := math.Abs(dx), math.Abs(dy)1530+ Str("unit", fmt.Sprint(event.Unit)).
1564- if absDX < cfg.TouchpadNavigationMinDelta {1531+ Bool("unit_known", event.UnitKnown).
1565- return touchpadNavigationNone1532+ Float64("dx_sum", dx).
1566- }1533+ Float64("dy_sum", dy).
1567- if absDY > absDX*cfg.TouchpadNavigationMaxVerticalRatio {1534+ Int64("cef_delta_x_sum", deltaX).
1568- return touchpadNavigationNone1535+ Int64("cef_delta_y_sum", deltaY).
1569- }1536+ Bool("nav_enabled", wv.inputConfig.TouchpadNavigationEnabled).
1570- if dx > 0 && canGoBack {1537+ Bool("can_go_back", wv.CanGoBack()).
1571- return touchpadNavigationBack1538+ Bool("can_go_forward", wv.CanGoForward()).
1572- }1539+ Msg("cef: touchpad horizontal scroll diagnostic")
1573- if dx < 0 && canGoForward {1540+
1574- return touchpadNavigationForward1541+ return cef2gtk.ScrollForwardToCEF
1542+}
1543+
1544+func (wv *WebView) handleNavigationSwipeAction(action cef2gtk.NavigationSwipeAction) {
1545+ logging.FromContext(wv.ctx).Debug().
1546+ Uint64("webview_id", uint64(wv.id)).
1547+ Str("action", fmt.Sprint(action)).
1548+ Bool("can_go_back", wv.CanGoBack()).
1549+ Bool("can_go_forward", wv.CanGoForward()).
1550+ Msg("cef: touchpad navigation swipe recognized")
1551+
1552+ switch action {
1553+ case cef2gtk.NavigationSwipeBack:
1554+ if err := wv.GoBack(wv.ctx); err != nil {
1555+ logging.FromContext(wv.ctx).Warn().Err(err).Msg("touchpad back navigation failed")
1556+ }
1557+ case cef2gtk.NavigationSwipeForward:
1558+ if err := wv.GoForward(wv.ctx); err != nil {
1559+ logging.FromContext(wv.ctx).Warn().Err(err).Msg("touchpad forward navigation failed")
1560+ }
1575 }1561 }
1576- return touchpadNavigationNone
1577 }1562 }
1578 1563
1579 func (wv *WebView) viewBridgeScale() float64 {1564 func (wv *WebView) viewBridgeScale() float64 {
M internal/infrastructure/cef/webview_input_test.go +9 -40
@@ -13,7 +13,7 @@ func TestWebViewBridgeInputOptions_LeavesTargetSelectionToAdapter(t *testing.T)
1313 nativeWidget: nativeWidget,
1414 inputConfig: RuntimeInputConfig{
1515 ScrollWheelMultiplier: 1.25,
16- ScrollTouchpadMultiplier: 0.35,
16+ ScrollPreciseMultiplier: 2.5,
1717 ScrollHorizontalMultiplier: 0.75,
1818 ScrollVerticalMultiplier: 1.5,
1919 ScrollMaxDelta: 120,
@@ -27,50 +27,19 @@ func TestWebViewBridgeInputOptions_LeavesTargetSelectionToAdapter(t *testing.T)
2727
2828 require.Zero(t, opts.Scale)
2929 require.InDelta(t, 1.25, opts.Scroll.WheelMultiplier, 0.001)
30- require.InDelta(t, 0.35, opts.Scroll.TouchpadMultiplier, 0.001)
30+ require.InDelta(t, 2.5, opts.Scroll.PreciseMultiplier, 0.001)
3131 require.InDelta(t, 0.75, opts.Scroll.HorizontalMultiplier, 0.001)
3232 require.InDelta(t, 1.5, opts.Scroll.VerticalMultiplier, 0.001)
3333 require.Equal(t, int32(120), opts.Scroll.MaxDelta)
3434 require.NotNil(t, opts.OnMiddleClick)
35- require.NotNil(t, opts.OnTouchpadSwipe)
35+ require.NotNil(t, opts.OnScroll)
36+ require.True(t, opts.NavigationSwipe.Enabled)
37+ require.InDelta(t, 80.0, opts.NavigationSwipe.MinDelta, 0.001)
38+ require.InDelta(t, 0.5, opts.NavigationSwipe.MaxVerticalRatio, 0.001)
39+ require.NotNil(t, opts.CanNavigateBack)
40+ require.NotNil(t, opts.CanNavigateForward)
41+ require.NotNil(t, opts.OnNavigateSwipe)
3642 require.NotNil(t, opts.SelectionText)
3743 require.NotNil(t, opts.OnClipboardShortcut)
3844 require.Same(t, nativeWidget, wv.nativeWidget)
3945 }
40-
41-func TestChooseTouchpadNavigationAction(t *testing.T) {
42- cfg := RuntimeInputConfig{
43- TouchpadNavigationEnabled: true,
44- TouchpadNavigationMinDelta: 80,
45- TouchpadNavigationMaxVerticalRatio: 0.5,
46- }
47-
48- tests := []struct {
49- name string
50- cfg *RuntimeInputConfig
51- dx, dy float64
52- canGoBack bool
53- canGoForward bool
54- want touchpadNavigationAction
55- }{
56- {name: "right swipe goes back", dx: 100, dy: 10, canGoBack: true, want: touchpadNavigationBack},
57- {name: "left swipe goes forward", dx: -100, dy: 10, canGoForward: true, want: touchpadNavigationForward},
58- {name: "below threshold ignored", dx: 40, dy: 0, canGoBack: true, want: touchpadNavigationNone},
59- {name: "disabled ignored", cfg: &RuntimeInputConfig{TouchpadNavigationEnabled: false, TouchpadNavigationMinDelta: 80, TouchpadNavigationMaxVerticalRatio: 0.5}, dx: 100, dy: 0, canGoBack: true, want: touchpadNavigationNone},
60- {name: "exact threshold goes back", dx: 80, dy: 0, canGoBack: true, want: touchpadNavigationBack},
61- {name: "vertical diagonal ignored", dx: 100, dy: 80, canGoBack: true, want: touchpadNavigationNone},
62- {name: "unavailable back ignored", dx: 100, dy: 0, canGoBack: false, want: touchpadNavigationNone},
63- {name: "unavailable forward ignored", dx: -100, dy: 0, canGoForward: false, want: touchpadNavigationNone},
64- }
65-
66- for _, tt := range tests {
67- t.Run(tt.name, func(t *testing.T) {
68- testCfg := cfg
69- if tt.cfg != nil {
70- testCfg = *tt.cfg
71- }
72- got := chooseTouchpadNavigationAction(testCfg, tt.dx, tt.dy, tt.canGoBack, tt.canGoForward)
73- require.Equal(t, tt.want, got)
74- })
75- }
76-}
@@ -13,7 +13,7 @@ func TestWebViewBridgeInputOptions_LeavesTargetSelectionToAdapter(t *testing.T)
13 nativeWidget: nativeWidget,13 nativeWidget: nativeWidget,
14 inputConfig: RuntimeInputConfig{14 inputConfig: RuntimeInputConfig{
15 ScrollWheelMultiplier: 1.25,15 ScrollWheelMultiplier: 1.25,
16- ScrollTouchpadMultiplier: 0.35,16+ ScrollPreciseMultiplier: 2.5,
17 ScrollHorizontalMultiplier: 0.75,17 ScrollHorizontalMultiplier: 0.75,
18 ScrollVerticalMultiplier: 1.5,18 ScrollVerticalMultiplier: 1.5,
19 ScrollMaxDelta: 120,19 ScrollMaxDelta: 120,
@@ -27,50 +27,19 @@ func TestWebViewBridgeInputOptions_LeavesTargetSelectionToAdapter(t *testing.T)
27 27
28 require.Zero(t, opts.Scale)28 require.Zero(t, opts.Scale)
29 require.InDelta(t, 1.25, opts.Scroll.WheelMultiplier, 0.001)29 require.InDelta(t, 1.25, opts.Scroll.WheelMultiplier, 0.001)
30- require.InDelta(t, 0.35, opts.Scroll.TouchpadMultiplier, 0.001)30+ require.InDelta(t, 2.5, opts.Scroll.PreciseMultiplier, 0.001)
31 require.InDelta(t, 0.75, opts.Scroll.HorizontalMultiplier, 0.001)31 require.InDelta(t, 0.75, opts.Scroll.HorizontalMultiplier, 0.001)
32 require.InDelta(t, 1.5, opts.Scroll.VerticalMultiplier, 0.001)32 require.InDelta(t, 1.5, opts.Scroll.VerticalMultiplier, 0.001)
33 require.Equal(t, int32(120), opts.Scroll.MaxDelta)33 require.Equal(t, int32(120), opts.Scroll.MaxDelta)
34 require.NotNil(t, opts.OnMiddleClick)34 require.NotNil(t, opts.OnMiddleClick)
35- require.NotNil(t, opts.OnTouchpadSwipe)35+ require.NotNil(t, opts.OnScroll)
36+ require.True(t, opts.NavigationSwipe.Enabled)
37+ require.InDelta(t, 80.0, opts.NavigationSwipe.MinDelta, 0.001)
38+ require.InDelta(t, 0.5, opts.NavigationSwipe.MaxVerticalRatio, 0.001)
39+ require.NotNil(t, opts.CanNavigateBack)
40+ require.NotNil(t, opts.CanNavigateForward)
41+ require.NotNil(t, opts.OnNavigateSwipe)
36 require.NotNil(t, opts.SelectionText)42 require.NotNil(t, opts.SelectionText)
37 require.NotNil(t, opts.OnClipboardShortcut)43 require.NotNil(t, opts.OnClipboardShortcut)
38 require.Same(t, nativeWidget, wv.nativeWidget)44 require.Same(t, nativeWidget, wv.nativeWidget)
39 }45 }
40-
41-func TestChooseTouchpadNavigationAction(t *testing.T) {
42- cfg := RuntimeInputConfig{
43- TouchpadNavigationEnabled: true,
44- TouchpadNavigationMinDelta: 80,
45- TouchpadNavigationMaxVerticalRatio: 0.5,
46- }
47-
48- tests := []struct {
49- name string
50- cfg *RuntimeInputConfig
51- dx, dy float64
52- canGoBack bool
53- canGoForward bool
54- want touchpadNavigationAction
55- }{
56- {name: "right swipe goes back", dx: 100, dy: 10, canGoBack: true, want: touchpadNavigationBack},
57- {name: "left swipe goes forward", dx: -100, dy: 10, canGoForward: true, want: touchpadNavigationForward},
58- {name: "below threshold ignored", dx: 40, dy: 0, canGoBack: true, want: touchpadNavigationNone},
59- {name: "disabled ignored", cfg: &RuntimeInputConfig{TouchpadNavigationEnabled: false, TouchpadNavigationMinDelta: 80, TouchpadNavigationMaxVerticalRatio: 0.5}, dx: 100, dy: 0, canGoBack: true, want: touchpadNavigationNone},
60- {name: "exact threshold goes back", dx: 80, dy: 0, canGoBack: true, want: touchpadNavigationBack},
61- {name: "vertical diagonal ignored", dx: 100, dy: 80, canGoBack: true, want: touchpadNavigationNone},
62- {name: "unavailable back ignored", dx: 100, dy: 0, canGoBack: false, want: touchpadNavigationNone},
63- {name: "unavailable forward ignored", dx: -100, dy: 0, canGoForward: false, want: touchpadNavigationNone},
64- }
65-
66- for _, tt := range tests {
67- t.Run(tt.name, func(t *testing.T) {
68- testCfg := cfg
69- if tt.cfg != nil {
70- testCfg = *tt.cfg
71- }
72- got := chooseTouchpadNavigationAction(testCfg, tt.dx, tt.dy, tt.canGoBack, tt.canGoForward)
73- require.Equal(t, tt.want, got)
74- })
75- }
76-}
M internal/infrastructure/config/defaults.go +11 -11
@@ -67,16 +67,16 @@ const (
6767 defaultUIScale = 1.0 // UI scale multiplier (1.0 = 100%, 1.2 = 120%)
6868
6969 // Performance defaults
70- defaultZoomCacheSize = 256 // domains to cache (~20KB memory)
71- defaultWebViewPoolPrewarmCount = 4 // WebViews to pre-create at startup
72- defaultCEFWindowlessFrameRate = 60 // static OSR frame rate when adaptive CEF pacing is disabled
73- defaultCEFWindowlessFrameRateMax = 240 // adaptive OSR frame-rate hard cap
74- defaultCEFScrollMultiplier = 1.0
75- defaultCEFScrollTouchpadMultiplier = 0.35
76- defaultCEFScrollMaxDelta = 0
77- defaultCEFTouchpadNavigation = true
78- defaultCEFTouchpadNavigationDelta = 80.0
79- defaultCEFTouchpadNavigationRatio = 0.5
70+ defaultZoomCacheSize = 256 // domains to cache (~20KB memory)
71+ defaultWebViewPoolPrewarmCount = 4 // WebViews to pre-create at startup
72+ defaultCEFWindowlessFrameRate = 60 // static OSR frame rate when adaptive CEF pacing is disabled
73+ defaultCEFWindowlessFrameRateMax = 240 // adaptive OSR frame-rate hard cap
74+ defaultCEFScrollMultiplier = 1.0
75+ defaultCEFScrollPreciseMultiplier = 2.5
76+ defaultCEFScrollMaxDelta = 0
77+ defaultCEFTouchpadNavigation = true
78+ defaultCEFTouchpadNavigationDelta = 15.0
79+ defaultCEFTouchpadNavigationRatio = 0.5
8080
8181 // Skia threading defaults (0 = unset, -1 = unset for GPU threads)
8282 defaultSkiaCPUPaintingThreads = 0
@@ -186,7 +186,7 @@ func DefaultConfig() *Config {
186186 EnableAudioHandler: true,
187187 Input: CEFInputConfig{
188188 ScrollWheelMultiplier: defaultCEFScrollMultiplier,
189- ScrollTouchpadMultiplier: defaultCEFScrollTouchpadMultiplier,
189+ ScrollPreciseMultiplier: defaultCEFScrollPreciseMultiplier,
190190 ScrollHorizontalMultiplier: defaultCEFScrollMultiplier,
191191 ScrollVerticalMultiplier: defaultCEFScrollMultiplier,
192192 ScrollMaxDelta: defaultCEFScrollMaxDelta,
@@ -67,16 +67,16 @@ const (
67 defaultUIScale = 1.0 // UI scale multiplier (1.0 = 100%, 1.2 = 120%)67 defaultUIScale = 1.0 // UI scale multiplier (1.0 = 100%, 1.2 = 120%)
68 68
69 // Performance defaults69 // Performance defaults
70- defaultZoomCacheSize = 256 // domains to cache (~20KB memory)70+ defaultZoomCacheSize = 256 // domains to cache (~20KB memory)
71- defaultWebViewPoolPrewarmCount = 4 // WebViews to pre-create at startup71+ defaultWebViewPoolPrewarmCount = 4 // WebViews to pre-create at startup
72- defaultCEFWindowlessFrameRate = 60 // static OSR frame rate when adaptive CEF pacing is disabled72+ defaultCEFWindowlessFrameRate = 60 // static OSR frame rate when adaptive CEF pacing is disabled
73- defaultCEFWindowlessFrameRateMax = 240 // adaptive OSR frame-rate hard cap73+ defaultCEFWindowlessFrameRateMax = 240 // adaptive OSR frame-rate hard cap
74- defaultCEFScrollMultiplier = 1.074+ defaultCEFScrollMultiplier = 1.0
75- defaultCEFScrollTouchpadMultiplier = 0.3575+ defaultCEFScrollPreciseMultiplier = 2.5
76- defaultCEFScrollMaxDelta = 076+ defaultCEFScrollMaxDelta = 0
77- defaultCEFTouchpadNavigation = true77+ defaultCEFTouchpadNavigation = true
78- defaultCEFTouchpadNavigationDelta = 80.078+ defaultCEFTouchpadNavigationDelta = 15.0
79- defaultCEFTouchpadNavigationRatio = 0.579+ defaultCEFTouchpadNavigationRatio = 0.5
80 80
81 // Skia threading defaults (0 = unset, -1 = unset for GPU threads)81 // Skia threading defaults (0 = unset, -1 = unset for GPU threads)
82 defaultSkiaCPUPaintingThreads = 082 defaultSkiaCPUPaintingThreads = 0
@@ -186,7 +186,7 @@ func DefaultConfig() *Config {
186 EnableAudioHandler: true,186 EnableAudioHandler: true,
187 Input: CEFInputConfig{187 Input: CEFInputConfig{
188 ScrollWheelMultiplier: defaultCEFScrollMultiplier,188 ScrollWheelMultiplier: defaultCEFScrollMultiplier,
189- ScrollTouchpadMultiplier: defaultCEFScrollTouchpadMultiplier,189+ ScrollPreciseMultiplier: defaultCEFScrollPreciseMultiplier,
190 ScrollHorizontalMultiplier: defaultCEFScrollMultiplier,190 ScrollHorizontalMultiplier: defaultCEFScrollMultiplier,
191 ScrollVerticalMultiplier: defaultCEFScrollMultiplier,191 ScrollVerticalMultiplier: defaultCEFScrollMultiplier,
192 ScrollMaxDelta: defaultCEFScrollMaxDelta,192 ScrollMaxDelta: defaultCEFScrollMaxDelta,
M internal/infrastructure/config/defaults_test.go +1 -1
@@ -26,7 +26,7 @@ func TestDefaultConfig_CoreDefaults(t *testing.T) {
2626 assert.True(t, cfg.Engine.CEF.EnableAudioHandler)
2727 assert.False(t, cfg.Engine.CEF.TraceHandlers)
2828 assert.InDelta(t, defaultCEFScrollMultiplier, cfg.Engine.CEF.Input.ScrollWheelMultiplier, 0.001)
29- assert.InDelta(t, defaultCEFScrollTouchpadMultiplier, cfg.Engine.CEF.Input.ScrollTouchpadMultiplier, 0.001)
29+ assert.InDelta(t, defaultCEFScrollPreciseMultiplier, cfg.Engine.CEF.Input.ScrollPreciseMultiplier, 0.001)
3030 assert.InDelta(t, defaultCEFScrollMultiplier, cfg.Engine.CEF.Input.ScrollHorizontalMultiplier, 0.001)
3131 assert.InDelta(t, defaultCEFScrollMultiplier, cfg.Engine.CEF.Input.ScrollVerticalMultiplier, 0.001)
3232 assert.Equal(t, int32(defaultCEFScrollMaxDelta), cfg.Engine.CEF.Input.ScrollMaxDelta)
@@ -26,7 +26,7 @@ func TestDefaultConfig_CoreDefaults(t *testing.T) {
26 assert.True(t, cfg.Engine.CEF.EnableAudioHandler)26 assert.True(t, cfg.Engine.CEF.EnableAudioHandler)
27 assert.False(t, cfg.Engine.CEF.TraceHandlers)27 assert.False(t, cfg.Engine.CEF.TraceHandlers)
28 assert.InDelta(t, defaultCEFScrollMultiplier, cfg.Engine.CEF.Input.ScrollWheelMultiplier, 0.001)28 assert.InDelta(t, defaultCEFScrollMultiplier, cfg.Engine.CEF.Input.ScrollWheelMultiplier, 0.001)
29- assert.InDelta(t, defaultCEFScrollTouchpadMultiplier, cfg.Engine.CEF.Input.ScrollTouchpadMultiplier, 0.001)29+ assert.InDelta(t, defaultCEFScrollPreciseMultiplier, cfg.Engine.CEF.Input.ScrollPreciseMultiplier, 0.001)
30 assert.InDelta(t, defaultCEFScrollMultiplier, cfg.Engine.CEF.Input.ScrollHorizontalMultiplier, 0.001)30 assert.InDelta(t, defaultCEFScrollMultiplier, cfg.Engine.CEF.Input.ScrollHorizontalMultiplier, 0.001)
31 assert.InDelta(t, defaultCEFScrollMultiplier, cfg.Engine.CEF.Input.ScrollVerticalMultiplier, 0.001)31 assert.InDelta(t, defaultCEFScrollMultiplier, cfg.Engine.CEF.Input.ScrollVerticalMultiplier, 0.001)
32 assert.Equal(t, int32(defaultCEFScrollMaxDelta), cfg.Engine.CEF.Input.ScrollMaxDelta)32 assert.Equal(t, int32(defaultCEFScrollMaxDelta), cfg.Engine.CEF.Input.ScrollMaxDelta)
M internal/infrastructure/config/engine.go +1 -1
@@ -121,7 +121,7 @@ type CEFEngineConfig struct {
121121 // CEFInputConfig holds CEF-specific input translation options.
122122 type CEFInputConfig struct {
123123 ScrollWheelMultiplier float64 `mapstructure:"scroll_wheel_multiplier" toml:"scroll_wheel_multiplier" yaml:"scroll_wheel_multiplier"` //nolint:lll // struct tags exceed lll limit
124- ScrollTouchpadMultiplier float64 `mapstructure:"scroll_touchpad_multiplier" toml:"scroll_touchpad_multiplier" yaml:"scroll_touchpad_multiplier"` //nolint:lll // struct tags exceed lll limit
124+ ScrollPreciseMultiplier float64 `mapstructure:"scroll_precise_multiplier" toml:"scroll_precise_multiplier" yaml:"scroll_precise_multiplier"` //nolint:lll // struct tags exceed lll limit
125125 ScrollHorizontalMultiplier float64 `mapstructure:"scroll_horizontal_multiplier" toml:"scroll_horizontal_multiplier" yaml:"scroll_horizontal_multiplier"` //nolint:lll // struct tags exceed lll limit
126126 ScrollVerticalMultiplier float64 `mapstructure:"scroll_vertical_multiplier" toml:"scroll_vertical_multiplier" yaml:"scroll_vertical_multiplier"` //nolint:lll // struct tags exceed lll limit
127127 ScrollMaxDelta int32 `mapstructure:"scroll_max_delta" toml:"scroll_max_delta" yaml:"scroll_max_delta"` //nolint:lll // struct tags exceed lll limit
@@ -121,7 +121,7 @@ type CEFEngineConfig struct {
121 // CEFInputConfig holds CEF-specific input translation options.121 // CEFInputConfig holds CEF-specific input translation options.
122 type CEFInputConfig struct {122 type CEFInputConfig struct {
123 ScrollWheelMultiplier float64 `mapstructure:"scroll_wheel_multiplier" toml:"scroll_wheel_multiplier" yaml:"scroll_wheel_multiplier"` //nolint:lll // struct tags exceed lll limit123 ScrollWheelMultiplier float64 `mapstructure:"scroll_wheel_multiplier" toml:"scroll_wheel_multiplier" yaml:"scroll_wheel_multiplier"` //nolint:lll // struct tags exceed lll limit
124- ScrollTouchpadMultiplier float64 `mapstructure:"scroll_touchpad_multiplier" toml:"scroll_touchpad_multiplier" yaml:"scroll_touchpad_multiplier"` //nolint:lll // struct tags exceed lll limit124+ ScrollPreciseMultiplier float64 `mapstructure:"scroll_precise_multiplier" toml:"scroll_precise_multiplier" yaml:"scroll_precise_multiplier"` //nolint:lll // struct tags exceed lll limit
125 ScrollHorizontalMultiplier float64 `mapstructure:"scroll_horizontal_multiplier" toml:"scroll_horizontal_multiplier" yaml:"scroll_horizontal_multiplier"` //nolint:lll // struct tags exceed lll limit125 ScrollHorizontalMultiplier float64 `mapstructure:"scroll_horizontal_multiplier" toml:"scroll_horizontal_multiplier" yaml:"scroll_horizontal_multiplier"` //nolint:lll // struct tags exceed lll limit
126 ScrollVerticalMultiplier float64 `mapstructure:"scroll_vertical_multiplier" toml:"scroll_vertical_multiplier" yaml:"scroll_vertical_multiplier"` //nolint:lll // struct tags exceed lll limit126 ScrollVerticalMultiplier float64 `mapstructure:"scroll_vertical_multiplier" toml:"scroll_vertical_multiplier" yaml:"scroll_vertical_multiplier"` //nolint:lll // struct tags exceed lll limit
127 ScrollMaxDelta int32 `mapstructure:"scroll_max_delta" toml:"scroll_max_delta" yaml:"scroll_max_delta"` //nolint:lll // struct tags exceed lll limit127 ScrollMaxDelta int32 `mapstructure:"scroll_max_delta" toml:"scroll_max_delta" yaml:"scroll_max_delta"` //nolint:lll // struct tags exceed lll limit
M internal/infrastructure/config/loader.go +1 -1
@@ -783,7 +783,7 @@ func (m *Manager) setEngineDefaults(defaults *Config) {
783783
784784 func (m *Manager) setCEFInputDefaults(input CEFInputConfig) {
785785 m.viper.SetDefault("engine.cef.input.scroll_wheel_multiplier", input.ScrollWheelMultiplier)
786- m.viper.SetDefault("engine.cef.input.scroll_touchpad_multiplier", input.ScrollTouchpadMultiplier)
786+ m.viper.SetDefault("engine.cef.input.scroll_precise_multiplier", input.ScrollPreciseMultiplier)
787787 m.viper.SetDefault("engine.cef.input.scroll_horizontal_multiplier", input.ScrollHorizontalMultiplier)
788788 m.viper.SetDefault("engine.cef.input.scroll_vertical_multiplier", input.ScrollVerticalMultiplier)
789789 m.viper.SetDefault("engine.cef.input.scroll_max_delta", input.ScrollMaxDelta)
@@ -783,7 +783,7 @@ func (m *Manager) setEngineDefaults(defaults *Config) {
783 783
784 func (m *Manager) setCEFInputDefaults(input CEFInputConfig) {784 func (m *Manager) setCEFInputDefaults(input CEFInputConfig) {
785 m.viper.SetDefault("engine.cef.input.scroll_wheel_multiplier", input.ScrollWheelMultiplier)785 m.viper.SetDefault("engine.cef.input.scroll_wheel_multiplier", input.ScrollWheelMultiplier)
786- m.viper.SetDefault("engine.cef.input.scroll_touchpad_multiplier", input.ScrollTouchpadMultiplier)786+ m.viper.SetDefault("engine.cef.input.scroll_precise_multiplier", input.ScrollPreciseMultiplier)
787 m.viper.SetDefault("engine.cef.input.scroll_horizontal_multiplier", input.ScrollHorizontalMultiplier)787 m.viper.SetDefault("engine.cef.input.scroll_horizontal_multiplier", input.ScrollHorizontalMultiplier)
788 m.viper.SetDefault("engine.cef.input.scroll_vertical_multiplier", input.ScrollVerticalMultiplier)788 m.viper.SetDefault("engine.cef.input.scroll_vertical_multiplier", input.ScrollVerticalMultiplier)
789 m.viper.SetDefault("engine.cef.input.scroll_max_delta", input.ScrollMaxDelta)789 m.viper.SetDefault("engine.cef.input.scroll_max_delta", input.ScrollMaxDelta)
M internal/infrastructure/config/loader_test.go +1 -1
@@ -18,7 +18,7 @@ func TestSetEngineDefaults(t *testing.T) {
1818 assert.Equal(t, 4, mgr.viper.GetInt("engine.pool_prewarm_count"))
1919 assert.Equal(t, 256, mgr.viper.GetInt("engine.zoom_cache_size"))
2020 assert.InDelta(t, defaultCEFScrollMultiplier, mgr.viper.GetFloat64("engine.cef.input.scroll_wheel_multiplier"), 0.001)
21- assert.InDelta(t, defaultCEFScrollTouchpadMultiplier, mgr.viper.GetFloat64("engine.cef.input.scroll_touchpad_multiplier"), 0.001)
21+ assert.InDelta(t, defaultCEFScrollPreciseMultiplier, mgr.viper.GetFloat64("engine.cef.input.scroll_precise_multiplier"), 0.001)
2222 assert.InDelta(t, defaultCEFScrollMultiplier, mgr.viper.GetFloat64("engine.cef.input.scroll_horizontal_multiplier"), 0.001)
2323 assert.InDelta(t, defaultCEFScrollMultiplier, mgr.viper.GetFloat64("engine.cef.input.scroll_vertical_multiplier"), 0.001)
2424 assert.Equal(t, defaultCEFScrollMaxDelta, mgr.viper.GetInt("engine.cef.input.scroll_max_delta"))
@@ -18,7 +18,7 @@ func TestSetEngineDefaults(t *testing.T) {
18 assert.Equal(t, 4, mgr.viper.GetInt("engine.pool_prewarm_count"))18 assert.Equal(t, 4, mgr.viper.GetInt("engine.pool_prewarm_count"))
19 assert.Equal(t, 256, mgr.viper.GetInt("engine.zoom_cache_size"))19 assert.Equal(t, 256, mgr.viper.GetInt("engine.zoom_cache_size"))
20 assert.InDelta(t, defaultCEFScrollMultiplier, mgr.viper.GetFloat64("engine.cef.input.scroll_wheel_multiplier"), 0.001)20 assert.InDelta(t, defaultCEFScrollMultiplier, mgr.viper.GetFloat64("engine.cef.input.scroll_wheel_multiplier"), 0.001)
21- assert.InDelta(t, defaultCEFScrollTouchpadMultiplier, mgr.viper.GetFloat64("engine.cef.input.scroll_touchpad_multiplier"), 0.001)21+ assert.InDelta(t, defaultCEFScrollPreciseMultiplier, mgr.viper.GetFloat64("engine.cef.input.scroll_precise_multiplier"), 0.001)
22 assert.InDelta(t, defaultCEFScrollMultiplier, mgr.viper.GetFloat64("engine.cef.input.scroll_horizontal_multiplier"), 0.001)22 assert.InDelta(t, defaultCEFScrollMultiplier, mgr.viper.GetFloat64("engine.cef.input.scroll_horizontal_multiplier"), 0.001)
23 assert.InDelta(t, defaultCEFScrollMultiplier, mgr.viper.GetFloat64("engine.cef.input.scroll_vertical_multiplier"), 0.001)23 assert.InDelta(t, defaultCEFScrollMultiplier, mgr.viper.GetFloat64("engine.cef.input.scroll_vertical_multiplier"), 0.001)
24 assert.Equal(t, defaultCEFScrollMaxDelta, mgr.viper.GetInt("engine.cef.input.scroll_max_delta"))24 assert.Equal(t, defaultCEFScrollMaxDelta, mgr.viper.GetInt("engine.cef.input.scroll_max_delta"))
M internal/infrastructure/config/schema_provider.go +3 -3
@@ -971,10 +971,10 @@ func (*SchemaProvider) getPerformanceKeys(defaults *Config) []entity.ConfigKeyIn
971971 Section: SectionPerformance,
972972 },
973973 {
974- Key: "engine.cef.input.scroll_touchpad_multiplier",
974+ Key: "engine.cef.input.scroll_precise_multiplier",
975975 Type: "float64",
976- Default: fmt.Sprintf("%.2f", defaults.Engine.CEF.Input.ScrollTouchpadMultiplier),
977- Description: "CEF touchpad/smooth scroll delta multiplier",
976+ Default: fmt.Sprintf("%.2f", defaults.Engine.CEF.Input.ScrollPreciseMultiplier),
977+ Description: "CEF precise/surface scroll delta multiplier",
978978 Range: ">0",
979979 Section: SectionPerformance,
980980 },
@@ -971,10 +971,10 @@ func (*SchemaProvider) getPerformanceKeys(defaults *Config) []entity.ConfigKeyIn
971 Section: SectionPerformance,971 Section: SectionPerformance,
972 },972 },
973 {973 {
974- Key: "engine.cef.input.scroll_touchpad_multiplier",974+ Key: "engine.cef.input.scroll_precise_multiplier",
975 Type: "float64",975 Type: "float64",
976- Default: fmt.Sprintf("%.2f", defaults.Engine.CEF.Input.ScrollTouchpadMultiplier),976+ Default: fmt.Sprintf("%.2f", defaults.Engine.CEF.Input.ScrollPreciseMultiplier),
977- Description: "CEF touchpad/smooth scroll delta multiplier",977+ Description: "CEF precise/surface scroll delta multiplier",
978 Range: ">0",978 Range: ">0",
979 Section: SectionPerformance,979 Section: SectionPerformance,
980 },980 },
M internal/infrastructure/config/validation.go +2 -2
@@ -367,8 +367,8 @@ func validateCEF(config *Config) []string {
367367 if invalidPositiveFloat(input.ScrollWheelMultiplier) {
368368 validationErrors = append(validationErrors, "engine.cef.input.scroll_wheel_multiplier must be > 0")
369369 }
370- if invalidPositiveFloat(input.ScrollTouchpadMultiplier) {
371- validationErrors = append(validationErrors, "engine.cef.input.scroll_touchpad_multiplier must be > 0")
370+ if invalidPositiveFloat(input.ScrollPreciseMultiplier) {
371+ validationErrors = append(validationErrors, "engine.cef.input.scroll_precise_multiplier must be > 0")
372372 }
373373 if invalidPositiveFloat(input.ScrollHorizontalMultiplier) {
374374 validationErrors = append(validationErrors, "engine.cef.input.scroll_horizontal_multiplier must be > 0")
@@ -367,8 +367,8 @@ func validateCEF(config *Config) []string {
367 if invalidPositiveFloat(input.ScrollWheelMultiplier) {367 if invalidPositiveFloat(input.ScrollWheelMultiplier) {
368 validationErrors = append(validationErrors, "engine.cef.input.scroll_wheel_multiplier must be > 0")368 validationErrors = append(validationErrors, "engine.cef.input.scroll_wheel_multiplier must be > 0")
369 }369 }
370- if invalidPositiveFloat(input.ScrollTouchpadMultiplier) {370+ if invalidPositiveFloat(input.ScrollPreciseMultiplier) {
371- validationErrors = append(validationErrors, "engine.cef.input.scroll_touchpad_multiplier must be > 0")371+ validationErrors = append(validationErrors, "engine.cef.input.scroll_precise_multiplier must be > 0")
372 }372 }
373 if invalidPositiveFloat(input.ScrollHorizontalMultiplier) {373 if invalidPositiveFloat(input.ScrollHorizontalMultiplier) {
374 validationErrors = append(validationErrors, "engine.cef.input.scroll_horizontal_multiplier must be > 0")374 validationErrors = append(validationErrors, "engine.cef.input.scroll_horizontal_multiplier must be > 0")
M internal/infrastructure/config/validation_test.go +3 -3
@@ -81,12 +81,12 @@ func TestValidateConfig_CEFConfig(t *testing.T) {
8181 wantText: "engine.cef.input.scroll_wheel_multiplier",
8282 },
8383 {
84- name: "negative touchpad multiplier",
84+ name: "negative precise multiplier",
8585 mutate: func(cfg *Config) {
86- cfg.Engine.CEF.Input.ScrollTouchpadMultiplier = -0.1
86+ cfg.Engine.CEF.Input.ScrollPreciseMultiplier = -0.1
8787 },
8888 wantErr: true,
89- wantText: "engine.cef.input.scroll_touchpad_multiplier",
89+ wantText: "engine.cef.input.scroll_precise_multiplier",
9090 },
9191 {
9292 name: "zero horizontal multiplier",
@@ -81,12 +81,12 @@ func TestValidateConfig_CEFConfig(t *testing.T) {
81 wantText: "engine.cef.input.scroll_wheel_multiplier",81 wantText: "engine.cef.input.scroll_wheel_multiplier",
82 },82 },
83 {83 {
84- name: "negative touchpad multiplier",84+ name: "negative precise multiplier",
85 mutate: func(cfg *Config) {85 mutate: func(cfg *Config) {
86- cfg.Engine.CEF.Input.ScrollTouchpadMultiplier = -0.186+ cfg.Engine.CEF.Input.ScrollPreciseMultiplier = -0.1
87 },87 },
88 wantErr: true,88 wantErr: true,
89- wantText: "engine.cef.input.scroll_touchpad_multiplier",89+ wantText: "engine.cef.input.scroll_precise_multiplier",
90 },90 },
91 {91 {
92 name: "zero horizontal multiplier",92 name: "zero horizontal multiplier",

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