detached 01d791b

fix(cef): restore touchpad scroll speed

commit
01d791b7706182a8ff5fef243552a6aaf5267831
author
brice · · 2026-06-17 21:15 UTC
parent
7f05154
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
7 files · +215 -8
M docs/config/index.md +5 -3
@@ -267,15 +267,17 @@ When Noctalia rewrites the watched file, Dumber reapplies the same resolved them
267267 | `engine.cef.windowless_frame_rate` | int32 | `0` | >= 0 | Explicit CEF OSR FPS cap; 0 uses adaptive mode if enabled, otherwise CEF default |
268268 | `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 |
269269 | `engine.cef.input.scroll_wheel_multiplier` | float | `1.0` | > 0 | Mouse wheel scroll sensitivity multiplier |
270-| `engine.cef.input.scroll_precise_multiplier` | float | `2.5` | > 0 | Precise/surface scroll sensitivity multiplier |
270+| `engine.cef.input.scroll_precise_multiplier` | float | `2.5` | > 0 | Precise/surface scroll sensitivity multiplier for touchpads and high-resolution wheels |
271271 | `engine.cef.input.scroll_horizontal_multiplier` | float | `1.0` | > 0 | Horizontal scroll sensitivity multiplier |
272-| `engine.cef.input.scroll_vertical_multiplier` | float | `1.0` | > 0 | Vertical scroll sensitivity multiplier |
272+| `engine.cef.input.scroll_vertical_multiplier` | float | `1.0` | > 0 | Vertical scroll sensitivity multiplier; touchpad vertical speed uses this together with `scroll_precise_multiplier` |
273273 | `engine.cef.input.scroll_max_delta` | int32 | `0` | >= 0 | Maximum absolute scroll delta after scaling; 0 disables clamping |
274274 | `engine.cef.input.touchpad_navigation_enabled` | bool | `true` | - | Enable two-finger touchpad swipe back/forward navigation |
275275 | `engine.cef.input.touchpad_navigation_min_delta` | float | `200.0` | > 0 | Minimum accumulated horizontal swipe delta required for navigation |
276-| `engine.cef.input.touchpad_navigation_max_vertical_ratio` | float | `0.5` | > 0 | Maximum vertical-to-horizontal delta ratio allowed for navigation swipes |
276+| `engine.cef.input.touchpad_navigation_max_vertical_ratio` | float | `0.5` | > 0 | Maximum vertical-to-horizontal delta ratio allowed for navigation swipes; does not affect vertical scroll speed |
277277 | `default_ui_scale` | float | `1.0` | > 0 | GTK widget UI scale (1.0=100%, 2.0=200%) |
278278
279+`engine.cef.input.scroll_precise_multiplier` controls touchpad/high-resolution wheel scroll speed, and `engine.cef.input.scroll_vertical_multiplier` applies an additional vertical-only scale. `engine.cef.input.touchpad_navigation_max_vertical_ratio` only filters horizontal back/forward swipe recognition; it does not tune vertical scroll speed.
280+
279281 `engine.cef.input.touchpad_navigation_min_delta` uses raw GTK touchpad surface units for back/forward gestures. The default `200.0` matches WebKit-style commit distance to reduce accidental navigation; raise or lower it in `config.toml` to tune gesture sensitivity.
280282 | `default_webpage_zoom` | float | `1.2` | > 0 | Default page zoom (1.0=100%, 1.2=120%) |
281283
@@ -267,15 +267,17 @@ When Noctalia rewrites the watched file, Dumber reapplies the same resolved them
267 | `engine.cef.windowless_frame_rate` | int32 | `0` | >= 0 | Explicit CEF OSR FPS cap; 0 uses adaptive mode if enabled, otherwise CEF default |267 | `engine.cef.windowless_frame_rate` | int32 | `0` | >= 0 | Explicit CEF OSR FPS cap; 0 uses adaptive mode if enabled, otherwise CEF default |
268 | `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 |268 | `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 |
269 | `engine.cef.input.scroll_wheel_multiplier` | float | `1.0` | > 0 | Mouse wheel scroll sensitivity multiplier |269 | `engine.cef.input.scroll_wheel_multiplier` | float | `1.0` | > 0 | Mouse wheel scroll sensitivity multiplier |
270-| `engine.cef.input.scroll_precise_multiplier` | float | `2.5` | > 0 | Precise/surface scroll sensitivity multiplier |270+| `engine.cef.input.scroll_precise_multiplier` | float | `2.5` | > 0 | Precise/surface scroll sensitivity multiplier for touchpads and high-resolution wheels |
271 | `engine.cef.input.scroll_horizontal_multiplier` | float | `1.0` | > 0 | Horizontal scroll sensitivity multiplier |271 | `engine.cef.input.scroll_horizontal_multiplier` | float | `1.0` | > 0 | Horizontal scroll sensitivity multiplier |
272-| `engine.cef.input.scroll_vertical_multiplier` | float | `1.0` | > 0 | Vertical scroll sensitivity multiplier |272+| `engine.cef.input.scroll_vertical_multiplier` | float | `1.0` | > 0 | Vertical scroll sensitivity multiplier; touchpad vertical speed uses this together with `scroll_precise_multiplier` |
273 | `engine.cef.input.scroll_max_delta` | int32 | `0` | >= 0 | Maximum absolute scroll delta after scaling; 0 disables clamping |273 | `engine.cef.input.scroll_max_delta` | int32 | `0` | >= 0 | Maximum absolute scroll delta after scaling; 0 disables clamping |
274 | `engine.cef.input.touchpad_navigation_enabled` | bool | `true` | - | Enable two-finger touchpad swipe back/forward navigation |274 | `engine.cef.input.touchpad_navigation_enabled` | bool | `true` | - | Enable two-finger touchpad swipe back/forward navigation |
275 | `engine.cef.input.touchpad_navigation_min_delta` | float | `200.0` | > 0 | Minimum accumulated horizontal swipe delta required for navigation |275 | `engine.cef.input.touchpad_navigation_min_delta` | float | `200.0` | > 0 | Minimum accumulated horizontal swipe delta required for navigation |
276-| `engine.cef.input.touchpad_navigation_max_vertical_ratio` | float | `0.5` | > 0 | Maximum vertical-to-horizontal delta ratio allowed for navigation swipes |276+| `engine.cef.input.touchpad_navigation_max_vertical_ratio` | float | `0.5` | > 0 | Maximum vertical-to-horizontal delta ratio allowed for navigation swipes; does not affect vertical scroll speed |
277 | `default_ui_scale` | float | `1.0` | > 0 | GTK widget UI scale (1.0=100%, 2.0=200%) |277 | `default_ui_scale` | float | `1.0` | > 0 | GTK widget UI scale (1.0=100%, 2.0=200%) |
278 278
279+`engine.cef.input.scroll_precise_multiplier` controls touchpad/high-resolution wheel scroll speed, and `engine.cef.input.scroll_vertical_multiplier` applies an additional vertical-only scale. `engine.cef.input.touchpad_navigation_max_vertical_ratio` only filters horizontal back/forward swipe recognition; it does not tune vertical scroll speed.
280+
279 `engine.cef.input.touchpad_navigation_min_delta` uses raw GTK touchpad surface units for back/forward gestures. The default `200.0` matches WebKit-style commit distance to reduce accidental navigation; raise or lower it in `config.toml` to tune gesture sensitivity.281 `engine.cef.input.touchpad_navigation_min_delta` uses raw GTK touchpad surface units for back/forward gestures. The default `200.0` matches WebKit-style commit distance to reduce accidental navigation; raise or lower it in `config.toml` to tune gesture sensitivity.
280 | `default_webpage_zoom` | float | `1.2` | > 0 | Default page zoom (1.0=100%, 1.2=120%) |282 | `default_webpage_zoom` | float | `1.2` | > 0 | Default page zoom (1.0=100%, 1.2=120%) |
281 283
M docs/reference/configuration.md +2 -0
@@ -78,6 +78,8 @@
7878 | `engine.cef.input.touchpad_navigation_max_vertical_ratio` | float | `0.5` | > 0 |
7979 | `default_ui_scale` | float | `1.0` | > 0 |
8080
81+Touchpad vertical scroll speed is controlled by `engine.cef.input.scroll_precise_multiplier` and the additional axis-specific `engine.cef.input.scroll_vertical_multiplier`. `engine.cef.input.touchpad_navigation_max_vertical_ratio` only filters horizontal back/forward swipe recognition; it does not tune vertical scroll speed.
82+
8183 `engine.cef.input.touchpad_navigation_min_delta` is measured in raw GTK touchpad surface units. The default `200.0` matches WebKit-style commit distance to reduce accidental back/forward navigation and can be overridden in `config.toml`.
8284 | `default_webpage_zoom` | float | `1.2` | > 0 |
8385 | `workspace.new_pane_url` | string | `about:blank` | |
@@ -78,6 +78,8 @@
78 | `engine.cef.input.touchpad_navigation_max_vertical_ratio` | float | `0.5` | > 0 |78 | `engine.cef.input.touchpad_navigation_max_vertical_ratio` | float | `0.5` | > 0 |
79 | `default_ui_scale` | float | `1.0` | > 0 |79 | `default_ui_scale` | float | `1.0` | > 0 |
80 80
81+Touchpad vertical scroll speed is controlled by `engine.cef.input.scroll_precise_multiplier` and the additional axis-specific `engine.cef.input.scroll_vertical_multiplier`. `engine.cef.input.touchpad_navigation_max_vertical_ratio` only filters horizontal back/forward swipe recognition; it does not tune vertical scroll speed.
82+
81 `engine.cef.input.touchpad_navigation_min_delta` is measured in raw GTK touchpad surface units. The default `200.0` matches WebKit-style commit distance to reduce accidental back/forward navigation and can be overridden in `config.toml`.83 `engine.cef.input.touchpad_navigation_min_delta` is measured in raw GTK touchpad surface units. The default `200.0` matches WebKit-style commit distance to reduce accidental back/forward navigation and can be overridden in `config.toml`.
82 | `default_webpage_zoom` | float | `1.2` | > 0 |84 | `default_webpage_zoom` | float | `1.2` | > 0 |
83 | `workspace.new_pane_url` | string | `about:blank` | |85 | `workspace.new_pane_url` | string | `about:blank` | |
M internal/infrastructure/cef/webview.go +7 -2
@@ -1497,7 +1497,7 @@ func (wv *WebView) bridgeInputOptions() cef2gtk.InputOptions {
14971497 }
14981498
14991499 func (wv *WebView) handleScrollInputDiagnostic(event cef2gtk.ScrollEvent) cef2gtk.ScrollDecision {
1500- if event.Phase != cef2gtk.ScrollPhaseUpdate || math.Abs(event.DX) == 0 {
1500+ if event.Phase != cef2gtk.ScrollPhaseUpdate || (math.Abs(event.DX) == 0 && math.Abs(event.DY) == 0) {
15011501 return cef2gtk.ScrollForwardToCEF
15021502 }
15031503
@@ -1533,12 +1533,17 @@ func (wv *WebView) handleScrollInputDiagnostic(event cef2gtk.ScrollEvent) cef2gt
15331533 Float64("dy_sum", dy).
15341534 Int64("cef_delta_x_sum", deltaX).
15351535 Int64("cef_delta_y_sum", deltaY).
1536+ Float64("scroll_wheel_multiplier", wv.inputConfig.ScrollWheelMultiplier).
1537+ Float64("scroll_precise_multiplier", wv.inputConfig.ScrollPreciseMultiplier).
1538+ Float64("scroll_horizontal_multiplier", wv.inputConfig.ScrollHorizontalMultiplier).
1539+ Float64("scroll_vertical_multiplier", wv.inputConfig.ScrollVerticalMultiplier).
1540+ Int32("scroll_max_delta", wv.inputConfig.ScrollMaxDelta).
15361541 Bool("nav_enabled", wv.inputConfig.TouchpadNavigationEnabled).
15371542 Float64("nav_min_delta", wv.inputConfig.TouchpadNavigationMinDelta).
15381543 Float64("nav_max_vertical_ratio", wv.inputConfig.TouchpadNavigationMaxVerticalRatio).
15391544 Bool("can_go_back", wv.CanGoBack()).
15401545 Bool("can_go_forward", wv.CanGoForward()).
1541- Msg("cef: touchpad horizontal scroll diagnostic")
1546+ Msg("cef: touchpad scroll diagnostic")
15421547
15431548 return cef2gtk.ScrollForwardToCEF
15441549 }
@@ -1497,7 +1497,7 @@ func (wv *WebView) bridgeInputOptions() cef2gtk.InputOptions {
1497 }1497 }
1498 1498
1499 func (wv *WebView) handleScrollInputDiagnostic(event cef2gtk.ScrollEvent) cef2gtk.ScrollDecision {1499 func (wv *WebView) handleScrollInputDiagnostic(event cef2gtk.ScrollEvent) cef2gtk.ScrollDecision {
1500- if event.Phase != cef2gtk.ScrollPhaseUpdate || math.Abs(event.DX) == 0 {1500+ if event.Phase != cef2gtk.ScrollPhaseUpdate || (math.Abs(event.DX) == 0 && math.Abs(event.DY) == 0) {
1501 return cef2gtk.ScrollForwardToCEF1501 return cef2gtk.ScrollForwardToCEF
1502 }1502 }
1503 1503
@@ -1533,12 +1533,17 @@ func (wv *WebView) handleScrollInputDiagnostic(event cef2gtk.ScrollEvent) cef2gt
1533 Float64("dy_sum", dy).1533 Float64("dy_sum", dy).
1534 Int64("cef_delta_x_sum", deltaX).1534 Int64("cef_delta_x_sum", deltaX).
1535 Int64("cef_delta_y_sum", deltaY).1535 Int64("cef_delta_y_sum", deltaY).
1536+ Float64("scroll_wheel_multiplier", wv.inputConfig.ScrollWheelMultiplier).
1537+ Float64("scroll_precise_multiplier", wv.inputConfig.ScrollPreciseMultiplier).
1538+ Float64("scroll_horizontal_multiplier", wv.inputConfig.ScrollHorizontalMultiplier).
1539+ Float64("scroll_vertical_multiplier", wv.inputConfig.ScrollVerticalMultiplier).
1540+ Int32("scroll_max_delta", wv.inputConfig.ScrollMaxDelta).
1536 Bool("nav_enabled", wv.inputConfig.TouchpadNavigationEnabled).1541 Bool("nav_enabled", wv.inputConfig.TouchpadNavigationEnabled).
1537 Float64("nav_min_delta", wv.inputConfig.TouchpadNavigationMinDelta).1542 Float64("nav_min_delta", wv.inputConfig.TouchpadNavigationMinDelta).
1538 Float64("nav_max_vertical_ratio", wv.inputConfig.TouchpadNavigationMaxVerticalRatio).1543 Float64("nav_max_vertical_ratio", wv.inputConfig.TouchpadNavigationMaxVerticalRatio).
1539 Bool("can_go_back", wv.CanGoBack()).1544 Bool("can_go_back", wv.CanGoBack()).
1540 Bool("can_go_forward", wv.CanGoForward()).1545 Bool("can_go_forward", wv.CanGoForward()).
1541- Msg("cef: touchpad horizontal scroll diagnostic")1546+ Msg("cef: touchpad scroll diagnostic")
1542 1547
1543 return cef2gtk.ScrollForwardToCEF1548 return cef2gtk.ScrollForwardToCEF
1544 }1549 }
M internal/infrastructure/config/loader_actions_test.go +27 -0
@@ -9,6 +9,33 @@ import (
99 "github.com/stretchr/testify/require"
1010 )
1111
12+func TestManagerLoad_UpgradesObsoleteCEFPreciseScrollDefaultInMemory(t *testing.T) {
13+ home := t.TempDir()
14+ t.Setenv("HOME", home)
15+ t.Setenv("XDG_CONFIG_HOME", filepath.Join(home, ".config"))
16+ t.Setenv("XDG_DATA_HOME", filepath.Join(home, ".local", "share"))
17+ t.Setenv("XDG_STATE_HOME", filepath.Join(home, ".local", "state"))
18+ t.Setenv("XDG_CACHE_HOME", filepath.Join(home, ".cache"))
19+
20+ configFile, err := GetConfigFile()
21+ require.NoError(t, err)
22+ require.NoError(t, os.MkdirAll(filepath.Dir(configFile), 0o755))
23+ require.NoError(t, os.WriteFile(configFile, []byte(`
24+[engine.cef.input]
25+scroll_precise_multiplier = 0.35
26+touchpad_navigation_max_vertical_ratio = 2.0
27+`), 0o644))
28+
29+ mgr, err := NewManager()
30+ require.NoError(t, err)
31+ require.NoError(t, mgr.Load())
32+
33+ cfg := mgr.Get()
34+ require.NotNil(t, cfg)
35+ assert.InDelta(t, defaultCEFScrollPreciseMultiplier, cfg.Engine.CEF.Input.ScrollPreciseMultiplier, 0.001)
36+ assert.InDelta(t, 2.0, cfg.Engine.CEF.Input.TouchpadNavigationMaxVerticalRatio, 0.001)
37+}
38+
1239 func TestManagerLoad_MergesMissingPaneModeActionsInMemory(t *testing.T) {
1340 home := t.TempDir()
1441 t.Setenv("HOME", home)
@@ -9,6 +9,33 @@ import (
9 "github.com/stretchr/testify/require"9 "github.com/stretchr/testify/require"
10 )10 )
11 11
12+func TestManagerLoad_UpgradesObsoleteCEFPreciseScrollDefaultInMemory(t *testing.T) {
13+ home := t.TempDir()
14+ t.Setenv("HOME", home)
15+ t.Setenv("XDG_CONFIG_HOME", filepath.Join(home, ".config"))
16+ t.Setenv("XDG_DATA_HOME", filepath.Join(home, ".local", "share"))
17+ t.Setenv("XDG_STATE_HOME", filepath.Join(home, ".local", "state"))
18+ t.Setenv("XDG_CACHE_HOME", filepath.Join(home, ".cache"))
19+
20+ configFile, err := GetConfigFile()
21+ require.NoError(t, err)
22+ require.NoError(t, os.MkdirAll(filepath.Dir(configFile), 0o755))
23+ require.NoError(t, os.WriteFile(configFile, []byte(`
24+[engine.cef.input]
25+scroll_precise_multiplier = 0.35
26+touchpad_navigation_max_vertical_ratio = 2.0
27+`), 0o644))
28+
29+ mgr, err := NewManager()
30+ require.NoError(t, err)
31+ require.NoError(t, mgr.Load())
32+
33+ cfg := mgr.Get()
34+ require.NotNil(t, cfg)
35+ assert.InDelta(t, defaultCEFScrollPreciseMultiplier, cfg.Engine.CEF.Input.ScrollPreciseMultiplier, 0.001)
36+ assert.InDelta(t, 2.0, cfg.Engine.CEF.Input.TouchpadNavigationMaxVerticalRatio, 0.001)
37+}
38+
12 func TestManagerLoad_MergesMissingPaneModeActionsInMemory(t *testing.T) {39 func TestManagerLoad_MergesMissingPaneModeActionsInMemory(t *testing.T) {
13 home := t.TempDir()40 home := t.TempDir()
14 t.Setenv("HOME", home)41 t.Setenv("HOME", home)
M internal/infrastructure/config/schema_provider.go +3 -3
@@ -1012,7 +1012,7 @@ func (*SchemaProvider) getPerformanceKeys(defaults *Config) []entity.ConfigKeyIn
10121012 Key: "engine.cef.input.scroll_precise_multiplier",
10131013 Type: "float64",
10141014 Default: fmt.Sprintf("%.2f", defaults.Engine.CEF.Input.ScrollPreciseMultiplier),
1015- Description: "CEF precise/surface scroll delta multiplier",
1015+ Description: "CEF precise/surface scroll delta multiplier for touchpads and high-resolution wheels",
10161016 Range: ">0",
10171017 Section: SectionPerformance,
10181018 },
@@ -1028,7 +1028,7 @@ func (*SchemaProvider) getPerformanceKeys(defaults *Config) []entity.ConfigKeyIn
10281028 Key: "engine.cef.input.scroll_vertical_multiplier",
10291029 Type: "float64",
10301030 Default: fmt.Sprintf("%.2f", defaults.Engine.CEF.Input.ScrollVerticalMultiplier),
1031- Description: "CEF vertical scroll delta multiplier",
1031+ Description: "CEF vertical scroll delta multiplier; touchpad vertical speed uses this together with scroll_precise_multiplier",
10321032 Range: ">0",
10331033 Section: SectionPerformance,
10341034 },
@@ -1059,7 +1059,7 @@ func (*SchemaProvider) getPerformanceKeys(defaults *Config) []entity.ConfigKeyIn
10591059 Key: "engine.cef.input.touchpad_navigation_max_vertical_ratio",
10601060 Type: "float64",
10611061 Default: fmt.Sprintf("%.2f", defaults.Engine.CEF.Input.TouchpadNavigationMaxVerticalRatio),
1062- Description: "Maximum vertical-to-horizontal delta ratio allowed for CEF touchpad navigation swipes",
1062+ Description: "Maximum vertical-to-horizontal delta ratio allowed for CEF touchpad back/forward swipes; does not affect vertical scroll speed",
10631063 Range: ">0",
10641064 Section: SectionPerformance,
10651065 },
@@ -1012,7 +1012,7 @@ func (*SchemaProvider) getPerformanceKeys(defaults *Config) []entity.ConfigKeyIn
1012 Key: "engine.cef.input.scroll_precise_multiplier",1012 Key: "engine.cef.input.scroll_precise_multiplier",
1013 Type: "float64",1013 Type: "float64",
1014 Default: fmt.Sprintf("%.2f", defaults.Engine.CEF.Input.ScrollPreciseMultiplier),1014 Default: fmt.Sprintf("%.2f", defaults.Engine.CEF.Input.ScrollPreciseMultiplier),
1015- Description: "CEF precise/surface scroll delta multiplier",1015+ Description: "CEF precise/surface scroll delta multiplier for touchpads and high-resolution wheels",
1016 Range: ">0",1016 Range: ">0",
1017 Section: SectionPerformance,1017 Section: SectionPerformance,
1018 },1018 },
@@ -1028,7 +1028,7 @@ func (*SchemaProvider) getPerformanceKeys(defaults *Config) []entity.ConfigKeyIn
1028 Key: "engine.cef.input.scroll_vertical_multiplier",1028 Key: "engine.cef.input.scroll_vertical_multiplier",
1029 Type: "float64",1029 Type: "float64",
1030 Default: fmt.Sprintf("%.2f", defaults.Engine.CEF.Input.ScrollVerticalMultiplier),1030 Default: fmt.Sprintf("%.2f", defaults.Engine.CEF.Input.ScrollVerticalMultiplier),
1031- Description: "CEF vertical scroll delta multiplier",1031+ Description: "CEF vertical scroll delta multiplier; touchpad vertical speed uses this together with scroll_precise_multiplier",
1032 Range: ">0",1032 Range: ">0",
1033 Section: SectionPerformance,1033 Section: SectionPerformance,
1034 },1034 },
@@ -1059,7 +1059,7 @@ func (*SchemaProvider) getPerformanceKeys(defaults *Config) []entity.ConfigKeyIn
1059 Key: "engine.cef.input.touchpad_navigation_max_vertical_ratio",1059 Key: "engine.cef.input.touchpad_navigation_max_vertical_ratio",
1060 Type: "float64",1060 Type: "float64",
1061 Default: fmt.Sprintf("%.2f", defaults.Engine.CEF.Input.TouchpadNavigationMaxVerticalRatio),1061 Default: fmt.Sprintf("%.2f", defaults.Engine.CEF.Input.TouchpadNavigationMaxVerticalRatio),
1062- Description: "Maximum vertical-to-horizontal delta ratio allowed for CEF touchpad navigation swipes",1062+ Description: "Maximum vertical-to-horizontal delta ratio allowed for CEF touchpad back/forward swipes; does not affect vertical scroll speed",
1063 Range: ">0",1063 Range: ">0",
1064 Section: SectionPerformance,1064 Section: SectionPerformance,
1065 },1065 },
M internal/infrastructure/config/transform_legacy.go +77 -0
@@ -69,6 +69,52 @@ func (*LegacyConfigTransformer) transformLegacyEngineConfig(rawConfig map[string
6969
7070 delete(cef, "enable_context_menu_handler")
7171 migrateLegacyCEFWindowlessFrameRateDefault(cef, hasAdaptive, adaptiveEnabled)
72+ migrateLegacyCEFInputScrollDefaults(cef)
73+}
74+
75+const legacyCEFScrollTouchpadMultiplierDefault = 0.35
76+
77+func migrateLegacyCEFInputScrollDefaults(cef map[string]any) {
78+ input, ok := cef["input"].(map[string]any)
79+ if !ok {
80+ return
81+ }
82+
83+ if legacyTouchpad, hasLegacyTouchpad := input["scroll_touchpad_multiplier"]; hasLegacyTouchpad {
84+ if precise, hasPrecise := input["scroll_precise_multiplier"]; !hasPrecise {
85+ input["scroll_precise_multiplier"] = migratedCEFPreciseScrollMultiplier(legacyTouchpad)
86+ } else if shouldPreferLegacyTouchpadScrollMultiplier(legacyTouchpad, precise) {
87+ input["scroll_precise_multiplier"] = legacyTouchpad
88+ }
89+ delete(input, "scroll_touchpad_multiplier")
90+ }
91+
92+ if current, hasPrecise := input["scroll_precise_multiplier"]; hasPrecise {
93+ input["scroll_precise_multiplier"] = migratedCEFPreciseScrollMultiplier(current)
94+ }
95+}
96+
97+func shouldPreferLegacyTouchpadScrollMultiplier(legacyTouchpad, precise any) bool {
98+ legacyMultiplier, legacyOK := float64ConfigValue(legacyTouchpad)
99+ preciseMultiplier, preciseOK := float64ConfigValue(precise)
100+ if !legacyOK || !preciseOK {
101+ return false
102+ }
103+ return !sameConfigFloat(legacyMultiplier, legacyCEFScrollTouchpadMultiplierDefault) &&
104+ (sameConfigFloat(preciseMultiplier, defaultCEFScrollPreciseMultiplier) ||
105+ sameConfigFloat(preciseMultiplier, legacyCEFScrollTouchpadMultiplierDefault))
106+}
107+
108+func migratedCEFPreciseScrollMultiplier(value any) any {
109+ multiplier, ok := float64ConfigValue(value)
110+ if !ok || !sameConfigFloat(multiplier, legacyCEFScrollTouchpadMultiplierDefault) {
111+ return value
112+ }
113+ return defaultCEFScrollPreciseMultiplier
114+}
115+
116+func sameConfigFloat(a, b float64) bool {
117+ return math.Abs(a-b) <= 0.000001
72118 }
73119
74120 func migrateLegacyCEFWindowlessFrameRateDefault(cef map[string]any, hasAdaptive, adaptiveEnabled bool) {
@@ -106,6 +152,37 @@ func nestedConfigBoolValue(rawConfig map[string]any, path ...string) (bool, bool
106152 return false, false
107153 }
108154
155+func float64ConfigValue(value any) (float64, bool) {
156+ switch v := value.(type) {
157+ case float32:
158+ return float64(v), true
159+ case float64:
160+ return v, true
161+ case int:
162+ return float64(v), true
163+ case int8:
164+ return float64(v), true
165+ case int16:
166+ return float64(v), true
167+ case int32:
168+ return float64(v), true
169+ case int64:
170+ return float64(v), true
171+ case uint:
172+ return float64(v), true
173+ case uint8:
174+ return float64(v), true
175+ case uint16:
176+ return float64(v), true
177+ case uint32:
178+ return float64(v), true
179+ case uint64:
180+ return float64(v), true
181+ default:
182+ return 0, false
183+ }
184+}
185+
109186 func int64ConfigValue(value any) (int64, bool) {
110187 switch v := value.(type) {
111188 case int:
@@ -69,6 +69,52 @@ func (*LegacyConfigTransformer) transformLegacyEngineConfig(rawConfig map[string
69 69
70 delete(cef, "enable_context_menu_handler")70 delete(cef, "enable_context_menu_handler")
71 migrateLegacyCEFWindowlessFrameRateDefault(cef, hasAdaptive, adaptiveEnabled)71 migrateLegacyCEFWindowlessFrameRateDefault(cef, hasAdaptive, adaptiveEnabled)
72+ migrateLegacyCEFInputScrollDefaults(cef)
73+}
74+
75+const legacyCEFScrollTouchpadMultiplierDefault = 0.35
76+
77+func migrateLegacyCEFInputScrollDefaults(cef map[string]any) {
78+ input, ok := cef["input"].(map[string]any)
79+ if !ok {
80+ return
81+ }
82+
83+ if legacyTouchpad, hasLegacyTouchpad := input["scroll_touchpad_multiplier"]; hasLegacyTouchpad {
84+ if precise, hasPrecise := input["scroll_precise_multiplier"]; !hasPrecise {
85+ input["scroll_precise_multiplier"] = migratedCEFPreciseScrollMultiplier(legacyTouchpad)
86+ } else if shouldPreferLegacyTouchpadScrollMultiplier(legacyTouchpad, precise) {
87+ input["scroll_precise_multiplier"] = legacyTouchpad
88+ }
89+ delete(input, "scroll_touchpad_multiplier")
90+ }
91+
92+ if current, hasPrecise := input["scroll_precise_multiplier"]; hasPrecise {
93+ input["scroll_precise_multiplier"] = migratedCEFPreciseScrollMultiplier(current)
94+ }
95+}
96+
97+func shouldPreferLegacyTouchpadScrollMultiplier(legacyTouchpad, precise any) bool {
98+ legacyMultiplier, legacyOK := float64ConfigValue(legacyTouchpad)
99+ preciseMultiplier, preciseOK := float64ConfigValue(precise)
100+ if !legacyOK || !preciseOK {
101+ return false
102+ }
103+ return !sameConfigFloat(legacyMultiplier, legacyCEFScrollTouchpadMultiplierDefault) &&
104+ (sameConfigFloat(preciseMultiplier, defaultCEFScrollPreciseMultiplier) ||
105+ sameConfigFloat(preciseMultiplier, legacyCEFScrollTouchpadMultiplierDefault))
106+}
107+
108+func migratedCEFPreciseScrollMultiplier(value any) any {
109+ multiplier, ok := float64ConfigValue(value)
110+ if !ok || !sameConfigFloat(multiplier, legacyCEFScrollTouchpadMultiplierDefault) {
111+ return value
112+ }
113+ return defaultCEFScrollPreciseMultiplier
114+}
115+
116+func sameConfigFloat(a, b float64) bool {
117+ return math.Abs(a-b) <= 0.000001
72 }118 }
73 119
74 func migrateLegacyCEFWindowlessFrameRateDefault(cef map[string]any, hasAdaptive, adaptiveEnabled bool) {120 func migrateLegacyCEFWindowlessFrameRateDefault(cef map[string]any, hasAdaptive, adaptiveEnabled bool) {
@@ -106,6 +152,37 @@ func nestedConfigBoolValue(rawConfig map[string]any, path ...string) (bool, bool
106 return false, false152 return false, false
107 }153 }
108 154
155+func float64ConfigValue(value any) (float64, bool) {
156+ switch v := value.(type) {
157+ case float32:
158+ return float64(v), true
159+ case float64:
160+ return v, true
161+ case int:
162+ return float64(v), true
163+ case int8:
164+ return float64(v), true
165+ case int16:
166+ return float64(v), true
167+ case int32:
168+ return float64(v), true
169+ case int64:
170+ return float64(v), true
171+ case uint:
172+ return float64(v), true
173+ case uint8:
174+ return float64(v), true
175+ case uint16:
176+ return float64(v), true
177+ case uint32:
178+ return float64(v), true
179+ case uint64:
180+ return float64(v), true
181+ default:
182+ return 0, false
183+ }
184+}
185+
109 func int64ConfigValue(value any) (int64, bool) {186 func int64ConfigValue(value any) (int64, bool) {
110 switch v := value.(type) {187 switch v := value.(type) {
111 case int:188 case int:
M internal/infrastructure/config/transform_legacy_test.go +94 -0
@@ -206,6 +206,100 @@ func TestTransformLegacyEngineConfig_MigratesLegacyCEFWindowlessFrameRateDefault
206206 assert.Equal(t, defaultCEFWindowlessFrameRateMax, cef["windowless_frame_rate_max"])
207207 }
208208
209+func TestTransformLegacyEngineConfig_UpgradesObsoletePreciseScrollDefault(t *testing.T) {
210+ transformer := NewLegacyConfigTransformer()
211+ rawConfig := map[string]any{
212+ "engine": map[string]any{
213+ "cef": map[string]any{
214+ "input": map[string]any{
215+ "scroll_precise_multiplier": 0.35,
216+ },
217+ },
218+ },
219+ }
220+
221+ transformer.TransformLegacyEngineConfig(rawConfig)
222+
223+ input := rawConfig["engine"].(map[string]any)["cef"].(map[string]any)["input"].(map[string]any)
224+ assert.Equal(t, defaultCEFScrollPreciseMultiplier, input["scroll_precise_multiplier"])
225+}
226+
227+func TestTransformLegacyEngineConfig_MigratesLegacyTouchpadScrollKey(t *testing.T) {
228+ transformer := NewLegacyConfigTransformer()
229+ rawConfig := map[string]any{
230+ "engine": map[string]any{
231+ "cef": map[string]any{
232+ "input": map[string]any{
233+ "scroll_touchpad_multiplier": 0.35,
234+ },
235+ },
236+ },
237+ }
238+
239+ transformer.TransformLegacyEngineConfig(rawConfig)
240+
241+ input := rawConfig["engine"].(map[string]any)["cef"].(map[string]any)["input"].(map[string]any)
242+ assert.Equal(t, defaultCEFScrollPreciseMultiplier, input["scroll_precise_multiplier"])
243+ assert.NotContains(t, input, "scroll_touchpad_multiplier")
244+}
245+
246+func TestTransformLegacyEngineConfig_PreservesCustomPreciseScrollMultiplier(t *testing.T) {
247+ transformer := NewLegacyConfigTransformer()
248+ rawConfig := map[string]any{
249+ "engine": map[string]any{
250+ "cef": map[string]any{
251+ "input": map[string]any{
252+ "scroll_precise_multiplier": 4.0,
253+ },
254+ },
255+ },
256+ }
257+
258+ transformer.TransformLegacyEngineConfig(rawConfig)
259+
260+ input := rawConfig["engine"].(map[string]any)["cef"].(map[string]any)["input"].(map[string]any)
261+ assert.Equal(t, 4.0, input["scroll_precise_multiplier"])
262+}
263+
264+func TestTransformLegacyEngineConfig_PreservesCustomLegacyTouchpadScrollMultiplier(t *testing.T) {
265+ transformer := NewLegacyConfigTransformer()
266+ rawConfig := map[string]any{
267+ "engine": map[string]any{
268+ "cef": map[string]any{
269+ "input": map[string]any{
270+ "scroll_touchpad_multiplier": 4.0,
271+ },
272+ },
273+ },
274+ }
275+
276+ transformer.TransformLegacyEngineConfig(rawConfig)
277+
278+ input := rawConfig["engine"].(map[string]any)["cef"].(map[string]any)["input"].(map[string]any)
279+ assert.Equal(t, 4.0, input["scroll_precise_multiplier"])
280+ assert.NotContains(t, input, "scroll_touchpad_multiplier")
281+}
282+
283+func TestTransformLegacyEngineConfig_PrefersCustomLegacyTouchpadOverGeneratedPreciseDefault(t *testing.T) {
284+ transformer := NewLegacyConfigTransformer()
285+ rawConfig := map[string]any{
286+ "engine": map[string]any{
287+ "cef": map[string]any{
288+ "input": map[string]any{
289+ "scroll_touchpad_multiplier": 4.0,
290+ "scroll_precise_multiplier": 0.35,
291+ },
292+ },
293+ },
294+ }
295+
296+ transformer.TransformLegacyEngineConfig(rawConfig)
297+
298+ input := rawConfig["engine"].(map[string]any)["cef"].(map[string]any)["input"].(map[string]any)
299+ assert.Equal(t, 4.0, input["scroll_precise_multiplier"])
300+ assert.NotContains(t, input, "scroll_touchpad_multiplier")
301+}
302+
209303 func TestTransformLegacyEngineConfig_PreservesExplicitCEFWindowlessFrameRate(t *testing.T) {
210304 transformer := NewLegacyConfigTransformer()
211305 rawConfig := map[string]any{
@@ -206,6 +206,100 @@ func TestTransformLegacyEngineConfig_MigratesLegacyCEFWindowlessFrameRateDefault
206 assert.Equal(t, defaultCEFWindowlessFrameRateMax, cef["windowless_frame_rate_max"])206 assert.Equal(t, defaultCEFWindowlessFrameRateMax, cef["windowless_frame_rate_max"])
207 }207 }
208 208
209+func TestTransformLegacyEngineConfig_UpgradesObsoletePreciseScrollDefault(t *testing.T) {
210+ transformer := NewLegacyConfigTransformer()
211+ rawConfig := map[string]any{
212+ "engine": map[string]any{
213+ "cef": map[string]any{
214+ "input": map[string]any{
215+ "scroll_precise_multiplier": 0.35,
216+ },
217+ },
218+ },
219+ }
220+
221+ transformer.TransformLegacyEngineConfig(rawConfig)
222+
223+ input := rawConfig["engine"].(map[string]any)["cef"].(map[string]any)["input"].(map[string]any)
224+ assert.Equal(t, defaultCEFScrollPreciseMultiplier, input["scroll_precise_multiplier"])
225+}
226+
227+func TestTransformLegacyEngineConfig_MigratesLegacyTouchpadScrollKey(t *testing.T) {
228+ transformer := NewLegacyConfigTransformer()
229+ rawConfig := map[string]any{
230+ "engine": map[string]any{
231+ "cef": map[string]any{
232+ "input": map[string]any{
233+ "scroll_touchpad_multiplier": 0.35,
234+ },
235+ },
236+ },
237+ }
238+
239+ transformer.TransformLegacyEngineConfig(rawConfig)
240+
241+ input := rawConfig["engine"].(map[string]any)["cef"].(map[string]any)["input"].(map[string]any)
242+ assert.Equal(t, defaultCEFScrollPreciseMultiplier, input["scroll_precise_multiplier"])
243+ assert.NotContains(t, input, "scroll_touchpad_multiplier")
244+}
245+
246+func TestTransformLegacyEngineConfig_PreservesCustomPreciseScrollMultiplier(t *testing.T) {
247+ transformer := NewLegacyConfigTransformer()
248+ rawConfig := map[string]any{
249+ "engine": map[string]any{
250+ "cef": map[string]any{
251+ "input": map[string]any{
252+ "scroll_precise_multiplier": 4.0,
253+ },
254+ },
255+ },
256+ }
257+
258+ transformer.TransformLegacyEngineConfig(rawConfig)
259+
260+ input := rawConfig["engine"].(map[string]any)["cef"].(map[string]any)["input"].(map[string]any)
261+ assert.Equal(t, 4.0, input["scroll_precise_multiplier"])
262+}
263+
264+func TestTransformLegacyEngineConfig_PreservesCustomLegacyTouchpadScrollMultiplier(t *testing.T) {
265+ transformer := NewLegacyConfigTransformer()
266+ rawConfig := map[string]any{
267+ "engine": map[string]any{
268+ "cef": map[string]any{
269+ "input": map[string]any{
270+ "scroll_touchpad_multiplier": 4.0,
271+ },
272+ },
273+ },
274+ }
275+
276+ transformer.TransformLegacyEngineConfig(rawConfig)
277+
278+ input := rawConfig["engine"].(map[string]any)["cef"].(map[string]any)["input"].(map[string]any)
279+ assert.Equal(t, 4.0, input["scroll_precise_multiplier"])
280+ assert.NotContains(t, input, "scroll_touchpad_multiplier")
281+}
282+
283+func TestTransformLegacyEngineConfig_PrefersCustomLegacyTouchpadOverGeneratedPreciseDefault(t *testing.T) {
284+ transformer := NewLegacyConfigTransformer()
285+ rawConfig := map[string]any{
286+ "engine": map[string]any{
287+ "cef": map[string]any{
288+ "input": map[string]any{
289+ "scroll_touchpad_multiplier": 4.0,
290+ "scroll_precise_multiplier": 0.35,
291+ },
292+ },
293+ },
294+ }
295+
296+ transformer.TransformLegacyEngineConfig(rawConfig)
297+
298+ input := rawConfig["engine"].(map[string]any)["cef"].(map[string]any)["input"].(map[string]any)
299+ assert.Equal(t, 4.0, input["scroll_precise_multiplier"])
300+ assert.NotContains(t, input, "scroll_touchpad_multiplier")
301+}
302+
209 func TestTransformLegacyEngineConfig_PreservesExplicitCEFWindowlessFrameRate(t *testing.T) {303 func TestTransformLegacyEngineConfig_PreservesExplicitCEFWindowlessFrameRate(t *testing.T) {
210 transformer := NewLegacyConfigTransformer()304 transformer := NewLegacyConfigTransformer()
211 rawConfig := map[string]any{305 rawConfig := map[string]any{

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