Merge pull request #270 from bnema/feat/cef-input-config
Add CEF scroll input tuning
18 files · +226 -11
M docs/config/index.md +5 -0
| @@ -194,6 +194,11 @@ border = "#363636" | |||
| 194 | 194 | | `engine.cef.adaptive_windowless_frame_rate` | bool | `true` | - | Enable adaptive CEF OSR FPS polling (only applies when `windowless_frame_rate = 0`) | | |
| 195 | 195 | | `engine.cef.windowless_frame_rate` | int32 | `0` | >= 0 | Explicit CEF OSR FPS cap; 0 uses adaptive mode if enabled, otherwise CEF default | | |
| 196 | 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 | | |
| 198 | + | | `engine.cef.input.scroll_touchpad_multiplier` | float | `0.35` | > 0 | Touchpad/smooth 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 | | |
| 201 | + | | `engine.cef.input.scroll_max_delta` | int32 | `0` | >= 0 | Maximum absolute scroll delta after scaling; 0 disables clamping | | |
| 197 | 202 | | `default_ui_scale` | float | `1.0` | > 0 | GTK widget UI scale (1.0=100%, 2.0=200%) | | |
| 198 | 203 | | `default_webpage_zoom` | float | `1.2` | > 0 | Default page zoom (1.0=100%, 1.2=120%) | | |
| 199 | 204 | ||
| @@ -194,6 +194,11 @@ border = "#363636" | |||||
| 194 | | `engine.cef.adaptive_windowless_frame_rate` | bool | `true` | - | Enable adaptive CEF OSR FPS polling (only applies when `windowless_frame_rate = 0`) | | 194 | | `engine.cef.adaptive_windowless_frame_rate` | bool | `true` | - | Enable adaptive CEF OSR FPS polling (only applies when `windowless_frame_rate = 0`) | | ||
| 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 | | |||
| 198 | + | | `engine.cef.input.scroll_touchpad_multiplier` | float | `0.35` | > 0 | Touchpad/smooth 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 | | |||
| 201 | + | | `engine.cef.input.scroll_max_delta` | int32 | `0` | >= 0 | Maximum absolute scroll delta after scaling; 0 disables clamping | | |||
| 197 | | `default_ui_scale` | float | `1.0` | > 0 | GTK widget UI scale (1.0=100%, 2.0=200%) | | 202 | | `default_ui_scale` | float | `1.0` | > 0 | GTK widget UI scale (1.0=100%, 2.0=200%) | | ||
| 198 | | `default_webpage_zoom` | float | `1.2` | > 0 | Default page zoom (1.0=100%, 1.2=120%) | | 203 | | `default_webpage_zoom` | float | `1.2` | > 0 | Default page zoom (1.0=100%, 1.2=120%) | | ||
| 199 | 204 | ||||
M docs/reference/configuration.md +5 -0
| @@ -64,6 +64,11 @@ | |||
| 64 | 64 | | `engine.cef.adaptive_windowless_frame_rate` | bool | `true` | | | |
| 65 | 65 | | `engine.cef.windowless_frame_rate` | int32 | `0` | >= 0 | | |
| 66 | 66 | | `engine.cef.windowless_frame_rate_max` | int32 | `240` | >= 0 | | |
| 67 | + | | `engine.cef.input.scroll_wheel_multiplier` | float | `1.0` | > 0 | | |
| 68 | + | | `engine.cef.input.scroll_touchpad_multiplier` | float | `0.35` | > 0 | | |
| 69 | + | | `engine.cef.input.scroll_horizontal_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 | | |
| 67 | 72 | | `default_ui_scale` | float | `1.0` | > 0 | | |
| 68 | 73 | | `default_webpage_zoom` | float | `1.2` | > 0 | | |
| 69 | 74 | | `workspace.new_pane_url` | string | `about:blank` | | | |
| @@ -64,6 +64,11 @@ | |||||
| 64 | | `engine.cef.adaptive_windowless_frame_rate` | bool | `true` | | | 64 | | `engine.cef.adaptive_windowless_frame_rate` | bool | `true` | | | ||
| 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 | | |||
| 68 | + | | `engine.cef.input.scroll_touchpad_multiplier` | float | `0.35` | > 0 | | |||
| 69 | + | | `engine.cef.input.scroll_horizontal_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 | | |||
| 67 | | `default_ui_scale` | float | `1.0` | > 0 | | 72 | | `default_ui_scale` | float | `1.0` | > 0 | | ||
| 68 | | `default_webpage_zoom` | float | `1.2` | > 0 | | 73 | | `default_webpage_zoom` | float | `1.2` | > 0 | | ||
| 69 | | `workspace.new_pane_url` | string | `about:blank` | | | 74 | | `workspace.new_pane_url` | string | `about:blank` | | | ||
M go.mod +1 -1
| @@ -7,7 +7,7 @@ require ( | |||
| 7 | 7 | github.com/andybalholm/brotli v1.2.1 | |
| 8 | 8 | github.com/bnema/purego v0.11.0-bnema.3 | |
| 9 | 9 | github.com/bnema/purego-cef v0.13.2 | |
| 10 | - | github.com/bnema/purego-cef2gtk v0.5.6 | |
| 10 | + | github.com/bnema/purego-cef2gtk v0.6.0 | |
| 11 | 11 | github.com/bnema/purego-pipewire v0.1.4 | |
| 12 | 12 | github.com/bnema/purego-sqlite v0.1.3 | |
| 13 | 13 | github.com/bnema/puregotk v0.5.4 | |
| @@ -7,7 +7,7 @@ require ( | |||||
| 7 | github.com/andybalholm/brotli v1.2.1 | 7 | github.com/andybalholm/brotli v1.2.1 | ||
| 8 | github.com/bnema/purego v0.11.0-bnema.3 | 8 | github.com/bnema/purego v0.11.0-bnema.3 | ||
| 9 | github.com/bnema/purego-cef v0.13.2 | 9 | github.com/bnema/purego-cef v0.13.2 | ||
| 10 | - | github.com/bnema/purego-cef2gtk v0.5.6 | 10 | + | github.com/bnema/purego-cef2gtk v0.6.0 |
| 11 | github.com/bnema/purego-pipewire v0.1.4 | 11 | github.com/bnema/purego-pipewire v0.1.4 | ||
| 12 | github.com/bnema/purego-sqlite v0.1.3 | 12 | github.com/bnema/purego-sqlite v0.1.3 | ||
| 13 | github.com/bnema/puregotk v0.5.4 | 13 | github.com/bnema/puregotk v0.5.4 | ||
M go.sum +2 -2
| @@ -14,8 +14,8 @@ github.com/bnema/purego v0.11.0-bnema.3 h1:lpTXuapRIBH2OTgLJTjYlK1olHmZkEQLloqgP | |||
| 14 | 14 | github.com/bnema/purego v0.11.0-bnema.3/go.mod h1:5BnOIUj+0fvk/vwv45iottFoGfkDT7cnLXnpM2s6Hng= | |
| 15 | 15 | github.com/bnema/purego-cef v0.13.2 h1:weSttuJVxjyzp/A5J15ZdzOAVWO/jvvd6nBf8/N4zsI= | |
| 16 | 16 | github.com/bnema/purego-cef v0.13.2/go.mod h1:Jbc4GgUvGakEOfbRnXJGYrDbXz03YEpVgTWKPk5ss9k= | |
| 17 | - | github.com/bnema/purego-cef2gtk v0.5.6 h1:eG4zFGBUhkCfLksFJ/Wo2p9sbMIgQ6dTrTzOJ6oNKZY= | |
| 18 | - | github.com/bnema/purego-cef2gtk v0.5.6/go.mod h1:LGMP/8ip347wu5wnLiQh8m+I+yq4H+aiuMAEICrbVFk= | |
| 17 | + | github.com/bnema/purego-cef2gtk v0.6.0 h1:I8WJfqa7FmylE+JwZn7k3gsSKw0aRXmyHbQnXtyMdas= | |
| 18 | + | github.com/bnema/purego-cef2gtk v0.6.0/go.mod h1:LGMP/8ip347wu5wnLiQh8m+I+yq4H+aiuMAEICrbVFk= | |
| 19 | 19 | github.com/bnema/purego-pipewire v0.1.4 h1:ThuPiZo9BeYn1w06hg/g0INgQUIBi8n9Gq1o5VRtjxk= | |
| 20 | 20 | github.com/bnema/purego-pipewire v0.1.4/go.mod h1:dn9RGRtNteqIarb8gTv97VD/FQgur7ZJ1BE/Ub9NYSk= | |
| 21 | 21 | github.com/bnema/purego-sqlite v0.1.3 h1:aXysq9+wuRkbFzlVe2oIiooYseFZFb6ArwbfDYYafvY= | |
| @@ -14,8 +14,8 @@ github.com/bnema/purego v0.11.0-bnema.3 h1:lpTXuapRIBH2OTgLJTjYlK1olHmZkEQLloqgP | |||||
| 14 | github.com/bnema/purego v0.11.0-bnema.3/go.mod h1:5BnOIUj+0fvk/vwv45iottFoGfkDT7cnLXnpM2s6Hng= | 14 | github.com/bnema/purego v0.11.0-bnema.3/go.mod h1:5BnOIUj+0fvk/vwv45iottFoGfkDT7cnLXnpM2s6Hng= | ||
| 15 | github.com/bnema/purego-cef v0.13.2 h1:weSttuJVxjyzp/A5J15ZdzOAVWO/jvvd6nBf8/N4zsI= | 15 | github.com/bnema/purego-cef v0.13.2 h1:weSttuJVxjyzp/A5J15ZdzOAVWO/jvvd6nBf8/N4zsI= | ||
| 16 | github.com/bnema/purego-cef v0.13.2/go.mod h1:Jbc4GgUvGakEOfbRnXJGYrDbXz03YEpVgTWKPk5ss9k= | 16 | github.com/bnema/purego-cef v0.13.2/go.mod h1:Jbc4GgUvGakEOfbRnXJGYrDbXz03YEpVgTWKPk5ss9k= | ||
| 17 | - | github.com/bnema/purego-cef2gtk v0.5.6 h1:eG4zFGBUhkCfLksFJ/Wo2p9sbMIgQ6dTrTzOJ6oNKZY= | 17 | + | github.com/bnema/purego-cef2gtk v0.6.0 h1:I8WJfqa7FmylE+JwZn7k3gsSKw0aRXmyHbQnXtyMdas= |
| 18 | - | github.com/bnema/purego-cef2gtk v0.5.6/go.mod h1:LGMP/8ip347wu5wnLiQh8m+I+yq4H+aiuMAEICrbVFk= | 18 | + | github.com/bnema/purego-cef2gtk v0.6.0/go.mod h1:LGMP/8ip347wu5wnLiQh8m+I+yq4H+aiuMAEICrbVFk= |
| 19 | github.com/bnema/purego-pipewire v0.1.4 h1:ThuPiZo9BeYn1w06hg/g0INgQUIBi8n9Gq1o5VRtjxk= | 19 | github.com/bnema/purego-pipewire v0.1.4 h1:ThuPiZo9BeYn1w06hg/g0INgQUIBi8n9Gq1o5VRtjxk= | ||
| 20 | github.com/bnema/purego-pipewire v0.1.4/go.mod h1:dn9RGRtNteqIarb8gTv97VD/FQgur7ZJ1BE/Ub9NYSk= | 20 | github.com/bnema/purego-pipewire v0.1.4/go.mod h1:dn9RGRtNteqIarb8gTv97VD/FQgur7ZJ1BE/Ub9NYSk= | ||
| 21 | github.com/bnema/purego-sqlite v0.1.3 h1:aXysq9+wuRkbFzlVe2oIiooYseFZFb6ArwbfDYYafvY= | 21 | github.com/bnema/purego-sqlite v0.1.3 h1:aXysq9+wuRkbFzlVe2oIiooYseFZFb6ArwbfDYYafvY= | ||
M internal/bootstrap/engine.go +10 -3
| @@ -84,9 +84,16 @@ func BuildEngine(input EngineInput) (port.Engine, error) { | |||
| 84 | 84 | AdaptiveWindowlessFrameRate: cfg.Engine.CEF.CEFAdaptiveWindowlessFrameRate(), | |
| 85 | 85 | WindowlessFrameRate: cfg.Engine.CEF.WindowlessFrameRate, | |
| 86 | 86 | WindowlessFrameRateMax: cfg.Engine.CEF.CEFWindowlessFrameRateMax(), | |
| 87 | - | EnableAudioHandler: cfg.Engine.CEF.EnableAudioHandler, | |
| 88 | - | TraceHandlers: cfg.Engine.CEF.TraceHandlers, | |
| 89 | - | ApplicationScale: cfg.DefaultUIScale, | |
| 87 | + | Input: cef.RuntimeInputConfig{ | |
| 88 | + | ScrollWheelMultiplier: cfg.Engine.CEF.Input.ScrollWheelMultiplier, | |
| 89 | + | ScrollTouchpadMultiplier: cfg.Engine.CEF.Input.ScrollTouchpadMultiplier, | |
| 90 | + | ScrollHorizontalMultiplier: cfg.Engine.CEF.Input.ScrollHorizontalMultiplier, | |
| 91 | + | ScrollVerticalMultiplier: cfg.Engine.CEF.Input.ScrollVerticalMultiplier, | |
| 92 | + | ScrollMaxDelta: cfg.Engine.CEF.Input.ScrollMaxDelta, | |
| 93 | + | }, | |
| 94 | + | EnableAudioHandler: cfg.Engine.CEF.EnableAudioHandler, | |
| 95 | + | TraceHandlers: cfg.Engine.CEF.TraceHandlers, | |
| 96 | + | ApplicationScale: cfg.DefaultUIScale, | |
| 90 | 97 | } | |
| 91 | 98 | deps := cef.EngineDependencies{ | |
| 92 | 99 | RegisterHandlers: handlers.RegisterAll, | |
| @@ -84,9 +84,16 @@ func BuildEngine(input EngineInput) (port.Engine, error) { | |||||
| 84 | AdaptiveWindowlessFrameRate: cfg.Engine.CEF.CEFAdaptiveWindowlessFrameRate(), | 84 | AdaptiveWindowlessFrameRate: cfg.Engine.CEF.CEFAdaptiveWindowlessFrameRate(), | ||
| 85 | WindowlessFrameRate: cfg.Engine.CEF.WindowlessFrameRate, | 85 | WindowlessFrameRate: cfg.Engine.CEF.WindowlessFrameRate, | ||
| 86 | WindowlessFrameRateMax: cfg.Engine.CEF.CEFWindowlessFrameRateMax(), | 86 | WindowlessFrameRateMax: cfg.Engine.CEF.CEFWindowlessFrameRateMax(), | ||
| 87 | - | EnableAudioHandler: cfg.Engine.CEF.EnableAudioHandler, | 87 | + | Input: cef.RuntimeInputConfig{ |
| 88 | - | TraceHandlers: cfg.Engine.CEF.TraceHandlers, | 88 | + | ScrollWheelMultiplier: cfg.Engine.CEF.Input.ScrollWheelMultiplier, |
| 89 | - | ApplicationScale: cfg.DefaultUIScale, | 89 | + | ScrollTouchpadMultiplier: cfg.Engine.CEF.Input.ScrollTouchpadMultiplier, |
| 90 | + | ScrollHorizontalMultiplier: cfg.Engine.CEF.Input.ScrollHorizontalMultiplier, | |||
| 91 | + | ScrollVerticalMultiplier: cfg.Engine.CEF.Input.ScrollVerticalMultiplier, | |||
| 92 | + | ScrollMaxDelta: cfg.Engine.CEF.Input.ScrollMaxDelta, | |||
| 93 | + | }, | |||
| 94 | + | EnableAudioHandler: cfg.Engine.CEF.EnableAudioHandler, | |||
| 95 | + | TraceHandlers: cfg.Engine.CEF.TraceHandlers, | |||
| 96 | + | ApplicationScale: cfg.DefaultUIScale, | |||
| 90 | } | 97 | } | ||
| 91 | deps := cef.EngineDependencies{ | 98 | deps := cef.EngineDependencies{ | ||
| 92 | RegisterHandlers: handlers.RegisterAll, | 99 | RegisterHandlers: handlers.RegisterAll, | ||
M internal/infrastructure/cef/engine_init.go +1 -0
| @@ -104,6 +104,7 @@ func NewEngine( | |||
| 104 | 104 | adaptiveWindowlessFrameRate: cfg.AdaptiveWindowlessFrameRate, | |
| 105 | 105 | windowlessFrameRate: windowlessFrameRate, | |
| 106 | 106 | windowlessFrameRateMax: cfg.WindowlessFrameRateMax, | |
| 107 | + | inputConfig: cfg.Input, | |
| 107 | 108 | audioOutputFactory: audioFactory, | |
| 108 | 109 | }, | |
| 109 | 110 | logger, | |
| @@ -104,6 +104,7 @@ func NewEngine( | |||||
| 104 | adaptiveWindowlessFrameRate: cfg.AdaptiveWindowlessFrameRate, | 104 | adaptiveWindowlessFrameRate: cfg.AdaptiveWindowlessFrameRate, | ||
| 105 | windowlessFrameRate: windowlessFrameRate, | 105 | windowlessFrameRate: windowlessFrameRate, | ||
| 106 | windowlessFrameRateMax: cfg.WindowlessFrameRateMax, | 106 | windowlessFrameRateMax: cfg.WindowlessFrameRateMax, | ||
| 107 | + | inputConfig: cfg.Input, | |||
| 107 | audioOutputFactory: audioFactory, | 108 | audioOutputFactory: audioFactory, | ||
| 108 | }, | 109 | }, | ||
| 109 | logger, | 110 | logger, | ||
M internal/infrastructure/cef/factory.go +4 -0
| @@ -25,6 +25,7 @@ type WebViewFactory struct { | |||
| 25 | 25 | adaptiveWindowlessFrameRate bool | |
| 26 | 26 | windowlessFrameRate int32 | |
| 27 | 27 | windowlessFrameRateMax int32 | |
| 28 | + | inputConfig RuntimeInputConfig | |
| 28 | 29 | bgColor atomic.Uint32 // packed ARGB for BrowserSettings.BackgroundColor | |
| 29 | 30 | audioOutputFactory port.AudioOutputFactory | |
| 30 | 31 | } | |
| @@ -33,6 +34,7 @@ type webViewFactoryOptions struct { | |||
| 33 | 34 | adaptiveWindowlessFrameRate bool | |
| 34 | 35 | windowlessFrameRate int32 | |
| 35 | 36 | windowlessFrameRateMax int32 | |
| 37 | + | inputConfig RuntimeInputConfig | |
| 36 | 38 | audioOutputFactory port.AudioOutputFactory | |
| 37 | 39 | } | |
| 38 | 40 | ||
| @@ -61,6 +63,7 @@ func newWebViewFactory(engine *Engine, opts webViewFactoryOptions) *WebViewFacto | |||
| 61 | 63 | adaptiveWindowlessFrameRate: opts.adaptiveWindowlessFrameRate, | |
| 62 | 64 | windowlessFrameRate: opts.windowlessFrameRate, | |
| 63 | 65 | windowlessFrameRateMax: opts.windowlessFrameRateMax, | |
| 66 | + | inputConfig: opts.inputConfig, | |
| 64 | 67 | audioOutputFactory: opts.audioOutputFactory, | |
| 65 | 68 | } | |
| 66 | 69 | } | |
| @@ -122,6 +125,7 @@ func (f *WebViewFactory) newWebView(ctx context.Context) (*WebView, error) { | |||
| 122 | 125 | adaptiveWindowlessFrameRate: f.adaptiveWindowlessFrameRate, | |
| 123 | 126 | windowlessFrameRate: f.windowlessFrameRate, | |
| 124 | 127 | windowlessFrameRateMax: f.windowlessFrameRateMax, | |
| 128 | + | inputConfig: f.inputConfig, | |
| 125 | 129 | backgroundColor: f.bgColor.Load(), | |
| 126 | 130 | } | |
| 127 | 131 | wv.runOnGTKSync(func() { | |
| @@ -25,6 +25,7 @@ type WebViewFactory struct { | |||||
| 25 | adaptiveWindowlessFrameRate bool | 25 | adaptiveWindowlessFrameRate bool | ||
| 26 | windowlessFrameRate int32 | 26 | windowlessFrameRate int32 | ||
| 27 | windowlessFrameRateMax int32 | 27 | windowlessFrameRateMax int32 | ||
| 28 | + | inputConfig RuntimeInputConfig | |||
| 28 | bgColor atomic.Uint32 // packed ARGB for BrowserSettings.BackgroundColor | 29 | bgColor atomic.Uint32 // packed ARGB for BrowserSettings.BackgroundColor | ||
| 29 | audioOutputFactory port.AudioOutputFactory | 30 | audioOutputFactory port.AudioOutputFactory | ||
| 30 | } | 31 | } | ||
| @@ -33,6 +34,7 @@ type webViewFactoryOptions struct { | |||||
| 33 | adaptiveWindowlessFrameRate bool | 34 | adaptiveWindowlessFrameRate bool | ||
| 34 | windowlessFrameRate int32 | 35 | windowlessFrameRate int32 | ||
| 35 | windowlessFrameRateMax int32 | 36 | windowlessFrameRateMax int32 | ||
| 37 | + | inputConfig RuntimeInputConfig | |||
| 36 | audioOutputFactory port.AudioOutputFactory | 38 | audioOutputFactory port.AudioOutputFactory | ||
| 37 | } | 39 | } | ||
| 38 | 40 | ||||
| @@ -61,6 +63,7 @@ func newWebViewFactory(engine *Engine, opts webViewFactoryOptions) *WebViewFacto | |||||
| 61 | adaptiveWindowlessFrameRate: opts.adaptiveWindowlessFrameRate, | 63 | adaptiveWindowlessFrameRate: opts.adaptiveWindowlessFrameRate, | ||
| 62 | windowlessFrameRate: opts.windowlessFrameRate, | 64 | windowlessFrameRate: opts.windowlessFrameRate, | ||
| 63 | windowlessFrameRateMax: opts.windowlessFrameRateMax, | 65 | windowlessFrameRateMax: opts.windowlessFrameRateMax, | ||
| 66 | + | inputConfig: opts.inputConfig, | |||
| 64 | audioOutputFactory: opts.audioOutputFactory, | 67 | audioOutputFactory: opts.audioOutputFactory, | ||
| 65 | } | 68 | } | ||
| 66 | } | 69 | } | ||
| @@ -122,6 +125,7 @@ func (f *WebViewFactory) newWebView(ctx context.Context) (*WebView, error) { | |||||
| 122 | adaptiveWindowlessFrameRate: f.adaptiveWindowlessFrameRate, | 125 | adaptiveWindowlessFrameRate: f.adaptiveWindowlessFrameRate, | ||
| 123 | windowlessFrameRate: f.windowlessFrameRate, | 126 | windowlessFrameRate: f.windowlessFrameRate, | ||
| 124 | windowlessFrameRateMax: f.windowlessFrameRateMax, | 127 | windowlessFrameRateMax: f.windowlessFrameRateMax, | ||
| 128 | + | inputConfig: f.inputConfig, | |||
| 125 | backgroundColor: f.bgColor.Load(), | 129 | backgroundColor: f.bgColor.Load(), | ||
| 126 | } | 130 | } | ||
| 127 | wv.runOnGTKSync(func() { | 131 | wv.runOnGTKSync(func() { | ||
M internal/infrastructure/cef/runtime_config.go +9 -0
| @@ -22,11 +22,20 @@ type RuntimeConfig struct { | |||
| 22 | 22 | AdaptiveWindowlessFrameRate bool | |
| 23 | 23 | WindowlessFrameRate int32 | |
| 24 | 24 | WindowlessFrameRateMax int32 | |
| 25 | + | Input RuntimeInputConfig | |
| 25 | 26 | EnableAudioHandler bool | |
| 26 | 27 | TraceHandlers bool | |
| 27 | 28 | ApplicationScale float64 | |
| 28 | 29 | } | |
| 29 | 30 | ||
| 31 | + | type RuntimeInputConfig struct { | |
| 32 | + | ScrollWheelMultiplier float64 | |
| 33 | + | ScrollTouchpadMultiplier float64 | |
| 34 | + | ScrollHorizontalMultiplier float64 | |
| 35 | + | ScrollVerticalMultiplier float64 | |
| 36 | + | ScrollMaxDelta int32 | |
| 37 | + | } | |
| 38 | + | ||
| 30 | 39 | type HandlerRegistrar func(context.Context, port.WebUIHandlerRouter, port.HandlerDependencies) error | |
| 31 | 40 | ||
| 32 | 41 | type AccentHandlerRegistrar func(context.Context, port.WebUIHandlerRouter, port.AccentKeyHandler) error | |
| @@ -22,11 +22,20 @@ type RuntimeConfig struct { | |||||
| 22 | AdaptiveWindowlessFrameRate bool | 22 | AdaptiveWindowlessFrameRate bool | ||
| 23 | WindowlessFrameRate int32 | 23 | WindowlessFrameRate int32 | ||
| 24 | WindowlessFrameRateMax int32 | 24 | WindowlessFrameRateMax int32 | ||
| 25 | + | Input RuntimeInputConfig | |||
| 25 | EnableAudioHandler bool | 26 | EnableAudioHandler bool | ||
| 26 | TraceHandlers bool | 27 | TraceHandlers bool | ||
| 27 | ApplicationScale float64 | 28 | ApplicationScale float64 | ||
| 28 | } | 29 | } | ||
| 29 | 30 | ||||
| 31 | + | type RuntimeInputConfig struct { | |||
| 32 | + | ScrollWheelMultiplier float64 | |||
| 33 | + | ScrollTouchpadMultiplier float64 | |||
| 34 | + | ScrollHorizontalMultiplier float64 | |||
| 35 | + | ScrollVerticalMultiplier float64 | |||
| 36 | + | ScrollMaxDelta int32 | |||
| 37 | + | } | |||
| 38 | + | ||||
| 30 | type HandlerRegistrar func(context.Context, port.WebUIHandlerRouter, port.HandlerDependencies) error | 39 | type HandlerRegistrar func(context.Context, port.WebUIHandlerRouter, port.HandlerDependencies) error | ||
| 31 | 40 | ||||
| 32 | type AccentHandlerRegistrar func(context.Context, port.WebUIHandlerRouter, port.AccentKeyHandler) error | 41 | type AccentHandlerRegistrar func(context.Context, port.WebUIHandlerRouter, port.AccentKeyHandler) error | ||
M internal/infrastructure/cef/webview.go +8 -0
| @@ -204,6 +204,7 @@ type WebView struct { | |||
| 204 | 204 | // can apply the same settings in OnBeforePopup. | |
| 205 | 205 | windowlessFrameRate int32 | |
| 206 | 206 | backgroundColor uint32 | |
| 207 | + | inputConfig RuntimeInputConfig | |
| 207 | 208 | ||
| 208 | 209 | // Audio output factory and active stream. | |
| 209 | 210 | audioOutputFactory port.AudioOutputFactory | |
| @@ -1428,6 +1429,13 @@ func (wv *WebView) bridgeInputOptions() cef2gtk.InputOptions { | |||
| 1428 | 1429 | } | |
| 1429 | 1430 | return cef2gtk.InputOptions{ | |
| 1430 | 1431 | Scale: 0, | |
| 1432 | + | Scroll: cef2gtk.ScrollOptions{ | |
| 1433 | + | WheelMultiplier: wv.inputConfig.ScrollWheelMultiplier, | |
| 1434 | + | TouchpadMultiplier: wv.inputConfig.ScrollTouchpadMultiplier, | |
| 1435 | + | HorizontalMultiplier: wv.inputConfig.ScrollHorizontalMultiplier, | |
| 1436 | + | VerticalMultiplier: wv.inputConfig.ScrollVerticalMultiplier, | |
| 1437 | + | MaxDelta: wv.inputConfig.ScrollMaxDelta, | |
| 1438 | + | }, | |
| 1431 | 1439 | OnMiddleClick: func(_, _ float64) bool { | |
| 1432 | 1440 | return wv.handleMiddleClickFromBridge() | |
| 1433 | 1441 | }, | |
| @@ -204,6 +204,7 @@ type WebView struct { | |||||
| 204 | // can apply the same settings in OnBeforePopup. | 204 | // can apply the same settings in OnBeforePopup. | ||
| 205 | windowlessFrameRate int32 | 205 | windowlessFrameRate int32 | ||
| 206 | backgroundColor uint32 | 206 | backgroundColor uint32 | ||
| 207 | + | inputConfig RuntimeInputConfig | |||
| 207 | 208 | ||||
| 208 | // Audio output factory and active stream. | 209 | // Audio output factory and active stream. | ||
| 209 | audioOutputFactory port.AudioOutputFactory | 210 | audioOutputFactory port.AudioOutputFactory | ||
| @@ -1428,6 +1429,13 @@ func (wv *WebView) bridgeInputOptions() cef2gtk.InputOptions { | |||||
| 1428 | } | 1429 | } | ||
| 1429 | return cef2gtk.InputOptions{ | 1430 | return cef2gtk.InputOptions{ | ||
| 1430 | Scale: 0, | 1431 | Scale: 0, | ||
| 1432 | + | Scroll: cef2gtk.ScrollOptions{ | |||
| 1433 | + | WheelMultiplier: wv.inputConfig.ScrollWheelMultiplier, | |||
| 1434 | + | TouchpadMultiplier: wv.inputConfig.ScrollTouchpadMultiplier, | |||
| 1435 | + | HorizontalMultiplier: wv.inputConfig.ScrollHorizontalMultiplier, | |||
| 1436 | + | VerticalMultiplier: wv.inputConfig.ScrollVerticalMultiplier, | |||
| 1437 | + | MaxDelta: wv.inputConfig.ScrollMaxDelta, | |||
| 1438 | + | }, | |||
| 1431 | OnMiddleClick: func(_, _ float64) bool { | 1439 | OnMiddleClick: func(_, _ float64) bool { | ||
| 1432 | return wv.handleMiddleClickFromBridge() | 1440 | return wv.handleMiddleClickFromBridge() | ||
| 1433 | }, | 1441 | }, | ||
M internal/infrastructure/cef/webview_input_test.go +15 -1
| @@ -9,11 +9,25 @@ import ( | |||
| 9 | 9 | ||
| 10 | 10 | func TestWebViewBridgeInputOptions_LeavesTargetSelectionToAdapter(t *testing.T) { | |
| 11 | 11 | nativeWidget := >k.Widget{} | |
| 12 | - | wv := &WebView{nativeWidget: nativeWidget} | |
| 12 | + | wv := &WebView{ | |
| 13 | + | nativeWidget: nativeWidget, | |
| 14 | + | inputConfig: RuntimeInputConfig{ | |
| 15 | + | ScrollWheelMultiplier: 1.25, | |
| 16 | + | ScrollTouchpadMultiplier: 0.35, | |
| 17 | + | ScrollHorizontalMultiplier: 0.75, | |
| 18 | + | ScrollVerticalMultiplier: 1.5, | |
| 19 | + | ScrollMaxDelta: 120, | |
| 20 | + | }, | |
| 21 | + | } | |
| 13 | 22 | ||
| 14 | 23 | opts := wv.bridgeInputOptions() | |
| 15 | 24 | ||
| 16 | 25 | require.Zero(t, opts.Scale) | |
| 26 | + | require.InDelta(t, 1.25, opts.Scroll.WheelMultiplier, 0.001) | |
| 27 | + | require.InDelta(t, 0.35, opts.Scroll.TouchpadMultiplier, 0.001) | |
| 28 | + | require.InDelta(t, 0.75, opts.Scroll.HorizontalMultiplier, 0.001) | |
| 29 | + | require.InDelta(t, 1.5, opts.Scroll.VerticalMultiplier, 0.001) | |
| 30 | + | require.Equal(t, int32(120), opts.Scroll.MaxDelta) | |
| 17 | 31 | require.NotNil(t, opts.OnMiddleClick) | |
| 18 | 32 | require.NotNil(t, opts.SelectionText) | |
| 19 | 33 | require.NotNil(t, opts.OnClipboardShortcut) | |
| @@ -9,11 +9,25 @@ import ( | |||||
| 9 | 9 | ||||
| 10 | func TestWebViewBridgeInputOptions_LeavesTargetSelectionToAdapter(t *testing.T) { | 10 | func TestWebViewBridgeInputOptions_LeavesTargetSelectionToAdapter(t *testing.T) { | ||
| 11 | nativeWidget := >k.Widget{} | 11 | nativeWidget := >k.Widget{} | ||
| 12 | - | wv := &WebView{nativeWidget: nativeWidget} | 12 | + | wv := &WebView{ |
| 13 | + | nativeWidget: nativeWidget, | |||
| 14 | + | inputConfig: RuntimeInputConfig{ | |||
| 15 | + | ScrollWheelMultiplier: 1.25, | |||
| 16 | + | ScrollTouchpadMultiplier: 0.35, | |||
| 17 | + | ScrollHorizontalMultiplier: 0.75, | |||
| 18 | + | ScrollVerticalMultiplier: 1.5, | |||
| 19 | + | ScrollMaxDelta: 120, | |||
| 20 | + | }, | |||
| 21 | + | } | |||
| 13 | 22 | ||||
| 14 | opts := wv.bridgeInputOptions() | 23 | opts := wv.bridgeInputOptions() | ||
| 15 | 24 | ||||
| 16 | require.Zero(t, opts.Scale) | 25 | require.Zero(t, opts.Scale) | ||
| 26 | + | require.InDelta(t, 1.25, opts.Scroll.WheelMultiplier, 0.001) | |||
| 27 | + | require.InDelta(t, 0.35, opts.Scroll.TouchpadMultiplier, 0.001) | |||
| 28 | + | require.InDelta(t, 0.75, opts.Scroll.HorizontalMultiplier, 0.001) | |||
| 29 | + | require.InDelta(t, 1.5, opts.Scroll.VerticalMultiplier, 0.001) | |||
| 30 | + | require.Equal(t, int32(120), opts.Scroll.MaxDelta) | |||
| 17 | require.NotNil(t, opts.OnMiddleClick) | 31 | require.NotNil(t, opts.OnMiddleClick) | ||
| 18 | require.NotNil(t, opts.SelectionText) | 32 | require.NotNil(t, opts.SelectionText) | ||
| 19 | require.NotNil(t, opts.OnClipboardShortcut) | 33 | require.NotNil(t, opts.OnClipboardShortcut) | ||
M internal/infrastructure/config/defaults.go +14 -4
| @@ -67,10 +67,13 @@ const ( | |||
| 67 | 67 | defaultUIScale = 1.0 // UI scale multiplier (1.0 = 100%, 1.2 = 120%) | |
| 68 | 68 | ||
| 69 | 69 | // 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 | |
| 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 | |
| 74 | 77 | ||
| 75 | 78 | // Skia threading defaults (0 = unset, -1 = unset for GPU threads) | |
| 76 | 79 | defaultSkiaCPUPaintingThreads = 0 | |
| @@ -178,6 +181,13 @@ func DefaultConfig() *Config { | |||
| 178 | 181 | WindowlessFrameRateMax: defaultCEFWindowlessFrameRateMax, | |
| 179 | 182 | LogFile: "", | |
| 180 | 183 | EnableAudioHandler: true, | |
| 184 | + | Input: CEFInputConfig{ | |
| 185 | + | ScrollWheelMultiplier: defaultCEFScrollMultiplier, | |
| 186 | + | ScrollTouchpadMultiplier: defaultCEFScrollTouchpadMultiplier, | |
| 187 | + | ScrollHorizontalMultiplier: defaultCEFScrollMultiplier, | |
| 188 | + | ScrollVerticalMultiplier: defaultCEFScrollMultiplier, | |
| 189 | + | ScrollMaxDelta: defaultCEFScrollMaxDelta, | |
| 190 | + | }, | |
| 181 | 191 | }, | |
| 182 | 192 | WebKit: WebKitEngineConfig{ | |
| 183 | 193 | ITPEnabled: true, | |
| @@ -67,10 +67,13 @@ 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 defaults | 69 | // 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 startup | 71 | + | defaultWebViewPoolPrewarmCount = 4 // WebViews to pre-create at startup |
| 72 | - | defaultCEFWindowlessFrameRate = 60 // static OSR frame rate when adaptive CEF pacing is disabled | 72 | + | defaultCEFWindowlessFrameRate = 60 // static OSR frame rate when adaptive CEF pacing is disabled |
| 73 | - | defaultCEFWindowlessFrameRateMax = 240 // adaptive OSR frame-rate hard cap | 73 | + | defaultCEFWindowlessFrameRateMax = 240 // adaptive OSR frame-rate hard cap |
| 74 | + | defaultCEFScrollMultiplier = 1.0 | |||
| 75 | + | defaultCEFScrollTouchpadMultiplier = 0.35 | |||
| 76 | + | defaultCEFScrollMaxDelta = 0 | |||
| 74 | 77 | ||||
| 75 | // Skia threading defaults (0 = unset, -1 = unset for GPU threads) | 78 | // Skia threading defaults (0 = unset, -1 = unset for GPU threads) | ||
| 76 | defaultSkiaCPUPaintingThreads = 0 | 79 | defaultSkiaCPUPaintingThreads = 0 | ||
| @@ -178,6 +181,13 @@ func DefaultConfig() *Config { | |||||
| 178 | WindowlessFrameRateMax: defaultCEFWindowlessFrameRateMax, | 181 | WindowlessFrameRateMax: defaultCEFWindowlessFrameRateMax, | ||
| 179 | LogFile: "", | 182 | LogFile: "", | ||
| 180 | EnableAudioHandler: true, | 183 | EnableAudioHandler: true, | ||
| 184 | + | Input: CEFInputConfig{ | |||
| 185 | + | ScrollWheelMultiplier: defaultCEFScrollMultiplier, | |||
| 186 | + | ScrollTouchpadMultiplier: defaultCEFScrollTouchpadMultiplier, | |||
| 187 | + | ScrollHorizontalMultiplier: defaultCEFScrollMultiplier, | |||
| 188 | + | ScrollVerticalMultiplier: defaultCEFScrollMultiplier, | |||
| 189 | + | ScrollMaxDelta: defaultCEFScrollMaxDelta, | |||
| 190 | + | }, | |||
| 181 | }, | 191 | }, | ||
| 182 | WebKit: WebKitEngineConfig{ | 192 | WebKit: WebKitEngineConfig{ | ||
| 183 | ITPEnabled: true, | 193 | ITPEnabled: true, | ||
M internal/infrastructure/config/defaults_test.go +5 -0
| @@ -25,6 +25,11 @@ func TestDefaultConfig_CoreDefaults(t *testing.T) { | |||
| 25 | 25 | assert.Empty(t, cfg.Engine.CEF.LogFile) | |
| 26 | 26 | assert.True(t, cfg.Engine.CEF.EnableAudioHandler) | |
| 27 | 27 | assert.False(t, cfg.Engine.CEF.TraceHandlers) | |
| 28 | + | assert.InDelta(t, defaultCEFScrollMultiplier, cfg.Engine.CEF.Input.ScrollWheelMultiplier, 0.001) | |
| 29 | + | assert.InDelta(t, defaultCEFScrollTouchpadMultiplier, cfg.Engine.CEF.Input.ScrollTouchpadMultiplier, 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) | |
| 32 | + | assert.Equal(t, int32(defaultCEFScrollMaxDelta), cfg.Engine.CEF.Input.ScrollMaxDelta) | |
| 28 | 33 | assert.True(t, cfg.Engine.WebKit.ITPEnabled) | |
| 29 | 34 | ||
| 30 | 35 | // System view shortcuts are first-class global actions. | |
| @@ -25,6 +25,11 @@ func TestDefaultConfig_CoreDefaults(t *testing.T) { | |||||
| 25 | assert.Empty(t, cfg.Engine.CEF.LogFile) | 25 | assert.Empty(t, cfg.Engine.CEF.LogFile) | ||
| 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) | |||
| 29 | + | assert.InDelta(t, defaultCEFScrollTouchpadMultiplier, cfg.Engine.CEF.Input.ScrollTouchpadMultiplier, 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) | |||
| 32 | + | assert.Equal(t, int32(defaultCEFScrollMaxDelta), cfg.Engine.CEF.Input.ScrollMaxDelta) | |||
| 28 | assert.True(t, cfg.Engine.WebKit.ITPEnabled) | 33 | assert.True(t, cfg.Engine.WebKit.ITPEnabled) | ||
| 29 | 34 | ||||
| 30 | // System view shortcuts are first-class global actions. | 35 | // System view shortcuts are first-class global actions. | ||
M internal/infrastructure/config/engine.go +11 -0
| @@ -110,12 +110,23 @@ type CEFEngineConfig struct { | |||
| 110 | 110 | WindowlessFrameRate int32 `mapstructure:"windowless_frame_rate" toml:"windowless_frame_rate" yaml:"windowless_frame_rate"` //nolint:lll // struct tags exceed lll limit | |
| 111 | 111 | // WindowlessFrameRateMax is the hard cap for adaptive monitor refresh rates. | |
| 112 | 112 | WindowlessFrameRateMax int32 `mapstructure:"windowless_frame_rate_max" toml:"windowless_frame_rate_max" yaml:"windowless_frame_rate_max"` //nolint:lll // struct tags exceed lll limit | |
| 113 | + | // Input controls GTK input translation before events are forwarded to CEF. | |
| 114 | + | Input CEFInputConfig `mapstructure:"input" toml:"input" yaml:"input"` | |
| 113 | 115 | // EnableAudioHandler opts into the experimental CEF AudioHandler bridge. | |
| 114 | 116 | EnableAudioHandler bool `mapstructure:"enable_audio_handler" toml:"enable_audio_handler" yaml:"enable_audio_handler"` | |
| 115 | 117 | // TraceHandlers enables purego-cef handler/refcount tracing for diagnostics. | |
| 116 | 118 | TraceHandlers bool `mapstructure:"trace_handlers" toml:"trace_handlers" yaml:"trace_handlers"` | |
| 117 | 119 | } | |
| 118 | 120 | ||
| 121 | + | // CEFInputConfig holds CEF-specific input translation options. | |
| 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 limit | |
| 124 | + | ScrollTouchpadMultiplier float64 `mapstructure:"scroll_touchpad_multiplier" toml:"scroll_touchpad_multiplier" yaml:"scroll_touchpad_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 limit | |
| 126 | + | 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 limit | |
| 128 | + | } | |
| 129 | + | ||
| 119 | 130 | // CEFRenderStack returns the effective CEF GPU render stack. | |
| 120 | 131 | func (c CEFEngineConfig) CEFRenderStack() CEFRenderStack { | |
| 121 | 132 | if c.RenderStack != "" { | |
| @@ -110,12 +110,23 @@ type CEFEngineConfig struct { | |||||
| 110 | WindowlessFrameRate int32 `mapstructure:"windowless_frame_rate" toml:"windowless_frame_rate" yaml:"windowless_frame_rate"` //nolint:lll // struct tags exceed lll limit | 110 | WindowlessFrameRate int32 `mapstructure:"windowless_frame_rate" toml:"windowless_frame_rate" yaml:"windowless_frame_rate"` //nolint:lll // struct tags exceed lll limit | ||
| 111 | // WindowlessFrameRateMax is the hard cap for adaptive monitor refresh rates. | 111 | // WindowlessFrameRateMax is the hard cap for adaptive monitor refresh rates. | ||
| 112 | WindowlessFrameRateMax int32 `mapstructure:"windowless_frame_rate_max" toml:"windowless_frame_rate_max" yaml:"windowless_frame_rate_max"` //nolint:lll // struct tags exceed lll limit | 112 | WindowlessFrameRateMax int32 `mapstructure:"windowless_frame_rate_max" toml:"windowless_frame_rate_max" yaml:"windowless_frame_rate_max"` //nolint:lll // struct tags exceed lll limit | ||
| 113 | + | // Input controls GTK input translation before events are forwarded to CEF. | |||
| 114 | + | Input CEFInputConfig `mapstructure:"input" toml:"input" yaml:"input"` | |||
| 113 | // EnableAudioHandler opts into the experimental CEF AudioHandler bridge. | 115 | // EnableAudioHandler opts into the experimental CEF AudioHandler bridge. | ||
| 114 | EnableAudioHandler bool `mapstructure:"enable_audio_handler" toml:"enable_audio_handler" yaml:"enable_audio_handler"` | 116 | EnableAudioHandler bool `mapstructure:"enable_audio_handler" toml:"enable_audio_handler" yaml:"enable_audio_handler"` | ||
| 115 | // TraceHandlers enables purego-cef handler/refcount tracing for diagnostics. | 117 | // TraceHandlers enables purego-cef handler/refcount tracing for diagnostics. | ||
| 116 | TraceHandlers bool `mapstructure:"trace_handlers" toml:"trace_handlers" yaml:"trace_handlers"` | 118 | TraceHandlers bool `mapstructure:"trace_handlers" toml:"trace_handlers" yaml:"trace_handlers"` | ||
| 117 | } | 119 | } | ||
| 118 | 120 | ||||
| 121 | + | // CEFInputConfig holds CEF-specific input translation options. | |||
| 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 limit | |||
| 124 | + | ScrollTouchpadMultiplier float64 `mapstructure:"scroll_touchpad_multiplier" toml:"scroll_touchpad_multiplier" yaml:"scroll_touchpad_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 limit | |||
| 126 | + | 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 limit | |||
| 128 | + | } | |||
| 129 | + | ||||
| 119 | // CEFRenderStack returns the effective CEF GPU render stack. | 130 | // CEFRenderStack returns the effective CEF GPU render stack. | ||
| 120 | func (c CEFEngineConfig) CEFRenderStack() CEFRenderStack { | 131 | func (c CEFEngineConfig) CEFRenderStack() CEFRenderStack { | ||
| 121 | if c.RenderStack != "" { | 132 | if c.RenderStack != "" { | ||
M internal/infrastructure/config/loader.go +5 -0
| @@ -746,6 +746,11 @@ func (m *Manager) setEngineDefaults(defaults *Config) { | |||
| 746 | 746 | m.viper.SetDefault("engine.cef.windowless_frame_rate_max", ce.CEFWindowlessFrameRateMax()) | |
| 747 | 747 | m.viper.SetDefault("engine.cef.enable_audio_handler", ce.EnableAudioHandler) | |
| 748 | 748 | m.viper.SetDefault("engine.cef.trace_handlers", ce.TraceHandlers) | |
| 749 | + | m.viper.SetDefault("engine.cef.input.scroll_wheel_multiplier", ce.Input.ScrollWheelMultiplier) | |
| 750 | + | m.viper.SetDefault("engine.cef.input.scroll_touchpad_multiplier", ce.Input.ScrollTouchpadMultiplier) | |
| 751 | + | m.viper.SetDefault("engine.cef.input.scroll_horizontal_multiplier", ce.Input.ScrollHorizontalMultiplier) | |
| 752 | + | m.viper.SetDefault("engine.cef.input.scroll_vertical_multiplier", ce.Input.ScrollVerticalMultiplier) | |
| 753 | + | m.viper.SetDefault("engine.cef.input.scroll_max_delta", ce.Input.ScrollMaxDelta) | |
| 749 | 754 | ||
| 750 | 755 | wk := e.WebKit | |
| 751 | 756 | m.viper.SetDefault("engine.webkit.itp_enabled", wk.ITPEnabled) | |
| @@ -746,6 +746,11 @@ func (m *Manager) setEngineDefaults(defaults *Config) { | |||||
| 746 | m.viper.SetDefault("engine.cef.windowless_frame_rate_max", ce.CEFWindowlessFrameRateMax()) | 746 | m.viper.SetDefault("engine.cef.windowless_frame_rate_max", ce.CEFWindowlessFrameRateMax()) | ||
| 747 | m.viper.SetDefault("engine.cef.enable_audio_handler", ce.EnableAudioHandler) | 747 | m.viper.SetDefault("engine.cef.enable_audio_handler", ce.EnableAudioHandler) | ||
| 748 | m.viper.SetDefault("engine.cef.trace_handlers", ce.TraceHandlers) | 748 | m.viper.SetDefault("engine.cef.trace_handlers", ce.TraceHandlers) | ||
| 749 | + | m.viper.SetDefault("engine.cef.input.scroll_wheel_multiplier", ce.Input.ScrollWheelMultiplier) | |||
| 750 | + | m.viper.SetDefault("engine.cef.input.scroll_touchpad_multiplier", ce.Input.ScrollTouchpadMultiplier) | |||
| 751 | + | m.viper.SetDefault("engine.cef.input.scroll_horizontal_multiplier", ce.Input.ScrollHorizontalMultiplier) | |||
| 752 | + | m.viper.SetDefault("engine.cef.input.scroll_vertical_multiplier", ce.Input.ScrollVerticalMultiplier) | |||
| 753 | + | m.viper.SetDefault("engine.cef.input.scroll_max_delta", ce.Input.ScrollMaxDelta) | |||
| 749 | 754 | ||||
| 750 | wk := e.WebKit | 755 | wk := e.WebKit | ||
| 751 | m.viper.SetDefault("engine.webkit.itp_enabled", wk.ITPEnabled) | 756 | m.viper.SetDefault("engine.webkit.itp_enabled", wk.ITPEnabled) | ||
M internal/infrastructure/config/loader_test.go +5 -0
| @@ -17,6 +17,11 @@ func TestSetEngineDefaults(t *testing.T) { | |||
| 17 | 17 | assert.Equal(t, "always", mgr.viper.GetString("engine.cookie_policy")) | |
| 18 | 18 | assert.Equal(t, 4, mgr.viper.GetInt("engine.pool_prewarm_count")) | |
| 19 | 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) | |
| 21 | + | assert.InDelta(t, defaultCEFScrollTouchpadMultiplier, mgr.viper.GetFloat64("engine.cef.input.scroll_touchpad_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) | |
| 24 | + | assert.Equal(t, defaultCEFScrollMaxDelta, mgr.viper.GetInt("engine.cef.input.scroll_max_delta")) | |
| 20 | 25 | ||
| 21 | 26 | // WebKit-specific engine fields | |
| 22 | 27 | assert.True(t, mgr.viper.GetBool("engine.webkit.itp_enabled")) | |
| @@ -17,6 +17,11 @@ func TestSetEngineDefaults(t *testing.T) { | |||||
| 17 | assert.Equal(t, "always", mgr.viper.GetString("engine.cookie_policy")) | 17 | assert.Equal(t, "always", mgr.viper.GetString("engine.cookie_policy")) | ||
| 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) | |||
| 21 | + | assert.InDelta(t, defaultCEFScrollTouchpadMultiplier, mgr.viper.GetFloat64("engine.cef.input.scroll_touchpad_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) | |||
| 24 | + | assert.Equal(t, defaultCEFScrollMaxDelta, mgr.viper.GetInt("engine.cef.input.scroll_max_delta")) | |||
| 20 | 25 | ||||
| 21 | // WebKit-specific engine fields | 26 | // WebKit-specific engine fields | ||
| 22 | assert.True(t, mgr.viper.GetBool("engine.webkit.itp_enabled")) | 27 | assert.True(t, mgr.viper.GetBool("engine.webkit.itp_enabled")) | ||
M internal/infrastructure/config/schema_provider.go +40 -0
| @@ -962,6 +962,46 @@ func (*SchemaProvider) getPerformanceKeys(defaults *Config) []entity.ConfigKeyIn | |||
| 962 | 962 | Range: ">=0", | |
| 963 | 963 | Section: SectionPerformance, | |
| 964 | 964 | }, | |
| 965 | + | { | |
| 966 | + | Key: "engine.cef.input.scroll_wheel_multiplier", | |
| 967 | + | Type: "float64", | |
| 968 | + | Default: fmt.Sprintf("%.2f", defaults.Engine.CEF.Input.ScrollWheelMultiplier), | |
| 969 | + | Description: "CEF mouse wheel scroll delta multiplier", | |
| 970 | + | Range: ">0", | |
| 971 | + | Section: SectionPerformance, | |
| 972 | + | }, | |
| 973 | + | { | |
| 974 | + | Key: "engine.cef.input.scroll_touchpad_multiplier", | |
| 975 | + | Type: "float64", | |
| 976 | + | Default: fmt.Sprintf("%.2f", defaults.Engine.CEF.Input.ScrollTouchpadMultiplier), | |
| 977 | + | Description: "CEF touchpad/smooth scroll delta multiplier", | |
| 978 | + | Range: ">0", | |
| 979 | + | Section: SectionPerformance, | |
| 980 | + | }, | |
| 981 | + | { | |
| 982 | + | Key: "engine.cef.input.scroll_horizontal_multiplier", | |
| 983 | + | Type: "float64", | |
| 984 | + | Default: fmt.Sprintf("%.2f", defaults.Engine.CEF.Input.ScrollHorizontalMultiplier), | |
| 985 | + | Description: "CEF horizontal scroll delta multiplier", | |
| 986 | + | Range: ">0", | |
| 987 | + | Section: SectionPerformance, | |
| 988 | + | }, | |
| 989 | + | { | |
| 990 | + | Key: "engine.cef.input.scroll_vertical_multiplier", | |
| 991 | + | Type: "float64", | |
| 992 | + | Default: fmt.Sprintf("%.2f", defaults.Engine.CEF.Input.ScrollVerticalMultiplier), | |
| 993 | + | Description: "CEF vertical scroll delta multiplier", | |
| 994 | + | Range: ">0", | |
| 995 | + | Section: SectionPerformance, | |
| 996 | + | }, | |
| 997 | + | { | |
| 998 | + | Key: "engine.cef.input.scroll_max_delta", | |
| 999 | + | Type: "int32", | |
| 1000 | + | Default: fmt.Sprintf("%d", defaults.Engine.CEF.Input.ScrollMaxDelta), | |
| 1001 | + | Description: "CEF maximum absolute scroll delta after scaling; 0 disables clamping", | |
| 1002 | + | Range: ">=0", | |
| 1003 | + | Section: SectionPerformance, | |
| 1004 | + | }, | |
| 965 | 1005 | { | |
| 966 | 1006 | Key: "engine.webkit.skia_cpu_painting_threads", | |
| 967 | 1007 | Type: "int", | |
| @@ -962,6 +962,46 @@ func (*SchemaProvider) getPerformanceKeys(defaults *Config) []entity.ConfigKeyIn | |||||
| 962 | Range: ">=0", | 962 | Range: ">=0", | ||
| 963 | Section: SectionPerformance, | 963 | Section: SectionPerformance, | ||
| 964 | }, | 964 | }, | ||
| 965 | + | { | |||
| 966 | + | Key: "engine.cef.input.scroll_wheel_multiplier", | |||
| 967 | + | Type: "float64", | |||
| 968 | + | Default: fmt.Sprintf("%.2f", defaults.Engine.CEF.Input.ScrollWheelMultiplier), | |||
| 969 | + | Description: "CEF mouse wheel scroll delta multiplier", | |||
| 970 | + | Range: ">0", | |||
| 971 | + | Section: SectionPerformance, | |||
| 972 | + | }, | |||
| 973 | + | { | |||
| 974 | + | Key: "engine.cef.input.scroll_touchpad_multiplier", | |||
| 975 | + | Type: "float64", | |||
| 976 | + | Default: fmt.Sprintf("%.2f", defaults.Engine.CEF.Input.ScrollTouchpadMultiplier), | |||
| 977 | + | Description: "CEF touchpad/smooth scroll delta multiplier", | |||
| 978 | + | Range: ">0", | |||
| 979 | + | Section: SectionPerformance, | |||
| 980 | + | }, | |||
| 981 | + | { | |||
| 982 | + | Key: "engine.cef.input.scroll_horizontal_multiplier", | |||
| 983 | + | Type: "float64", | |||
| 984 | + | Default: fmt.Sprintf("%.2f", defaults.Engine.CEF.Input.ScrollHorizontalMultiplier), | |||
| 985 | + | Description: "CEF horizontal scroll delta multiplier", | |||
| 986 | + | Range: ">0", | |||
| 987 | + | Section: SectionPerformance, | |||
| 988 | + | }, | |||
| 989 | + | { | |||
| 990 | + | Key: "engine.cef.input.scroll_vertical_multiplier", | |||
| 991 | + | Type: "float64", | |||
| 992 | + | Default: fmt.Sprintf("%.2f", defaults.Engine.CEF.Input.ScrollVerticalMultiplier), | |||
| 993 | + | Description: "CEF vertical scroll delta multiplier", | |||
| 994 | + | Range: ">0", | |||
| 995 | + | Section: SectionPerformance, | |||
| 996 | + | }, | |||
| 997 | + | { | |||
| 998 | + | Key: "engine.cef.input.scroll_max_delta", | |||
| 999 | + | Type: "int32", | |||
| 1000 | + | Default: fmt.Sprintf("%d", defaults.Engine.CEF.Input.ScrollMaxDelta), | |||
| 1001 | + | Description: "CEF maximum absolute scroll delta after scaling; 0 disables clamping", | |||
| 1002 | + | Range: ">=0", | |||
| 1003 | + | Section: SectionPerformance, | |||
| 1004 | + | }, | |||
| 965 | { | 1005 | { | ||
| 966 | Key: "engine.webkit.skia_cpu_painting_threads", | 1006 | Key: "engine.webkit.skia_cpu_painting_threads", | ||
| 967 | Type: "int", | 1007 | Type: "int", | ||
M internal/infrastructure/config/validation.go +22 -0
| @@ -2,6 +2,7 @@ package config | |||
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | 4 | "fmt" | |
| 5 | + | "math" | |
| 5 | 6 | "net/url" | |
| 6 | 7 | "sort" | |
| 7 | 8 | "strings" | |
| @@ -362,9 +363,30 @@ func validateCEF(config *Config) []string { | |||
| 362 | 363 | validationErrors = append(validationErrors, "engine.cef.windowless_frame_rate_max must be >= 0") | |
| 363 | 364 | } | |
| 364 | 365 | ||
| 366 | + | input := config.Engine.CEF.Input | |
| 367 | + | if invalidPositiveFloat(input.ScrollWheelMultiplier) { | |
| 368 | + | validationErrors = append(validationErrors, "engine.cef.input.scroll_wheel_multiplier must be > 0") | |
| 369 | + | } | |
| 370 | + | if invalidPositiveFloat(input.ScrollTouchpadMultiplier) { | |
| 371 | + | validationErrors = append(validationErrors, "engine.cef.input.scroll_touchpad_multiplier must be > 0") | |
| 372 | + | } | |
| 373 | + | if invalidPositiveFloat(input.ScrollHorizontalMultiplier) { | |
| 374 | + | validationErrors = append(validationErrors, "engine.cef.input.scroll_horizontal_multiplier must be > 0") | |
| 375 | + | } | |
| 376 | + | if invalidPositiveFloat(input.ScrollVerticalMultiplier) { | |
| 377 | + | validationErrors = append(validationErrors, "engine.cef.input.scroll_vertical_multiplier must be > 0") | |
| 378 | + | } | |
| 379 | + | if input.ScrollMaxDelta < 0 { | |
| 380 | + | validationErrors = append(validationErrors, "engine.cef.input.scroll_max_delta must be >= 0") | |
| 381 | + | } | |
| 382 | + | ||
| 365 | 383 | return validationErrors | |
| 366 | 384 | } | |
| 367 | 385 | ||
| 386 | + | func invalidPositiveFloat(value float64) bool { | |
| 387 | + | return value <= 0 || math.IsNaN(value) || math.IsInf(value, 0) | |
| 388 | + | } | |
| 389 | + | ||
| 368 | 390 | func validateWorkspaceNewPaneURL(config *Config) []string { | |
| 369 | 391 | var validationErrors []string | |
| 370 | 392 | ||
| @@ -2,6 +2,7 @@ package config | |||||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | "fmt" | 4 | "fmt" | ||
| 5 | + | "math" | |||
| 5 | "net/url" | 6 | "net/url" | ||
| 6 | "sort" | 7 | "sort" | ||
| 7 | "strings" | 8 | "strings" | ||
| @@ -362,9 +363,30 @@ func validateCEF(config *Config) []string { | |||||
| 362 | validationErrors = append(validationErrors, "engine.cef.windowless_frame_rate_max must be >= 0") | 363 | validationErrors = append(validationErrors, "engine.cef.windowless_frame_rate_max must be >= 0") | ||
| 363 | } | 364 | } | ||
| 364 | 365 | ||||
| 366 | + | input := config.Engine.CEF.Input | |||
| 367 | + | if invalidPositiveFloat(input.ScrollWheelMultiplier) { | |||
| 368 | + | validationErrors = append(validationErrors, "engine.cef.input.scroll_wheel_multiplier must be > 0") | |||
| 369 | + | } | |||
| 370 | + | if invalidPositiveFloat(input.ScrollTouchpadMultiplier) { | |||
| 371 | + | validationErrors = append(validationErrors, "engine.cef.input.scroll_touchpad_multiplier must be > 0") | |||
| 372 | + | } | |||
| 373 | + | if invalidPositiveFloat(input.ScrollHorizontalMultiplier) { | |||
| 374 | + | validationErrors = append(validationErrors, "engine.cef.input.scroll_horizontal_multiplier must be > 0") | |||
| 375 | + | } | |||
| 376 | + | if invalidPositiveFloat(input.ScrollVerticalMultiplier) { | |||
| 377 | + | validationErrors = append(validationErrors, "engine.cef.input.scroll_vertical_multiplier must be > 0") | |||
| 378 | + | } | |||
| 379 | + | if input.ScrollMaxDelta < 0 { | |||
| 380 | + | validationErrors = append(validationErrors, "engine.cef.input.scroll_max_delta must be >= 0") | |||
| 381 | + | } | |||
| 382 | + | ||||
| 365 | return validationErrors | 383 | return validationErrors | ||
| 366 | } | 384 | } | ||
| 367 | 385 | ||||
| 386 | + | func invalidPositiveFloat(value float64) bool { | |||
| 387 | + | return value <= 0 || math.IsNaN(value) || math.IsInf(value, 0) | |||
| 388 | + | } | |||
| 389 | + | ||||
| 368 | func validateWorkspaceNewPaneURL(config *Config) []string { | 390 | func validateWorkspaceNewPaneURL(config *Config) []string { | ||
| 369 | var validationErrors []string | 391 | var validationErrors []string | ||
| 370 | 392 | ||||
M internal/infrastructure/config/validation_test.go +64 -0
| @@ -1,6 +1,7 @@ | |||
| 1 | 1 | package config | |
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | + | "math" | |
| 4 | 5 | "testing" | |
| 5 | 6 | ||
| 6 | 7 | "github.com/stretchr/testify/assert" | |
| @@ -71,6 +72,69 @@ func TestValidateConfig_CEFConfig(t *testing.T) { | |||
| 71 | 72 | wantErr: true, | |
| 72 | 73 | wantText: "engine.cef.windowless_frame_rate", | |
| 73 | 74 | }, | |
| 75 | + | { | |
| 76 | + | name: "zero wheel multiplier", | |
| 77 | + | mutate: func(cfg *Config) { | |
| 78 | + | cfg.Engine.CEF.Input.ScrollWheelMultiplier = 0 | |
| 79 | + | }, | |
| 80 | + | wantErr: true, | |
| 81 | + | wantText: "engine.cef.input.scroll_wheel_multiplier", | |
| 82 | + | }, | |
| 83 | + | { | |
| 84 | + | name: "negative touchpad multiplier", | |
| 85 | + | mutate: func(cfg *Config) { | |
| 86 | + | cfg.Engine.CEF.Input.ScrollTouchpadMultiplier = -0.1 | |
| 87 | + | }, | |
| 88 | + | wantErr: true, | |
| 89 | + | wantText: "engine.cef.input.scroll_touchpad_multiplier", | |
| 90 | + | }, | |
| 91 | + | { | |
| 92 | + | name: "zero horizontal multiplier", | |
| 93 | + | mutate: func(cfg *Config) { | |
| 94 | + | cfg.Engine.CEF.Input.ScrollHorizontalMultiplier = 0 | |
| 95 | + | }, | |
| 96 | + | wantErr: true, | |
| 97 | + | wantText: "engine.cef.input.scroll_horizontal_multiplier", | |
| 98 | + | }, | |
| 99 | + | { | |
| 100 | + | name: "negative vertical multiplier", | |
| 101 | + | mutate: func(cfg *Config) { | |
| 102 | + | cfg.Engine.CEF.Input.ScrollVerticalMultiplier = -0.1 | |
| 103 | + | }, | |
| 104 | + | wantErr: true, | |
| 105 | + | wantText: "engine.cef.input.scroll_vertical_multiplier", | |
| 106 | + | }, | |
| 107 | + | { | |
| 108 | + | name: "nan horizontal multiplier", | |
| 109 | + | mutate: func(cfg *Config) { | |
| 110 | + | cfg.Engine.CEF.Input.ScrollHorizontalMultiplier = math.NaN() | |
| 111 | + | }, | |
| 112 | + | wantErr: true, | |
| 113 | + | wantText: "engine.cef.input.scroll_horizontal_multiplier", | |
| 114 | + | }, | |
| 115 | + | { | |
| 116 | + | name: "infinite vertical multiplier", | |
| 117 | + | mutate: func(cfg *Config) { | |
| 118 | + | cfg.Engine.CEF.Input.ScrollVerticalMultiplier = math.Inf(1) | |
| 119 | + | }, | |
| 120 | + | wantErr: true, | |
| 121 | + | wantText: "engine.cef.input.scroll_vertical_multiplier", | |
| 122 | + | }, | |
| 123 | + | { | |
| 124 | + | name: "zero max delta disables clamp", | |
| 125 | + | mutate: func(cfg *Config) { | |
| 126 | + | cfg.Engine.CEF.Input.ScrollMaxDelta = 0 | |
| 127 | + | }, | |
| 128 | + | wantErr: false, | |
| 129 | + | }, | |
| 130 | + | { | |
| 131 | + | name: "negative max delta", | |
| 132 | + | mutate: func(cfg *Config) { | |
| 133 | + | cfg.Engine.CEF.Input.ScrollMaxDelta = -1 | |
| 134 | + | }, | |
| 135 | + | wantErr: true, | |
| 136 | + | wantText: "engine.cef.input.scroll_max_delta", | |
| 137 | + | }, | |
| 74 | 138 | } | |
| 75 | 139 | ||
| 76 | 140 | for _, tt := range tests { | |
| @@ -1,6 +1,7 @@ | |||||
| 1 | package config | 1 | package config | ||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | + | "math" | |||
| 4 | "testing" | 5 | "testing" | ||
| 5 | 6 | ||||
| 6 | "github.com/stretchr/testify/assert" | 7 | "github.com/stretchr/testify/assert" | ||
| @@ -71,6 +72,69 @@ func TestValidateConfig_CEFConfig(t *testing.T) { | |||||
| 71 | wantErr: true, | 72 | wantErr: true, | ||
| 72 | wantText: "engine.cef.windowless_frame_rate", | 73 | wantText: "engine.cef.windowless_frame_rate", | ||
| 73 | }, | 74 | }, | ||
| 75 | + | { | |||
| 76 | + | name: "zero wheel multiplier", | |||
| 77 | + | mutate: func(cfg *Config) { | |||
| 78 | + | cfg.Engine.CEF.Input.ScrollWheelMultiplier = 0 | |||
| 79 | + | }, | |||
| 80 | + | wantErr: true, | |||
| 81 | + | wantText: "engine.cef.input.scroll_wheel_multiplier", | |||
| 82 | + | }, | |||
| 83 | + | { | |||
| 84 | + | name: "negative touchpad multiplier", | |||
| 85 | + | mutate: func(cfg *Config) { | |||
| 86 | + | cfg.Engine.CEF.Input.ScrollTouchpadMultiplier = -0.1 | |||
| 87 | + | }, | |||
| 88 | + | wantErr: true, | |||
| 89 | + | wantText: "engine.cef.input.scroll_touchpad_multiplier", | |||
| 90 | + | }, | |||
| 91 | + | { | |||
| 92 | + | name: "zero horizontal multiplier", | |||
| 93 | + | mutate: func(cfg *Config) { | |||
| 94 | + | cfg.Engine.CEF.Input.ScrollHorizontalMultiplier = 0 | |||
| 95 | + | }, | |||
| 96 | + | wantErr: true, | |||
| 97 | + | wantText: "engine.cef.input.scroll_horizontal_multiplier", | |||
| 98 | + | }, | |||
| 99 | + | { | |||
| 100 | + | name: "negative vertical multiplier", | |||
| 101 | + | mutate: func(cfg *Config) { | |||
| 102 | + | cfg.Engine.CEF.Input.ScrollVerticalMultiplier = -0.1 | |||
| 103 | + | }, | |||
| 104 | + | wantErr: true, | |||
| 105 | + | wantText: "engine.cef.input.scroll_vertical_multiplier", | |||
| 106 | + | }, | |||
| 107 | + | { | |||
| 108 | + | name: "nan horizontal multiplier", | |||
| 109 | + | mutate: func(cfg *Config) { | |||
| 110 | + | cfg.Engine.CEF.Input.ScrollHorizontalMultiplier = math.NaN() | |||
| 111 | + | }, | |||
| 112 | + | wantErr: true, | |||
| 113 | + | wantText: "engine.cef.input.scroll_horizontal_multiplier", | |||
| 114 | + | }, | |||
| 115 | + | { | |||
| 116 | + | name: "infinite vertical multiplier", | |||
| 117 | + | mutate: func(cfg *Config) { | |||
| 118 | + | cfg.Engine.CEF.Input.ScrollVerticalMultiplier = math.Inf(1) | |||
| 119 | + | }, | |||
| 120 | + | wantErr: true, | |||
| 121 | + | wantText: "engine.cef.input.scroll_vertical_multiplier", | |||
| 122 | + | }, | |||
| 123 | + | { | |||
| 124 | + | name: "zero max delta disables clamp", | |||
| 125 | + | mutate: func(cfg *Config) { | |||
| 126 | + | cfg.Engine.CEF.Input.ScrollMaxDelta = 0 | |||
| 127 | + | }, | |||
| 128 | + | wantErr: false, | |||
| 129 | + | }, | |||
| 130 | + | { | |||
| 131 | + | name: "negative max delta", | |||
| 132 | + | mutate: func(cfg *Config) { | |||
| 133 | + | cfg.Engine.CEF.Input.ScrollMaxDelta = -1 | |||
| 134 | + | }, | |||
| 135 | + | wantErr: true, | |||
| 136 | + | wantText: "engine.cef.input.scroll_max_delta", | |||
| 137 | + | }, | |||
| 74 | } | 138 | } | ||
| 75 | 139 | ||||
| 76 | for _, tt := range tests { | 140 | for _, tt := range tests { | ||