detached 6a62fb1

Merge pull request #307 from bnema/fix/touchpad-scroll-speed

fix(cef): restore touchpad scroll speed
commit
6a62fb182164788ca78a30eef93eaaed2e1e8a89
author
bnema · · 2026-06-18 03:25 UTC
committer
GitHub · · 2026-06-18 03:25 UTC
merge of
7f05154 + 5af6b41 · diff against the first parent
signature
GPG UnverifiedB5690EEEBB952194 The key is unknown
7 files · +224 -13
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 +9 -8
@@ -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 },
@@ -1056,12 +1056,13 @@ func (*SchemaProvider) getPerformanceKeys(defaults *Config) []entity.ConfigKeyIn
10561056 Section: SectionPerformance,
10571057 },
10581058 {
1059- Key: "engine.cef.input.touchpad_navigation_max_vertical_ratio",
1060- Type: "float64",
1061- Default: fmt.Sprintf("%.2f", defaults.Engine.CEF.Input.TouchpadNavigationMaxVerticalRatio),
1062- Description: "Maximum vertical-to-horizontal delta ratio allowed for CEF touchpad navigation swipes",
1063- Range: ">0",
1064- Section: SectionPerformance,
1059+ Key: "engine.cef.input.touchpad_navigation_max_vertical_ratio",
1060+ Type: "float64",
1061+ Default: fmt.Sprintf("%.2f", defaults.Engine.CEF.Input.TouchpadNavigationMaxVerticalRatio),
1062+ Description: "Maximum vertical-to-horizontal delta ratio allowed for CEF touchpad back/forward swipes; " +
1063+ "does not affect vertical scroll speed",
1064+ Range: ">0",
1065+ Section: SectionPerformance,
10651066 },
10661067 {
10671068 Key: "engine.webkit.skia_cpu_painting_threads",
@@ -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 },
@@ -1056,12 +1056,13 @@ func (*SchemaProvider) getPerformanceKeys(defaults *Config) []entity.ConfigKeyIn
1056 Section: SectionPerformance,1056 Section: SectionPerformance,
1057 },1057 },
1058 {1058 {
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; " +
1063- Range: ">0",1063+ "does not affect vertical scroll speed",
1064- Section: SectionPerformance,1064+ Range: ">0",
1065+ Section: SectionPerformance,
1065 },1066 },
1066 {1067 {
1067 Key: "engine.webkit.skia_cpu_painting_threads",1068 Key: "engine.webkit.skia_cpu_painting_threads",
M internal/infrastructure/config/transform_legacy.go +80 -0
@@ -69,6 +69,55 @@ 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 (
76+ legacyCEFScrollTouchpadMultiplierDefault = 0.35
77+ configFloatTolerance = 0.000001
78+)
79+
80+func migrateLegacyCEFInputScrollDefaults(cef map[string]any) {
81+ input, ok := cef["input"].(map[string]any)
82+ if !ok {
83+ return
84+ }
85+
86+ if legacyTouchpad, hasLegacyTouchpad := input["scroll_touchpad_multiplier"]; hasLegacyTouchpad {
87+ if precise, hasPrecise := input["scroll_precise_multiplier"]; !hasPrecise {
88+ input["scroll_precise_multiplier"] = migratedCEFPreciseScrollMultiplier(legacyTouchpad)
89+ } else if shouldPreferLegacyTouchpadScrollMultiplier(legacyTouchpad, precise) {
90+ input["scroll_precise_multiplier"] = legacyTouchpad
91+ }
92+ delete(input, "scroll_touchpad_multiplier")
93+ }
94+
95+ if current, hasPrecise := input["scroll_precise_multiplier"]; hasPrecise {
96+ input["scroll_precise_multiplier"] = migratedCEFPreciseScrollMultiplier(current)
97+ }
98+}
99+
100+func shouldPreferLegacyTouchpadScrollMultiplier(legacyTouchpad, precise any) bool {
101+ legacyMultiplier, legacyOK := float64ConfigValue(legacyTouchpad)
102+ preciseMultiplier, preciseOK := float64ConfigValue(precise)
103+ if !legacyOK || !preciseOK {
104+ return false
105+ }
106+ return !sameConfigFloat(legacyMultiplier, legacyCEFScrollTouchpadMultiplierDefault) &&
107+ (sameConfigFloat(preciseMultiplier, defaultCEFScrollPreciseMultiplier) ||
108+ sameConfigFloat(preciseMultiplier, legacyCEFScrollTouchpadMultiplierDefault))
109+}
110+
111+func migratedCEFPreciseScrollMultiplier(value any) any {
112+ multiplier, ok := float64ConfigValue(value)
113+ if !ok || !sameConfigFloat(multiplier, legacyCEFScrollTouchpadMultiplierDefault) {
114+ return value
115+ }
116+ return defaultCEFScrollPreciseMultiplier
117+}
118+
119+func sameConfigFloat(a, b float64) bool {
120+ return math.Abs(a-b) <= configFloatTolerance
72121 }
73122
74123 func migrateLegacyCEFWindowlessFrameRateDefault(cef map[string]any, hasAdaptive, adaptiveEnabled bool) {
@@ -106,6 +155,37 @@ func nestedConfigBoolValue(rawConfig map[string]any, path ...string) (bool, bool
106155 return false, false
107156 }
108157
158+func float64ConfigValue(value any) (float64, bool) {
159+ switch v := value.(type) {
160+ case float32:
161+ return float64(v), true
162+ case float64:
163+ return v, true
164+ case int:
165+ return float64(v), true
166+ case int8:
167+ return float64(v), true
168+ case int16:
169+ return float64(v), true
170+ case int32:
171+ return float64(v), true
172+ case int64:
173+ return float64(v), true
174+ case uint:
175+ return float64(v), true
176+ case uint8:
177+ return float64(v), true
178+ case uint16:
179+ return float64(v), true
180+ case uint32:
181+ return float64(v), true
182+ case uint64:
183+ return float64(v), true
184+ default:
185+ return 0, false
186+ }
187+}
188+
109189 func int64ConfigValue(value any) (int64, bool) {
110190 switch v := value.(type) {
111191 case int:
@@ -69,6 +69,55 @@ 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 (
76+ legacyCEFScrollTouchpadMultiplierDefault = 0.35
77+ configFloatTolerance = 0.000001
78+)
79+
80+func migrateLegacyCEFInputScrollDefaults(cef map[string]any) {
81+ input, ok := cef["input"].(map[string]any)
82+ if !ok {
83+ return
84+ }
85+
86+ if legacyTouchpad, hasLegacyTouchpad := input["scroll_touchpad_multiplier"]; hasLegacyTouchpad {
87+ if precise, hasPrecise := input["scroll_precise_multiplier"]; !hasPrecise {
88+ input["scroll_precise_multiplier"] = migratedCEFPreciseScrollMultiplier(legacyTouchpad)
89+ } else if shouldPreferLegacyTouchpadScrollMultiplier(legacyTouchpad, precise) {
90+ input["scroll_precise_multiplier"] = legacyTouchpad
91+ }
92+ delete(input, "scroll_touchpad_multiplier")
93+ }
94+
95+ if current, hasPrecise := input["scroll_precise_multiplier"]; hasPrecise {
96+ input["scroll_precise_multiplier"] = migratedCEFPreciseScrollMultiplier(current)
97+ }
98+}
99+
100+func shouldPreferLegacyTouchpadScrollMultiplier(legacyTouchpad, precise any) bool {
101+ legacyMultiplier, legacyOK := float64ConfigValue(legacyTouchpad)
102+ preciseMultiplier, preciseOK := float64ConfigValue(precise)
103+ if !legacyOK || !preciseOK {
104+ return false
105+ }
106+ return !sameConfigFloat(legacyMultiplier, legacyCEFScrollTouchpadMultiplierDefault) &&
107+ (sameConfigFloat(preciseMultiplier, defaultCEFScrollPreciseMultiplier) ||
108+ sameConfigFloat(preciseMultiplier, legacyCEFScrollTouchpadMultiplierDefault))
109+}
110+
111+func migratedCEFPreciseScrollMultiplier(value any) any {
112+ multiplier, ok := float64ConfigValue(value)
113+ if !ok || !sameConfigFloat(multiplier, legacyCEFScrollTouchpadMultiplierDefault) {
114+ return value
115+ }
116+ return defaultCEFScrollPreciseMultiplier
117+}
118+
119+func sameConfigFloat(a, b float64) bool {
120+ return math.Abs(a-b) <= configFloatTolerance
72 }121 }
73 122
74 func migrateLegacyCEFWindowlessFrameRateDefault(cef map[string]any, hasAdaptive, adaptiveEnabled bool) {123 func migrateLegacyCEFWindowlessFrameRateDefault(cef map[string]any, hasAdaptive, adaptiveEnabled bool) {
@@ -106,6 +155,37 @@ func nestedConfigBoolValue(rawConfig map[string]any, path ...string) (bool, bool
106 return false, false155 return false, false
107 }156 }
108 157
158+func float64ConfigValue(value any) (float64, bool) {
159+ switch v := value.(type) {
160+ case float32:
161+ return float64(v), true
162+ case float64:
163+ return v, true
164+ case int:
165+ return float64(v), true
166+ case int8:
167+ return float64(v), true
168+ case int16:
169+ return float64(v), true
170+ case int32:
171+ return float64(v), true
172+ case int64:
173+ return float64(v), true
174+ case uint:
175+ return float64(v), true
176+ case uint8:
177+ return float64(v), true
178+ case uint16:
179+ return float64(v), true
180+ case uint32:
181+ return float64(v), true
182+ case uint64:
183+ return float64(v), true
184+ default:
185+ return 0, false
186+ }
187+}
188+
109 func int64ConfigValue(value any) (int64, bool) {189 func int64ConfigValue(value any) (int64, bool) {
110 switch v := value.(type) {190 switch v := value.(type) {
111 case int:191 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.InDelta(t, defaultCEFScrollPreciseMultiplier, input["scroll_precise_multiplier"], 0.001)
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.InDelta(t, defaultCEFScrollPreciseMultiplier, input["scroll_precise_multiplier"], 0.001)
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.InDelta(t, 4.0, input["scroll_precise_multiplier"], 0.001)
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.InDelta(t, 4.0, input["scroll_precise_multiplier"], 0.001)
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.InDelta(t, 4.0, input["scroll_precise_multiplier"], 0.001)
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.InDelta(t, defaultCEFScrollPreciseMultiplier, input["scroll_precise_multiplier"], 0.001)
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.InDelta(t, defaultCEFScrollPreciseMultiplier, input["scroll_precise_multiplier"], 0.001)
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.InDelta(t, 4.0, input["scroll_precise_multiplier"], 0.001)
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.InDelta(t, 4.0, input["scroll_precise_multiplier"], 0.001)
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.InDelta(t, 4.0, input["scroll_precise_multiplier"], 0.001)
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