detached b9b46e5

feat(rendering): expose WebKit/GTK env toggles

commit
b9b46e5ded2172b752e7668216a3c4f7f1f8b234
author
brice · · 2025-12-22 18:55 UTC
committer
brice · · 2025-12-22 21:37 UTC
parent
84af634
signature
unsigned
10 files · +393 -239
R internal/application/port/gstreamer_env.go → internal/application/port/rendering_env.go +28 -13 renamed, 61% similar
@@ -17,33 +17,48 @@ const (
1717 GPUVendorUnknown GPUVendor = "unknown"
1818 )
1919
20-// GStreamerEnvSettings contains settings for GStreamer environment configuration.
20+// RenderingEnvSettings contains all rendering-related environment settings.
2121 // This is a port-local type to avoid import cycles with config package.
22-type GStreamerEnvSettings struct {
23- // ForceVSync enables vertical sync for video playback
22+type RenderingEnvSettings struct {
23+ // --- GStreamer settings (from MediaConfig) ---
24+ // ForceVSync enables vertical sync for video playback.
2425 ForceVSync bool
25- // GLRenderingMode controls OpenGL API selection: "auto", "gles2", "gl3", "none"
26+ // GLRenderingMode controls OpenGL API selection: "auto", "gles2", "gl3", "none".
2627 GLRenderingMode string
27- // GStreamerDebugLevel sets GStreamer debug verbosity (0-5)
28+ // GStreamerDebugLevel sets GStreamer debug verbosity (0-5).
2829 GStreamerDebugLevel int
29- // VideoBufferSizeMB sets buffer size in megabytes for video streaming
30+ // VideoBufferSizeMB sets buffer size in megabytes for video streaming.
3031 VideoBufferSizeMB int
31- // QueueBufferTimeSec sets queue buffer time in seconds for prebuffering
32+ // QueueBufferTimeSec sets queue buffer time in seconds for prebuffering.
3233 QueueBufferTimeSec int
34+
35+ // --- WebKit compositor settings (from RenderingConfig) ---
36+ DisableDMABufRenderer bool
37+ ForceCompositingMode bool
38+ DisableCompositingMode bool
39+
40+ // --- GTK/GSK settings (from RenderingConfig) ---
41+ GSKRenderer string
42+ DisableMipmaps bool
43+ PreferGL bool
44+
45+ // --- Debug settings ---
46+ ShowFPS bool
47+ SampleMemory bool
48+ DebugFrames bool
3349 }
3450
35-// GStreamerEnvManager configures GStreamer environment variables
36-// based on system configuration and detected GPU vendor.
51+// RenderingEnvManager configures rendering environment variables
52+// for GStreamer, WebKit, and GTK/GSK.
3753 // Environment variables must be set BEFORE GTK/WebKit initialization.
38-type GStreamerEnvManager interface {
54+type RenderingEnvManager interface {
3955 // DetectGPUVendor identifies the primary GPU vendor from system info.
4056 // Uses /sys/class/drm/card*/device/vendor as primary detection method.
4157 DetectGPUVendor(ctx context.Context) GPUVendor
4258
43- // ApplyEnvironment sets GStreamer environment variables based on
44- // the provided settings and detected GPU vendor.
59+ // ApplyEnvironment sets all rendering environment variables.
4560 // Must be called before any GTK/GStreamer initialization.
46- ApplyEnvironment(ctx context.Context, settings GStreamerEnvSettings) error
61+ ApplyEnvironment(ctx context.Context, settings RenderingEnvSettings) error
4762
4863 // GetAppliedVars returns a map of environment variables that were set.
4964 // Useful for logging and debugging.
@@ -17,33 +17,48 @@ const (
17 GPUVendorUnknown GPUVendor = "unknown"17 GPUVendorUnknown GPUVendor = "unknown"
18 )18 )
19 19
20-// GStreamerEnvSettings contains settings for GStreamer environment configuration.20+// RenderingEnvSettings contains all rendering-related environment settings.
21 // This is a port-local type to avoid import cycles with config package.21 // This is a port-local type to avoid import cycles with config package.
22-type GStreamerEnvSettings struct {22+type RenderingEnvSettings struct {
23- // ForceVSync enables vertical sync for video playback23+ // --- GStreamer settings (from MediaConfig) ---
24+ // ForceVSync enables vertical sync for video playback.
24 ForceVSync bool25 ForceVSync bool
25- // GLRenderingMode controls OpenGL API selection: "auto", "gles2", "gl3", "none"26+ // GLRenderingMode controls OpenGL API selection: "auto", "gles2", "gl3", "none".
26 GLRenderingMode string27 GLRenderingMode string
27- // GStreamerDebugLevel sets GStreamer debug verbosity (0-5)28+ // GStreamerDebugLevel sets GStreamer debug verbosity (0-5).
28 GStreamerDebugLevel int29 GStreamerDebugLevel int
29- // VideoBufferSizeMB sets buffer size in megabytes for video streaming30+ // VideoBufferSizeMB sets buffer size in megabytes for video streaming.
30 VideoBufferSizeMB int31 VideoBufferSizeMB int
31- // QueueBufferTimeSec sets queue buffer time in seconds for prebuffering32+ // QueueBufferTimeSec sets queue buffer time in seconds for prebuffering.
32 QueueBufferTimeSec int33 QueueBufferTimeSec int
34+
35+ // --- WebKit compositor settings (from RenderingConfig) ---
36+ DisableDMABufRenderer bool
37+ ForceCompositingMode bool
38+ DisableCompositingMode bool
39+
40+ // --- GTK/GSK settings (from RenderingConfig) ---
41+ GSKRenderer string
42+ DisableMipmaps bool
43+ PreferGL bool
44+
45+ // --- Debug settings ---
46+ ShowFPS bool
47+ SampleMemory bool
48+ DebugFrames bool
33 }49 }
34 50
35-// GStreamerEnvManager configures GStreamer environment variables51+// RenderingEnvManager configures rendering environment variables
36-// based on system configuration and detected GPU vendor.52+// for GStreamer, WebKit, and GTK/GSK.
37 // Environment variables must be set BEFORE GTK/WebKit initialization.53 // Environment variables must be set BEFORE GTK/WebKit initialization.
38-type GStreamerEnvManager interface {54+type RenderingEnvManager interface {
39 // DetectGPUVendor identifies the primary GPU vendor from system info.55 // DetectGPUVendor identifies the primary GPU vendor from system info.
40 // Uses /sys/class/drm/card*/device/vendor as primary detection method.56 // Uses /sys/class/drm/card*/device/vendor as primary detection method.
41 DetectGPUVendor(ctx context.Context) GPUVendor57 DetectGPUVendor(ctx context.Context) GPUVendor
42 58
43- // ApplyEnvironment sets GStreamer environment variables based on59+ // ApplyEnvironment sets all rendering environment variables.
44- // the provided settings and detected GPU vendor.
45 // Must be called before any GTK/GStreamer initialization.60 // Must be called before any GTK/GStreamer initialization.
46- ApplyEnvironment(ctx context.Context, settings GStreamerEnvSettings) error61+ ApplyEnvironment(ctx context.Context, settings RenderingEnvSettings) error
47 62
48 // GetAppliedVars returns a map of environment variables that were set.63 // GetAppliedVars returns a map of environment variables that were set.
49 // Useful for logging and debugging.64 // Useful for logging and debugging.
M internal/bootstrap/webkit_stack.go +26 -10
@@ -8,8 +8,8 @@ import (
88 "github.com/bnema/dumber/internal/application/port"
99 "github.com/bnema/dumber/internal/application/usecase"
1010 "github.com/bnema/dumber/internal/infrastructure/config"
11+ "github.com/bnema/dumber/internal/infrastructure/env"
1112 "github.com/bnema/dumber/internal/infrastructure/filtering"
12- "github.com/bnema/dumber/internal/infrastructure/media"
1313 "github.com/bnema/dumber/internal/infrastructure/webkit"
1414 "github.com/bnema/dumber/internal/ui/theme"
1515 "github.com/rs/zerolog"
@@ -32,24 +32,40 @@ func BuildWebKitStack(
3232 themeManager *theme.Manager,
3333 logger zerolog.Logger,
3434 ) WebKitStack {
35- // CRITICAL: Configure GStreamer environment BEFORE WebKit/GTK initialization.
36- // Environment variables must be set before GStreamer initializes its pipelines.
37- gstEnv := media.NewEnvManager()
38- gpuVendor := gstEnv.DetectGPUVendor(ctx)
39- gstSettings := port.GStreamerEnvSettings{
35+ // CRITICAL: Configure rendering environment BEFORE WebKit/GTK initialization.
36+ // Environment variables must be set before GTK/WebKit/GStreamer initializes.
37+ renderEnv := env.NewManager()
38+ gpuVendor := renderEnv.DetectGPUVendor(ctx)
39+ renderSettings := port.RenderingEnvSettings{
40+ // GStreamer settings
4041 ForceVSync: cfg.Media.ForceVSync,
4142 GLRenderingMode: string(cfg.Media.GLRenderingMode),
4243 GStreamerDebugLevel: cfg.Media.GStreamerDebugLevel,
4344 VideoBufferSizeMB: cfg.Media.VideoBufferSizeMB,
4445 QueueBufferTimeSec: cfg.Media.QueueBufferTimeSec,
46+
47+ // WebKit compositor settings
48+ DisableDMABufRenderer: cfg.Rendering.DisableDMABufRenderer,
49+ ForceCompositingMode: cfg.Rendering.ForceCompositingMode,
50+ DisableCompositingMode: cfg.Rendering.DisableCompositingMode,
51+
52+ // GTK/GSK settings
53+ GSKRenderer: string(cfg.Rendering.GSKRenderer),
54+ DisableMipmaps: cfg.Rendering.DisableMipmaps,
55+ PreferGL: cfg.Rendering.PreferGL,
56+
57+ // Debug settings
58+ ShowFPS: cfg.Rendering.ShowFPS,
59+ SampleMemory: cfg.Rendering.SampleMemory,
60+ DebugFrames: cfg.Rendering.DebugFrames,
4561 }
46- if err := gstEnv.ApplyEnvironment(ctx, gstSettings); err != nil {
47- logger.Warn().Err(err).Msg("failed to apply gstreamer environment")
62+ if err := renderEnv.ApplyEnvironment(ctx, renderSettings); err != nil {
63+ logger.Warn().Err(err).Msg("failed to apply rendering environment")
4864 }
4965 logger.Info().
5066 Str("gpu", string(gpuVendor)).
51- Interface("vars", gstEnv.GetAppliedVars()).
52- Msg("gstreamer environment configured")
67+ Interface("vars", renderEnv.GetAppliedVars()).
68+ Msg("rendering environment configured")
5369
5470 wkCtx, err := webkit.NewWebKitContext(ctx, dataDir, cacheDir)
5571 if err != nil {
@@ -8,8 +8,8 @@ import (
8 "github.com/bnema/dumber/internal/application/port"8 "github.com/bnema/dumber/internal/application/port"
9 "github.com/bnema/dumber/internal/application/usecase"9 "github.com/bnema/dumber/internal/application/usecase"
10 "github.com/bnema/dumber/internal/infrastructure/config"10 "github.com/bnema/dumber/internal/infrastructure/config"
11+ "github.com/bnema/dumber/internal/infrastructure/env"
11 "github.com/bnema/dumber/internal/infrastructure/filtering"12 "github.com/bnema/dumber/internal/infrastructure/filtering"
12- "github.com/bnema/dumber/internal/infrastructure/media"
13 "github.com/bnema/dumber/internal/infrastructure/webkit"13 "github.com/bnema/dumber/internal/infrastructure/webkit"
14 "github.com/bnema/dumber/internal/ui/theme"14 "github.com/bnema/dumber/internal/ui/theme"
15 "github.com/rs/zerolog"15 "github.com/rs/zerolog"
@@ -32,24 +32,40 @@ func BuildWebKitStack(
32 themeManager *theme.Manager,32 themeManager *theme.Manager,
33 logger zerolog.Logger,33 logger zerolog.Logger,
34 ) WebKitStack {34 ) WebKitStack {
35- // CRITICAL: Configure GStreamer environment BEFORE WebKit/GTK initialization.35+ // CRITICAL: Configure rendering environment BEFORE WebKit/GTK initialization.
36- // Environment variables must be set before GStreamer initializes its pipelines.36+ // Environment variables must be set before GTK/WebKit/GStreamer initializes.
37- gstEnv := media.NewEnvManager()37+ renderEnv := env.NewManager()
38- gpuVendor := gstEnv.DetectGPUVendor(ctx)38+ gpuVendor := renderEnv.DetectGPUVendor(ctx)
39- gstSettings := port.GStreamerEnvSettings{39+ renderSettings := port.RenderingEnvSettings{
40+ // GStreamer settings
40 ForceVSync: cfg.Media.ForceVSync,41 ForceVSync: cfg.Media.ForceVSync,
41 GLRenderingMode: string(cfg.Media.GLRenderingMode),42 GLRenderingMode: string(cfg.Media.GLRenderingMode),
42 GStreamerDebugLevel: cfg.Media.GStreamerDebugLevel,43 GStreamerDebugLevel: cfg.Media.GStreamerDebugLevel,
43 VideoBufferSizeMB: cfg.Media.VideoBufferSizeMB,44 VideoBufferSizeMB: cfg.Media.VideoBufferSizeMB,
44 QueueBufferTimeSec: cfg.Media.QueueBufferTimeSec,45 QueueBufferTimeSec: cfg.Media.QueueBufferTimeSec,
46+
47+ // WebKit compositor settings
48+ DisableDMABufRenderer: cfg.Rendering.DisableDMABufRenderer,
49+ ForceCompositingMode: cfg.Rendering.ForceCompositingMode,
50+ DisableCompositingMode: cfg.Rendering.DisableCompositingMode,
51+
52+ // GTK/GSK settings
53+ GSKRenderer: string(cfg.Rendering.GSKRenderer),
54+ DisableMipmaps: cfg.Rendering.DisableMipmaps,
55+ PreferGL: cfg.Rendering.PreferGL,
56+
57+ // Debug settings
58+ ShowFPS: cfg.Rendering.ShowFPS,
59+ SampleMemory: cfg.Rendering.SampleMemory,
60+ DebugFrames: cfg.Rendering.DebugFrames,
45 }61 }
46- if err := gstEnv.ApplyEnvironment(ctx, gstSettings); err != nil {62+ if err := renderEnv.ApplyEnvironment(ctx, renderSettings); err != nil {
47- logger.Warn().Err(err).Msg("failed to apply gstreamer environment")63+ logger.Warn().Err(err).Msg("failed to apply rendering environment")
48 }64 }
49 logger.Info().65 logger.Info().
50 Str("gpu", string(gpuVendor)).66 Str("gpu", string(gpuVendor)).
51- Interface("vars", gstEnv.GetAppliedVars()).67+ Interface("vars", renderEnv.GetAppliedVars()).
52- Msg("gstreamer environment configured")68+ Msg("rendering environment configured")
53 69
54 wkCtx, err := webkit.NewWebKitContext(ctx, dataDir, cacheDir)70 wkCtx, err := webkit.NewWebKitContext(ctx, dataDir, cacheDir)
55 if err != nil {71 if err != nil {
M internal/infrastructure/config/defaults.go +13 -1
@@ -112,7 +112,19 @@ func DefaultConfig() *Config {
112112 Debug: DebugConfig{
113113 EnableDevTools: true,
114114 },
115- RenderingMode: RenderingModeGPU,
115+ Rendering: RenderingConfig{
116+ Mode: RenderingModeGPU,
117+ DisableDMABufRenderer: false,
118+ ForceCompositingMode: false,
119+ DisableCompositingMode: false,
120+ GSKRenderer: GSKRendererVulkan,
121+ DisableMipmaps: false,
122+ PreferGL: false,
123+ DrawCompositingIndicators: false,
124+ ShowFPS: false,
125+ SampleMemory: false,
126+ DebugFrames: false,
127+ },
116128 DefaultWebpageZoom: 1.2, // 120% default zoom for better readability
117129 DefaultUIScale: defaultUIScale, // 1.0 = 100%, 2.0 = 200%
118130 Workspace: WorkspaceConfig{
@@ -112,7 +112,19 @@ func DefaultConfig() *Config {
112 Debug: DebugConfig{112 Debug: DebugConfig{
113 EnableDevTools: true,113 EnableDevTools: true,
114 },114 },
115- RenderingMode: RenderingModeGPU,115+ Rendering: RenderingConfig{
116+ Mode: RenderingModeGPU,
117+ DisableDMABufRenderer: false,
118+ ForceCompositingMode: false,
119+ DisableCompositingMode: false,
120+ GSKRenderer: GSKRendererVulkan,
121+ DisableMipmaps: false,
122+ PreferGL: false,
123+ DrawCompositingIndicators: false,
124+ ShowFPS: false,
125+ SampleMemory: false,
126+ DebugFrames: false,
127+ },
116 DefaultWebpageZoom: 1.2, // 120% default zoom for better readability128 DefaultWebpageZoom: 1.2, // 120% default zoom for better readability
117 DefaultUIScale: defaultUIScale, // 1.0 = 100%, 2.0 = 200%129 DefaultUIScale: defaultUIScale, // 1.0 = 100%, 2.0 = 200%
118 Workspace: WorkspaceConfig{130 Workspace: WorkspaceConfig{
M internal/infrastructure/config/loader.go +29 -9
@@ -46,9 +46,6 @@ func NewManager() (*Manager, error) {
4646 // The explicit bindings below are only for special cases with different naming patterns.
4747
4848 // Explicit bindings for legacy or system env vars
49- if err := v.BindEnv("rendering_mode", "DUMBER_RENDERING_MODE"); err != nil {
50- return nil, fmt.Errorf("failed to bind DUMBER_RENDERING_MODE: %w", err)
51- }
5249 if err := v.BindEnv("default_webpage_zoom", "DUMBER_DEFAULT_WEBPAGE_ZOOM"); err != nil {
5350 return nil, fmt.Errorf("failed to bind DUMBER_DEFAULT_WEBPAGE_ZOOM: %w", err)
5451 }
@@ -158,15 +155,28 @@ func ensureDatabasePath(config *Config) error {
158155 }
159156
160157 func normalizeConfig(config *Config) {
161- switch strings.ToLower(string(config.RenderingMode)) {
158+ switch strings.ToLower(string(config.Rendering.Mode)) {
162159 case "", string(RenderingModeAuto):
163- config.RenderingMode = RenderingModeAuto
160+ config.Rendering.Mode = RenderingModeAuto
164161 case string(RenderingModeGPU):
165- config.RenderingMode = RenderingModeGPU
162+ config.Rendering.Mode = RenderingModeGPU
166163 case string(RenderingModeCPU):
167- config.RenderingMode = RenderingModeCPU
164+ config.Rendering.Mode = RenderingModeCPU
165+ default:
166+ config.Rendering.Mode = RenderingModeAuto
167+ }
168+
169+ switch strings.ToLower(string(config.Rendering.GSKRenderer)) {
170+ case "", string(GSKRendererAuto):
171+ config.Rendering.GSKRenderer = GSKRendererAuto
172+ case string(GSKRendererOpenGL):
173+ config.Rendering.GSKRenderer = GSKRendererOpenGL
174+ case string(GSKRendererVulkan):
175+ config.Rendering.GSKRenderer = GSKRendererVulkan
176+ case string(GSKRendererCairo):
177+ config.Rendering.GSKRenderer = GSKRendererCairo
168178 default:
169- config.RenderingMode = RenderingModeAuto
179+ config.Rendering.GSKRenderer = GSKRendererAuto
170180 }
171181
172182 switch config.Appearance.ColorScheme {
@@ -352,7 +362,17 @@ func (m *Manager) setAppearanceDefaults(defaults *Config) {
352362 }
353363
354364 func (m *Manager) setRenderingDefaults(defaults *Config) {
355- m.viper.SetDefault("rendering_mode", string(RenderingModeGPU))
365+ m.viper.SetDefault("rendering.mode", string(defaults.Rendering.Mode))
366+ m.viper.SetDefault("rendering.disable_dmabuf_renderer", defaults.Rendering.DisableDMABufRenderer)
367+ m.viper.SetDefault("rendering.force_compositing_mode", defaults.Rendering.ForceCompositingMode)
368+ m.viper.SetDefault("rendering.disable_compositing_mode", defaults.Rendering.DisableCompositingMode)
369+ m.viper.SetDefault("rendering.gsk_renderer", string(defaults.Rendering.GSKRenderer))
370+ m.viper.SetDefault("rendering.disable_mipmaps", defaults.Rendering.DisableMipmaps)
371+ m.viper.SetDefault("rendering.prefer_gl", defaults.Rendering.PreferGL)
372+ m.viper.SetDefault("rendering.draw_compositing_indicators", defaults.Rendering.DrawCompositingIndicators)
373+ m.viper.SetDefault("rendering.show_fps", defaults.Rendering.ShowFPS)
374+ m.viper.SetDefault("rendering.sample_memory", defaults.Rendering.SampleMemory)
375+ m.viper.SetDefault("rendering.debug_frames", defaults.Rendering.DebugFrames)
356376 m.viper.SetDefault("default_webpage_zoom", defaults.DefaultWebpageZoom)
357377 m.viper.SetDefault("default_ui_scale", defaults.DefaultUIScale)
358378 }
@@ -46,9 +46,6 @@ func NewManager() (*Manager, error) {
46 // The explicit bindings below are only for special cases with different naming patterns.46 // The explicit bindings below are only for special cases with different naming patterns.
47 47
48 // Explicit bindings for legacy or system env vars48 // Explicit bindings for legacy or system env vars
49- if err := v.BindEnv("rendering_mode", "DUMBER_RENDERING_MODE"); err != nil {
50- return nil, fmt.Errorf("failed to bind DUMBER_RENDERING_MODE: %w", err)
51- }
52 if err := v.BindEnv("default_webpage_zoom", "DUMBER_DEFAULT_WEBPAGE_ZOOM"); err != nil {49 if err := v.BindEnv("default_webpage_zoom", "DUMBER_DEFAULT_WEBPAGE_ZOOM"); err != nil {
53 return nil, fmt.Errorf("failed to bind DUMBER_DEFAULT_WEBPAGE_ZOOM: %w", err)50 return nil, fmt.Errorf("failed to bind DUMBER_DEFAULT_WEBPAGE_ZOOM: %w", err)
54 }51 }
@@ -158,15 +155,28 @@ func ensureDatabasePath(config *Config) error {
158 }155 }
159 156
160 func normalizeConfig(config *Config) {157 func normalizeConfig(config *Config) {
161- switch strings.ToLower(string(config.RenderingMode)) {158+ switch strings.ToLower(string(config.Rendering.Mode)) {
162 case "", string(RenderingModeAuto):159 case "", string(RenderingModeAuto):
163- config.RenderingMode = RenderingModeAuto160+ config.Rendering.Mode = RenderingModeAuto
164 case string(RenderingModeGPU):161 case string(RenderingModeGPU):
165- config.RenderingMode = RenderingModeGPU162+ config.Rendering.Mode = RenderingModeGPU
166 case string(RenderingModeCPU):163 case string(RenderingModeCPU):
167- config.RenderingMode = RenderingModeCPU164+ config.Rendering.Mode = RenderingModeCPU
165+ default:
166+ config.Rendering.Mode = RenderingModeAuto
167+ }
168+
169+ switch strings.ToLower(string(config.Rendering.GSKRenderer)) {
170+ case "", string(GSKRendererAuto):
171+ config.Rendering.GSKRenderer = GSKRendererAuto
172+ case string(GSKRendererOpenGL):
173+ config.Rendering.GSKRenderer = GSKRendererOpenGL
174+ case string(GSKRendererVulkan):
175+ config.Rendering.GSKRenderer = GSKRendererVulkan
176+ case string(GSKRendererCairo):
177+ config.Rendering.GSKRenderer = GSKRendererCairo
168 default:178 default:
169- config.RenderingMode = RenderingModeAuto179+ config.Rendering.GSKRenderer = GSKRendererAuto
170 }180 }
171 181
172 switch config.Appearance.ColorScheme {182 switch config.Appearance.ColorScheme {
@@ -352,7 +362,17 @@ func (m *Manager) setAppearanceDefaults(defaults *Config) {
352 }362 }
353 363
354 func (m *Manager) setRenderingDefaults(defaults *Config) {364 func (m *Manager) setRenderingDefaults(defaults *Config) {
355- m.viper.SetDefault("rendering_mode", string(RenderingModeGPU))365+ m.viper.SetDefault("rendering.mode", string(defaults.Rendering.Mode))
366+ m.viper.SetDefault("rendering.disable_dmabuf_renderer", defaults.Rendering.DisableDMABufRenderer)
367+ m.viper.SetDefault("rendering.force_compositing_mode", defaults.Rendering.ForceCompositingMode)
368+ m.viper.SetDefault("rendering.disable_compositing_mode", defaults.Rendering.DisableCompositingMode)
369+ m.viper.SetDefault("rendering.gsk_renderer", string(defaults.Rendering.GSKRenderer))
370+ m.viper.SetDefault("rendering.disable_mipmaps", defaults.Rendering.DisableMipmaps)
371+ m.viper.SetDefault("rendering.prefer_gl", defaults.Rendering.PreferGL)
372+ m.viper.SetDefault("rendering.draw_compositing_indicators", defaults.Rendering.DrawCompositingIndicators)
373+ m.viper.SetDefault("rendering.show_fps", defaults.Rendering.ShowFPS)
374+ m.viper.SetDefault("rendering.sample_memory", defaults.Rendering.SampleMemory)
375+ m.viper.SetDefault("rendering.debug_frames", defaults.Rendering.DebugFrames)
356 m.viper.SetDefault("default_webpage_zoom", defaults.DefaultWebpageZoom)376 m.viper.SetDefault("default_webpage_zoom", defaults.DefaultWebpageZoom)
357 m.viper.SetDefault("default_ui_scale", defaults.DefaultUIScale)377 m.viper.SetDefault("default_ui_scale", defaults.DefaultUIScale)
358 }378 }
M internal/infrastructure/config/schema.go +57 -2
@@ -11,8 +11,6 @@ type Config struct {
1111 Logging LoggingConfig `mapstructure:"logging" yaml:"logging" toml:"logging"`
1212 Appearance AppearanceConfig `mapstructure:"appearance" yaml:"appearance" toml:"appearance"`
1313 Debug DebugConfig `mapstructure:"debug" yaml:"debug" toml:"debug"`
14- // RenderingMode controls GPU/CPU rendering selection for WebKit
15- RenderingMode RenderingMode `mapstructure:"rendering_mode" yaml:"rendering_mode" toml:"rendering_mode"`
1614 // DefaultWebpageZoom sets the default zoom level for pages without saved zoom settings (1.0 = 100%, 1.2 = 120%)
1715 DefaultWebpageZoom float64 `mapstructure:"default_webpage_zoom" yaml:"default_webpage_zoom" toml:"default_webpage_zoom"`
1816 // DefaultUIScale sets the default UI scale for GTK widgets (1.0 = 100%, 2.0 = 200%)
@@ -23,6 +21,8 @@ type Config struct {
2321 ContentFiltering ContentFilteringConfig `mapstructure:"content_filtering" yaml:"content_filtering" toml:"content_filtering"`
2422 // Omnibox controls the omnibox behavior (initial history display)
2523 Omnibox OmniboxConfig `mapstructure:"omnibox" yaml:"omnibox" toml:"omnibox"`
24+ // Rendering controls WebKit, GTK and compositor behavior.
25+ Rendering RenderingConfig `mapstructure:"rendering" yaml:"rendering" toml:"rendering"`
2626 // Media controls video playback and hardware acceleration
2727 Media MediaConfig `mapstructure:"media" yaml:"media" toml:"media"`
2828 // Runtime configures optional runtime overrides (e.g., /opt prefix for WebKitGTK/GTK).
@@ -38,6 +38,61 @@ const (
3838 RenderingModeCPU RenderingMode = "cpu"
3939 )
4040
41+// GSKRendererMode controls the GTK Scene Kit renderer selection.
42+type GSKRendererMode string
43+
44+const (
45+ GSKRendererAuto GSKRendererMode = "auto"
46+ GSKRendererOpenGL GSKRendererMode = "opengl"
47+ GSKRendererVulkan GSKRendererMode = "vulkan"
48+ GSKRendererCairo GSKRendererMode = "cairo"
49+)
50+
51+// RenderingConfig controls WebKit and GTK/GSK rendering behavior.
52+type RenderingConfig struct {
53+ // Mode controls GPU/CPU rendering selection for WebKit (auto, gpu, cpu).
54+ Mode RenderingMode `mapstructure:"mode" yaml:"mode" toml:"mode"`
55+
56+ // DisableDMABufRenderer disables DMA-BUF accelerated renderer.
57+ // Env: WEBKIT_DISABLE_DMABUF_RENDERER
58+ DisableDMABufRenderer bool `mapstructure:"disable_dmabuf_renderer" yaml:"disable_dmabuf_renderer" toml:"disable_dmabuf_renderer"`
59+
60+ // ForceCompositingMode forces accelerated compositing.
61+ // Env: WEBKIT_FORCE_COMPOSITING_MODE
62+ ForceCompositingMode bool `mapstructure:"force_compositing_mode" yaml:"force_compositing_mode" toml:"force_compositing_mode"`
63+
64+ // DisableCompositingMode disables accelerated compositing.
65+ // Env: WEBKIT_DISABLE_COMPOSITING_MODE
66+ DisableCompositingMode bool `mapstructure:"disable_compositing_mode" yaml:"disable_compositing_mode" toml:"disable_compositing_mode"`
67+
68+ // GSKRenderer overrides GTK Scene Kit renderer selection.
69+ // Env: GSK_RENDERER
70+ GSKRenderer GSKRendererMode `mapstructure:"gsk_renderer" yaml:"gsk_renderer" toml:"gsk_renderer"`
71+
72+ // DisableMipmaps disables mipmap generation.
73+ // Env: GSK_GPU_DISABLE=mipmap
74+ DisableMipmaps bool `mapstructure:"disable_mipmaps" yaml:"disable_mipmaps" toml:"disable_mipmaps"`
75+
76+ // PreferGL forces OpenGL over GLES.
77+ // Env: GDK_DEBUG=gl-prefer-gl
78+ PreferGL bool `mapstructure:"prefer_gl" yaml:"prefer_gl" toml:"prefer_gl"`
79+
80+ // DrawCompositingIndicators shows compositing layer borders and repaint counters.
81+ DrawCompositingIndicators bool `mapstructure:"draw_compositing_indicators" yaml:"draw_compositing_indicators" toml:"draw_compositing_indicators"` //nolint:lll // struct tags exceed lll limit
82+
83+ // ShowFPS displays WebKit FPS counter.
84+ // Env: WEBKIT_SHOW_FPS
85+ ShowFPS bool `mapstructure:"show_fps" yaml:"show_fps" toml:"show_fps"`
86+
87+ // SampleMemory enables memory sampling.
88+ // Env: WEBKIT_SAMPLE_MEMORY
89+ SampleMemory bool `mapstructure:"sample_memory" yaml:"sample_memory" toml:"sample_memory"`
90+
91+ // DebugFrames enables frame timing debug output.
92+ // Env: GDK_DEBUG=frames
93+ DebugFrames bool `mapstructure:"debug_frames" yaml:"debug_frames" toml:"debug_frames"`
94+}
95+
4196 // HardwareDecodingMode controls video hardware acceleration.
4297 type HardwareDecodingMode string
4398
@@ -11,8 +11,6 @@ type Config struct {
11 Logging LoggingConfig `mapstructure:"logging" yaml:"logging" toml:"logging"`11 Logging LoggingConfig `mapstructure:"logging" yaml:"logging" toml:"logging"`
12 Appearance AppearanceConfig `mapstructure:"appearance" yaml:"appearance" toml:"appearance"`12 Appearance AppearanceConfig `mapstructure:"appearance" yaml:"appearance" toml:"appearance"`
13 Debug DebugConfig `mapstructure:"debug" yaml:"debug" toml:"debug"`13 Debug DebugConfig `mapstructure:"debug" yaml:"debug" toml:"debug"`
14- // RenderingMode controls GPU/CPU rendering selection for WebKit
15- RenderingMode RenderingMode `mapstructure:"rendering_mode" yaml:"rendering_mode" toml:"rendering_mode"`
16 // DefaultWebpageZoom sets the default zoom level for pages without saved zoom settings (1.0 = 100%, 1.2 = 120%)14 // DefaultWebpageZoom sets the default zoom level for pages without saved zoom settings (1.0 = 100%, 1.2 = 120%)
17 DefaultWebpageZoom float64 `mapstructure:"default_webpage_zoom" yaml:"default_webpage_zoom" toml:"default_webpage_zoom"`15 DefaultWebpageZoom float64 `mapstructure:"default_webpage_zoom" yaml:"default_webpage_zoom" toml:"default_webpage_zoom"`
18 // DefaultUIScale sets the default UI scale for GTK widgets (1.0 = 100%, 2.0 = 200%)16 // DefaultUIScale sets the default UI scale for GTK widgets (1.0 = 100%, 2.0 = 200%)
@@ -23,6 +21,8 @@ type Config struct {
23 ContentFiltering ContentFilteringConfig `mapstructure:"content_filtering" yaml:"content_filtering" toml:"content_filtering"`21 ContentFiltering ContentFilteringConfig `mapstructure:"content_filtering" yaml:"content_filtering" toml:"content_filtering"`
24 // Omnibox controls the omnibox behavior (initial history display)22 // Omnibox controls the omnibox behavior (initial history display)
25 Omnibox OmniboxConfig `mapstructure:"omnibox" yaml:"omnibox" toml:"omnibox"`23 Omnibox OmniboxConfig `mapstructure:"omnibox" yaml:"omnibox" toml:"omnibox"`
24+ // Rendering controls WebKit, GTK and compositor behavior.
25+ Rendering RenderingConfig `mapstructure:"rendering" yaml:"rendering" toml:"rendering"`
26 // Media controls video playback and hardware acceleration26 // Media controls video playback and hardware acceleration
27 Media MediaConfig `mapstructure:"media" yaml:"media" toml:"media"`27 Media MediaConfig `mapstructure:"media" yaml:"media" toml:"media"`
28 // Runtime configures optional runtime overrides (e.g., /opt prefix for WebKitGTK/GTK).28 // Runtime configures optional runtime overrides (e.g., /opt prefix for WebKitGTK/GTK).
@@ -38,6 +38,61 @@ const (
38 RenderingModeCPU RenderingMode = "cpu"38 RenderingModeCPU RenderingMode = "cpu"
39 )39 )
40 40
41+// GSKRendererMode controls the GTK Scene Kit renderer selection.
42+type GSKRendererMode string
43+
44+const (
45+ GSKRendererAuto GSKRendererMode = "auto"
46+ GSKRendererOpenGL GSKRendererMode = "opengl"
47+ GSKRendererVulkan GSKRendererMode = "vulkan"
48+ GSKRendererCairo GSKRendererMode = "cairo"
49+)
50+
51+// RenderingConfig controls WebKit and GTK/GSK rendering behavior.
52+type RenderingConfig struct {
53+ // Mode controls GPU/CPU rendering selection for WebKit (auto, gpu, cpu).
54+ Mode RenderingMode `mapstructure:"mode" yaml:"mode" toml:"mode"`
55+
56+ // DisableDMABufRenderer disables DMA-BUF accelerated renderer.
57+ // Env: WEBKIT_DISABLE_DMABUF_RENDERER
58+ DisableDMABufRenderer bool `mapstructure:"disable_dmabuf_renderer" yaml:"disable_dmabuf_renderer" toml:"disable_dmabuf_renderer"`
59+
60+ // ForceCompositingMode forces accelerated compositing.
61+ // Env: WEBKIT_FORCE_COMPOSITING_MODE
62+ ForceCompositingMode bool `mapstructure:"force_compositing_mode" yaml:"force_compositing_mode" toml:"force_compositing_mode"`
63+
64+ // DisableCompositingMode disables accelerated compositing.
65+ // Env: WEBKIT_DISABLE_COMPOSITING_MODE
66+ DisableCompositingMode bool `mapstructure:"disable_compositing_mode" yaml:"disable_compositing_mode" toml:"disable_compositing_mode"`
67+
68+ // GSKRenderer overrides GTK Scene Kit renderer selection.
69+ // Env: GSK_RENDERER
70+ GSKRenderer GSKRendererMode `mapstructure:"gsk_renderer" yaml:"gsk_renderer" toml:"gsk_renderer"`
71+
72+ // DisableMipmaps disables mipmap generation.
73+ // Env: GSK_GPU_DISABLE=mipmap
74+ DisableMipmaps bool `mapstructure:"disable_mipmaps" yaml:"disable_mipmaps" toml:"disable_mipmaps"`
75+
76+ // PreferGL forces OpenGL over GLES.
77+ // Env: GDK_DEBUG=gl-prefer-gl
78+ PreferGL bool `mapstructure:"prefer_gl" yaml:"prefer_gl" toml:"prefer_gl"`
79+
80+ // DrawCompositingIndicators shows compositing layer borders and repaint counters.
81+ DrawCompositingIndicators bool `mapstructure:"draw_compositing_indicators" yaml:"draw_compositing_indicators" toml:"draw_compositing_indicators"` //nolint:lll // struct tags exceed lll limit
82+
83+ // ShowFPS displays WebKit FPS counter.
84+ // Env: WEBKIT_SHOW_FPS
85+ ShowFPS bool `mapstructure:"show_fps" yaml:"show_fps" toml:"show_fps"`
86+
87+ // SampleMemory enables memory sampling.
88+ // Env: WEBKIT_SAMPLE_MEMORY
89+ SampleMemory bool `mapstructure:"sample_memory" yaml:"sample_memory" toml:"sample_memory"`
90+
91+ // DebugFrames enables frame timing debug output.
92+ // Env: GDK_DEBUG=frames
93+ DebugFrames bool `mapstructure:"debug_frames" yaml:"debug_frames" toml:"debug_frames"`
94+}
95+
41 // HardwareDecodingMode controls video hardware acceleration.96 // HardwareDecodingMode controls video hardware acceleration.
42 type HardwareDecodingMode string97 type HardwareDecodingMode string
43 98
M internal/infrastructure/config/validation.go +27 -3
@@ -258,12 +258,36 @@ func validateOmnibox(config *Config) []string {
258258 }
259259
260260 func validateRendering(config *Config) []string {
261- switch config.RenderingMode {
261+ var validationErrors []string
262+
263+ switch config.Rendering.Mode {
262264 case RenderingModeAuto, RenderingModeGPU, RenderingModeCPU, "":
263- return nil
264265 default:
265- return []string{fmt.Sprintf("rendering_mode must be one of: auto, gpu, cpu (got: %s)", config.RenderingMode)}
266+ validationErrors = append(
267+ validationErrors,
268+ fmt.Sprintf("rendering.mode must be one of: auto, gpu, cpu (got: %s)", config.Rendering.Mode),
269+ )
270+ }
271+
272+ switch config.Rendering.GSKRenderer {
273+ case GSKRendererAuto, GSKRendererOpenGL, GSKRendererVulkan, GSKRendererCairo, "":
274+ default:
275+ validationErrors = append(
276+ validationErrors,
277+ fmt.Sprintf(
278+ "rendering.gsk_renderer must be one of: auto, opengl, vulkan, cairo (got: %s)",
279+ config.Rendering.GSKRenderer,
280+ ),
281+ )
282+ }
283+
284+ if config.Rendering.ForceCompositingMode && config.Rendering.DisableCompositingMode {
285+ validationErrors = append(validationErrors,
286+ "rendering.force_compositing_mode and rendering.disable_compositing_mode cannot both be true",
287+ )
266288 }
289+
290+ return validationErrors
267291 }
268292
269293 func validateColorScheme(config *Config) []string {
@@ -258,12 +258,36 @@ func validateOmnibox(config *Config) []string {
258 }258 }
259 259
260 func validateRendering(config *Config) []string {260 func validateRendering(config *Config) []string {
261- switch config.RenderingMode {261+ var validationErrors []string
262+
263+ switch config.Rendering.Mode {
262 case RenderingModeAuto, RenderingModeGPU, RenderingModeCPU, "":264 case RenderingModeAuto, RenderingModeGPU, RenderingModeCPU, "":
263- return nil
264 default:265 default:
265- return []string{fmt.Sprintf("rendering_mode must be one of: auto, gpu, cpu (got: %s)", config.RenderingMode)}266+ validationErrors = append(
267+ validationErrors,
268+ fmt.Sprintf("rendering.mode must be one of: auto, gpu, cpu (got: %s)", config.Rendering.Mode),
269+ )
270+ }
271+
272+ switch config.Rendering.GSKRenderer {
273+ case GSKRendererAuto, GSKRendererOpenGL, GSKRendererVulkan, GSKRendererCairo, "":
274+ default:
275+ validationErrors = append(
276+ validationErrors,
277+ fmt.Sprintf(
278+ "rendering.gsk_renderer must be one of: auto, opengl, vulkan, cairo (got: %s)",
279+ config.Rendering.GSKRenderer,
280+ ),
281+ )
282+ }
283+
284+ if config.Rendering.ForceCompositingMode && config.Rendering.DisableCompositingMode {
285+ validationErrors = append(validationErrors,
286+ "rendering.force_compositing_mode and rendering.disable_compositing_mode cannot both be true",
287+ )
266 }288 }
289+
290+ return validationErrors
267 }291 }
268 292
269 func validateColorScheme(config *Config) []string {293 func validateColorScheme(config *Config) []string {
M internal/infrastructure/config/watcher.go +1 -24
@@ -3,7 +3,6 @@ package config
33 import (
44 "fmt"
55 "os"
6- "strings"
76
87 "github.com/fsnotify/fsnotify"
98 )
@@ -69,29 +68,7 @@ func (m *Manager) reload() error {
6968 config.Database.Path = dbPath
7069 }
7170
72- // Normalize/validate rendering mode using switch statement
73- switch strings.ToLower(string(config.RenderingMode)) {
74- case "", string(RenderingModeAuto):
75- config.RenderingMode = RenderingModeAuto
76- case string(RenderingModeGPU):
77- config.RenderingMode = RenderingModeGPU
78- case string(RenderingModeCPU):
79- config.RenderingMode = RenderingModeCPU
80- default:
81- config.RenderingMode = RenderingModeAuto
82- }
83-
84- // Validate ColorScheme setting using switch statement
85- switch config.Appearance.ColorScheme {
86- case ThemePreferDark, ThemePreferLight, ThemeDefault:
87- // Valid values - no changes needed
88- case "":
89- // Empty string is treated the same as "default"
90- config.Appearance.ColorScheme = ThemeDefault
91- default:
92- // Invalid value - reset to default
93- config.Appearance.ColorScheme = ThemeDefault
94- }
71+ normalizeConfig(config)
9572
9673 // Validate all config values
9774 if err := validateConfig(config); err != nil {
@@ -3,7 +3,6 @@ package config
3 import (3 import (
4 "fmt"4 "fmt"
5 "os"5 "os"
6- "strings"
7 6
8 "github.com/fsnotify/fsnotify"7 "github.com/fsnotify/fsnotify"
9 )8 )
@@ -69,29 +68,7 @@ func (m *Manager) reload() error {
69 config.Database.Path = dbPath68 config.Database.Path = dbPath
70 }69 }
71 70
72- // Normalize/validate rendering mode using switch statement71+ normalizeConfig(config)
73- switch strings.ToLower(string(config.RenderingMode)) {
74- case "", string(RenderingModeAuto):
75- config.RenderingMode = RenderingModeAuto
76- case string(RenderingModeGPU):
77- config.RenderingMode = RenderingModeGPU
78- case string(RenderingModeCPU):
79- config.RenderingMode = RenderingModeCPU
80- default:
81- config.RenderingMode = RenderingModeAuto
82- }
83-
84- // Validate ColorScheme setting using switch statement
85- switch config.Appearance.ColorScheme {
86- case ThemePreferDark, ThemePreferLight, ThemeDefault:
87- // Valid values - no changes needed
88- case "":
89- // Empty string is treated the same as "default"
90- config.Appearance.ColorScheme = ThemeDefault
91- default:
92- // Invalid value - reset to default
93- config.Appearance.ColorScheme = ThemeDefault
94- }
95 72
96 // Validate all config values73 // Validate all config values
97 if err := validateConfig(config); err != nil {74 if err := validateConfig(config); err != nil {
A internal/infrastructure/env/manager.go +209 -0
@@ -0,0 +1,209 @@
1+// Package env provides environment variable management for rendering subsystems.
2+package env
3+
4+import (
5+ "context"
6+ "fmt"
7+ "os"
8+ "path/filepath"
9+ "strings"
10+
11+ "github.com/bnema/dumber/internal/application/port"
12+ "github.com/bnema/dumber/internal/logging"
13+)
14+
15+// Manager implements port.RenderingEnvManager for configuring
16+// environment variables for GStreamer, WebKit, and GTK/GSK.
17+type Manager struct {
18+ appliedVars map[string]string
19+ gpuVendor port.GPUVendor
20+}
21+
22+// NewManager creates a new environment manager.
23+func NewManager() *Manager {
24+ return &Manager{
25+ appliedVars: make(map[string]string),
26+ gpuVendor: port.GPUVendorUnknown,
27+ }
28+}
29+
30+// DetectGPUVendor identifies the primary GPU vendor from system info.
31+func (m *Manager) DetectGPUVendor(ctx context.Context) port.GPUVendor {
32+ log := logging.FromContext(ctx)
33+
34+ vendor := m.detectFromDRM()
35+ if vendor != port.GPUVendorUnknown {
36+ m.gpuVendor = vendor
37+ log.Debug().Str("vendor", string(vendor)).Msg("detected GPU vendor from DRM")
38+ return vendor
39+ }
40+
41+ log.Debug().Msg("could not detect GPU vendor, using unknown")
42+ m.gpuVendor = port.GPUVendorUnknown
43+ return port.GPUVendorUnknown
44+}
45+
46+// ApplyEnvironment sets all rendering environment variables.
47+// Must be called before GTK/WebKit initialization.
48+func (m *Manager) ApplyEnvironment(ctx context.Context, settings port.RenderingEnvSettings) error {
49+ log := logging.FromContext(ctx)
50+ m.appliedVars = make(map[string]string)
51+
52+ m.applyGStreamerEnv(settings)
53+ m.applyGPUSpecificEnv()
54+ m.applyWebKitEnv(settings)
55+ m.applyGTKEnv(settings)
56+ m.applyDebugEnv(settings)
57+
58+ log.Debug().
59+ Interface("vars", m.appliedVars).
60+ Str("gpu", string(m.gpuVendor)).
61+ Msg("applied rendering environment")
62+
63+ return nil
64+}
65+
66+func (m *Manager) applyGStreamerEnv(settings port.RenderingEnvSettings) {
67+ // VSync
68+ if settings.ForceVSync {
69+ m.setEnv("__GL_SYNC_TO_VBLANK", "1")
70+ m.setEnv("vblank_mode", "3")
71+ }
72+
73+ // GL API
74+ switch strings.ToLower(settings.GLRenderingMode) {
75+ case "gles2":
76+ m.setEnv("GST_GL_API", "gles2")
77+ case "gl3":
78+ m.setEnv("GST_GL_API", "opengl3")
79+ case "none":
80+ m.setEnv("GST_GL_API", "none")
81+ case "auto", "":
82+ // Don't set - let GStreamer decide
83+ }
84+
85+ // GStreamer debug level
86+ if settings.GStreamerDebugLevel > 0 {
87+ level := settings.GStreamerDebugLevel
88+ if level > 5 {
89+ level = 5
90+ }
91+ m.setEnv("GST_DEBUG", fmt.Sprintf("%d", level))
92+ }
93+
94+ // Video buffer size
95+ if settings.VideoBufferSizeMB > 0 {
96+ bufferBytes := settings.VideoBufferSizeMB * 1024 * 1024
97+ m.setEnv("GST_BUFFER_SIZE", fmt.Sprintf("%d", bufferBytes))
98+ m.setEnv("GST_QUEUE2_MAX_SIZE_BYTES", fmt.Sprintf("%d", bufferBytes))
99+ }
100+
101+ // Queue buffer time
102+ if settings.QueueBufferTimeSec > 0 {
103+ bufferTimeNs := settings.QueueBufferTimeSec * 1_000_000_000
104+ m.setEnv("GST_QUEUE2_MAX_SIZE_TIME", fmt.Sprintf("%d", bufferTimeNs))
105+ }
106+}
107+
108+func (m *Manager) applyGPUSpecificEnv() {
109+ if os.Getenv("LIBVA_DRIVER_NAME") != "" {
110+ return
111+ }
112+
113+ switch m.gpuVendor {
114+ case port.GPUVendorAMD:
115+ m.setEnv("LIBVA_DRIVER_NAME", "radeonsi")
116+ case port.GPUVendorIntel:
117+ m.setEnv("LIBVA_DRIVER_NAME", "iHD")
118+ case port.GPUVendorNVIDIA:
119+ m.setEnv("LIBVA_DRIVER_NAME", "nvidia")
120+ if os.Getenv("GST_GL_PLATFORM") == "" {
121+ m.setEnv("GST_GL_PLATFORM", "egl")
122+ }
123+ }
124+}
125+
126+func (m *Manager) applyWebKitEnv(settings port.RenderingEnvSettings) {
127+ if settings.DisableDMABufRenderer {
128+ m.setEnv("WEBKIT_DISABLE_DMABUF_RENDERER", "1")
129+ }
130+ if settings.ForceCompositingMode {
131+ m.setEnv("WEBKIT_FORCE_COMPOSITING_MODE", "1")
132+ }
133+ if settings.DisableCompositingMode {
134+ m.setEnv("WEBKIT_DISABLE_COMPOSITING_MODE", "1")
135+ }
136+}
137+
138+func (m *Manager) applyGTKEnv(settings port.RenderingEnvSettings) {
139+ renderer := strings.ToLower(strings.TrimSpace(settings.GSKRenderer))
140+ if renderer != "" && renderer != "auto" {
141+ m.setEnv("GSK_RENDERER", renderer)
142+ }
143+
144+ var gskDisable []string
145+ if settings.DisableMipmaps {
146+ gskDisable = append(gskDisable, "mipmap")
147+ }
148+ if len(gskDisable) > 0 {
149+ m.setEnv("GSK_GPU_DISABLE", strings.Join(gskDisable, ","))
150+ }
151+
152+ var gdkDebug []string
153+ if settings.PreferGL {
154+ gdkDebug = append(gdkDebug, "gl-prefer-gl")
155+ }
156+ if settings.DebugFrames {
157+ gdkDebug = append(gdkDebug, "frames")
158+ }
159+ if len(gdkDebug) > 0 {
160+ m.setEnv("GDK_DEBUG", strings.Join(gdkDebug, ","))
161+ }
162+}
163+
164+func (m *Manager) applyDebugEnv(settings port.RenderingEnvSettings) {
165+ if settings.ShowFPS {
166+ m.setEnv("WEBKIT_SHOW_FPS", "1")
167+ }
168+ if settings.SampleMemory {
169+ m.setEnv("WEBKIT_SAMPLE_MEMORY", "1")
170+ }
171+}
172+
173+func (m *Manager) setEnv(key, value string) {
174+ _ = os.Setenv(key, value)
175+ m.appliedVars[key] = value
176+}
177+
178+// GetAppliedVars returns a copy of applied environment variables.
179+func (m *Manager) GetAppliedVars() map[string]string {
180+ result := make(map[string]string, len(m.appliedVars))
181+ for k, v := range m.appliedVars {
182+ result[k] = v
183+ }
184+ return result
185+}
186+
187+func (*Manager) detectFromDRM() port.GPUVendor {
188+ const drmBase = "/sys/class/drm"
189+ for _, card := range []string{"card0", "card1"} {
190+ vendorPath := filepath.Join(drmBase, card, "device", "vendor")
191+ data, err := os.ReadFile(vendorPath)
192+ if err != nil {
193+ continue
194+ }
195+
196+ vendorID := strings.TrimSpace(string(data))
197+ switch vendorID {
198+ case "0x1002":
199+ return port.GPUVendorAMD
200+ case "0x8086":
201+ return port.GPUVendorIntel
202+ case "0x10de":
203+ return port.GPUVendorNVIDIA
204+ }
205+ }
206+ return port.GPUVendorUnknown
207+}
208+
209+var _ port.RenderingEnvManager = (*Manager)(nil)
@@ -0,0 +1,209 @@
1+// Package env provides environment variable management for rendering subsystems.
2+package env
3+
4+import (
5+ "context"
6+ "fmt"
7+ "os"
8+ "path/filepath"
9+ "strings"
10+
11+ "github.com/bnema/dumber/internal/application/port"
12+ "github.com/bnema/dumber/internal/logging"
13+)
14+
15+// Manager implements port.RenderingEnvManager for configuring
16+// environment variables for GStreamer, WebKit, and GTK/GSK.
17+type Manager struct {
18+ appliedVars map[string]string
19+ gpuVendor port.GPUVendor
20+}
21+
22+// NewManager creates a new environment manager.
23+func NewManager() *Manager {
24+ return &Manager{
25+ appliedVars: make(map[string]string),
26+ gpuVendor: port.GPUVendorUnknown,
27+ }
28+}
29+
30+// DetectGPUVendor identifies the primary GPU vendor from system info.
31+func (m *Manager) DetectGPUVendor(ctx context.Context) port.GPUVendor {
32+ log := logging.FromContext(ctx)
33+
34+ vendor := m.detectFromDRM()
35+ if vendor != port.GPUVendorUnknown {
36+ m.gpuVendor = vendor
37+ log.Debug().Str("vendor", string(vendor)).Msg("detected GPU vendor from DRM")
38+ return vendor
39+ }
40+
41+ log.Debug().Msg("could not detect GPU vendor, using unknown")
42+ m.gpuVendor = port.GPUVendorUnknown
43+ return port.GPUVendorUnknown
44+}
45+
46+// ApplyEnvironment sets all rendering environment variables.
47+// Must be called before GTK/WebKit initialization.
48+func (m *Manager) ApplyEnvironment(ctx context.Context, settings port.RenderingEnvSettings) error {
49+ log := logging.FromContext(ctx)
50+ m.appliedVars = make(map[string]string)
51+
52+ m.applyGStreamerEnv(settings)
53+ m.applyGPUSpecificEnv()
54+ m.applyWebKitEnv(settings)
55+ m.applyGTKEnv(settings)
56+ m.applyDebugEnv(settings)
57+
58+ log.Debug().
59+ Interface("vars", m.appliedVars).
60+ Str("gpu", string(m.gpuVendor)).
61+ Msg("applied rendering environment")
62+
63+ return nil
64+}
65+
66+func (m *Manager) applyGStreamerEnv(settings port.RenderingEnvSettings) {
67+ // VSync
68+ if settings.ForceVSync {
69+ m.setEnv("__GL_SYNC_TO_VBLANK", "1")
70+ m.setEnv("vblank_mode", "3")
71+ }
72+
73+ // GL API
74+ switch strings.ToLower(settings.GLRenderingMode) {
75+ case "gles2":
76+ m.setEnv("GST_GL_API", "gles2")
77+ case "gl3":
78+ m.setEnv("GST_GL_API", "opengl3")
79+ case "none":
80+ m.setEnv("GST_GL_API", "none")
81+ case "auto", "":
82+ // Don't set - let GStreamer decide
83+ }
84+
85+ // GStreamer debug level
86+ if settings.GStreamerDebugLevel > 0 {
87+ level := settings.GStreamerDebugLevel
88+ if level > 5 {
89+ level = 5
90+ }
91+ m.setEnv("GST_DEBUG", fmt.Sprintf("%d", level))
92+ }
93+
94+ // Video buffer size
95+ if settings.VideoBufferSizeMB > 0 {
96+ bufferBytes := settings.VideoBufferSizeMB * 1024 * 1024
97+ m.setEnv("GST_BUFFER_SIZE", fmt.Sprintf("%d", bufferBytes))
98+ m.setEnv("GST_QUEUE2_MAX_SIZE_BYTES", fmt.Sprintf("%d", bufferBytes))
99+ }
100+
101+ // Queue buffer time
102+ if settings.QueueBufferTimeSec > 0 {
103+ bufferTimeNs := settings.QueueBufferTimeSec * 1_000_000_000
104+ m.setEnv("GST_QUEUE2_MAX_SIZE_TIME", fmt.Sprintf("%d", bufferTimeNs))
105+ }
106+}
107+
108+func (m *Manager) applyGPUSpecificEnv() {
109+ if os.Getenv("LIBVA_DRIVER_NAME") != "" {
110+ return
111+ }
112+
113+ switch m.gpuVendor {
114+ case port.GPUVendorAMD:
115+ m.setEnv("LIBVA_DRIVER_NAME", "radeonsi")
116+ case port.GPUVendorIntel:
117+ m.setEnv("LIBVA_DRIVER_NAME", "iHD")
118+ case port.GPUVendorNVIDIA:
119+ m.setEnv("LIBVA_DRIVER_NAME", "nvidia")
120+ if os.Getenv("GST_GL_PLATFORM") == "" {
121+ m.setEnv("GST_GL_PLATFORM", "egl")
122+ }
123+ }
124+}
125+
126+func (m *Manager) applyWebKitEnv(settings port.RenderingEnvSettings) {
127+ if settings.DisableDMABufRenderer {
128+ m.setEnv("WEBKIT_DISABLE_DMABUF_RENDERER", "1")
129+ }
130+ if settings.ForceCompositingMode {
131+ m.setEnv("WEBKIT_FORCE_COMPOSITING_MODE", "1")
132+ }
133+ if settings.DisableCompositingMode {
134+ m.setEnv("WEBKIT_DISABLE_COMPOSITING_MODE", "1")
135+ }
136+}
137+
138+func (m *Manager) applyGTKEnv(settings port.RenderingEnvSettings) {
139+ renderer := strings.ToLower(strings.TrimSpace(settings.GSKRenderer))
140+ if renderer != "" && renderer != "auto" {
141+ m.setEnv("GSK_RENDERER", renderer)
142+ }
143+
144+ var gskDisable []string
145+ if settings.DisableMipmaps {
146+ gskDisable = append(gskDisable, "mipmap")
147+ }
148+ if len(gskDisable) > 0 {
149+ m.setEnv("GSK_GPU_DISABLE", strings.Join(gskDisable, ","))
150+ }
151+
152+ var gdkDebug []string
153+ if settings.PreferGL {
154+ gdkDebug = append(gdkDebug, "gl-prefer-gl")
155+ }
156+ if settings.DebugFrames {
157+ gdkDebug = append(gdkDebug, "frames")
158+ }
159+ if len(gdkDebug) > 0 {
160+ m.setEnv("GDK_DEBUG", strings.Join(gdkDebug, ","))
161+ }
162+}
163+
164+func (m *Manager) applyDebugEnv(settings port.RenderingEnvSettings) {
165+ if settings.ShowFPS {
166+ m.setEnv("WEBKIT_SHOW_FPS", "1")
167+ }
168+ if settings.SampleMemory {
169+ m.setEnv("WEBKIT_SAMPLE_MEMORY", "1")
170+ }
171+}
172+
173+func (m *Manager) setEnv(key, value string) {
174+ _ = os.Setenv(key, value)
175+ m.appliedVars[key] = value
176+}
177+
178+// GetAppliedVars returns a copy of applied environment variables.
179+func (m *Manager) GetAppliedVars() map[string]string {
180+ result := make(map[string]string, len(m.appliedVars))
181+ for k, v := range m.appliedVars {
182+ result[k] = v
183+ }
184+ return result
185+}
186+
187+func (*Manager) detectFromDRM() port.GPUVendor {
188+ const drmBase = "/sys/class/drm"
189+ for _, card := range []string{"card0", "card1"} {
190+ vendorPath := filepath.Join(drmBase, card, "device", "vendor")
191+ data, err := os.ReadFile(vendorPath)
192+ if err != nil {
193+ continue
194+ }
195+
196+ vendorID := strings.TrimSpace(string(data))
197+ switch vendorID {
198+ case "0x1002":
199+ return port.GPUVendorAMD
200+ case "0x8086":
201+ return port.GPUVendorIntel
202+ case "0x10de":
203+ return port.GPUVendorNVIDIA
204+ }
205+ }
206+ return port.GPUVendorUnknown
207+}
208+
209+var _ port.RenderingEnvManager = (*Manager)(nil)
D internal/infrastructure/media/env_manager.go +0 -175
@@ -1,175 +0,0 @@
1-// Package media provides video playback configuration and diagnostics.
2-package media
3-
4-import (
5- "context"
6- "fmt"
7- "os"
8- "path/filepath"
9- "strings"
10-
11- "github.com/bnema/dumber/internal/application/port"
12- "github.com/bnema/dumber/internal/logging"
13-)
14-
15-// EnvManager implements port.GStreamerEnvManager for configuring
16-// GStreamer environment variables based on GPU vendor and user config.
17-type EnvManager struct {
18- appliedVars map[string]string
19- gpuVendor port.GPUVendor
20-}
21-
22-// NewEnvManager creates a new GStreamer environment manager.
23-func NewEnvManager() *EnvManager {
24- return &EnvManager{
25- appliedVars: make(map[string]string),
26- gpuVendor: port.GPUVendorUnknown,
27- }
28-}
29-
30-// DetectGPUVendor identifies the primary GPU vendor from system info.
31-func (e *EnvManager) DetectGPUVendor(ctx context.Context) port.GPUVendor {
32- log := logging.FromContext(ctx)
33-
34- // Try reading from /sys/class/drm/card*/device/vendor
35- vendor := e.detectFromDRM()
36- if vendor != port.GPUVendorUnknown {
37- e.gpuVendor = vendor
38- log.Debug().Str("vendor", string(vendor)).Msg("detected GPU vendor from DRM")
39- return vendor
40- }
41-
42- log.Debug().Msg("could not detect GPU vendor, using unknown")
43- e.gpuVendor = port.GPUVendorUnknown
44- return port.GPUVendorUnknown
45-}
46-
47-// detectFromDRM reads GPU vendor from /sys/class/drm/card*/device/vendor.
48-func (*EnvManager) detectFromDRM() port.GPUVendor {
49- const drmBase = "/sys/class/drm"
50- // Check card0 first (primary GPU), then card1 if needed
51- for _, card := range []string{"card0", "card1"} {
52- vendorPath := filepath.Join(drmBase, card, "device", "vendor")
53- data, err := os.ReadFile(vendorPath)
54- if err != nil {
55- continue
56- }
57-
58- vendorID := strings.TrimSpace(string(data))
59- switch vendorID {
60- case "0x1002":
61- return port.GPUVendorAMD
62- case "0x8086":
63- return port.GPUVendorIntel
64- case "0x10de":
65- return port.GPUVendorNVIDIA
66- }
67- }
68- return port.GPUVendorUnknown
69-}
70-
71-// ApplyEnvironment sets GStreamer environment variables based on settings and GPU.
72-// Must be called before GTK/WebKit initialization.
73-func (e *EnvManager) ApplyEnvironment(ctx context.Context, settings port.GStreamerEnvSettings) error {
74- log := logging.FromContext(ctx)
75-
76- // Clear previous applied vars
77- e.appliedVars = make(map[string]string)
78-
79- // 1. Apply VSync settings
80- if settings.ForceVSync {
81- e.setEnv("__GL_SYNC_TO_VBLANK", "1")
82- e.setEnv("vblank_mode", "3") // Mesa DRI config: always sync
83- }
84-
85- // 2. Apply GL rendering mode
86- switch strings.ToLower(settings.GLRenderingMode) {
87- case "gles2":
88- e.setEnv("GST_GL_API", "gles2")
89- case "gl3":
90- e.setEnv("GST_GL_API", "opengl3")
91- case "none":
92- e.setEnv("GST_GL_API", "none")
93- case "auto", "":
94- // Don't set - let GStreamer decide
95- }
96-
97- // 3. GStreamer debug level
98- if settings.GStreamerDebugLevel > 0 {
99- level := settings.GStreamerDebugLevel
100- if level > 5 {
101- level = 5 // Cap at max verbosity
102- }
103- e.setEnv("GST_DEBUG", fmt.Sprintf("%d", level))
104- }
105-
106- // 4. GPU-specific optimizations (only if not already set by user)
107- e.applyGPUSpecificEnv()
108-
109- // 5. Video buffer size (for smoother streaming)
110- if settings.VideoBufferSizeMB > 0 {
111- bufferBytes := settings.VideoBufferSizeMB * 1024 * 1024
112- e.setEnv("GST_BUFFER_SIZE", fmt.Sprintf("%d", bufferBytes))
113- e.setEnv("GST_QUEUE2_MAX_SIZE_BYTES", fmt.Sprintf("%d", bufferBytes))
114- }
115-
116- // 6. Queue buffer time (prebuffering duration)
117- if settings.QueueBufferTimeSec > 0 {
118- // Convert seconds to nanoseconds for GStreamer
119- bufferTimeNs := settings.QueueBufferTimeSec * 1_000_000_000
120- e.setEnv("GST_QUEUE2_MAX_SIZE_TIME", fmt.Sprintf("%d", bufferTimeNs))
121- }
122-
123- log.Debug().
124- Interface("vars", e.appliedVars).
125- Str("gpu", string(e.gpuVendor)).
126- Msg("applied gstreamer environment")
127-
128- return nil
129-}
130-
131-// applyGPUSpecificEnv sets GPU vendor-specific environment variables.
132-func (e *EnvManager) applyGPUSpecificEnv() {
133- // Only set LIBVA_DRIVER_NAME if not already set by user
134- if os.Getenv("LIBVA_DRIVER_NAME") != "" {
135- return
136- }
137-
138- switch e.gpuVendor {
139- case port.GPUVendorAMD:
140- // AMD uses radeonsi driver (Mesa VA-API)
141- e.setEnv("LIBVA_DRIVER_NAME", "radeonsi")
142-
143- case port.GPUVendorIntel:
144- // Intel uses iHD (modern) or i965 (legacy)
145- // iHD is preferred for Gen 8+ (Broadwell and newer)
146- e.setEnv("LIBVA_DRIVER_NAME", "iHD")
147-
148- case port.GPUVendorNVIDIA:
149- // NVIDIA uses nvidia-vaapi-driver
150- e.setEnv("LIBVA_DRIVER_NAME", "nvidia")
151- // NVIDIA benefits from EGL platform for better performance
152- if os.Getenv("GST_GL_PLATFORM") == "" {
153- e.setEnv("GST_GL_PLATFORM", "egl")
154- }
155- }
156-}
157-
158-// setEnv sets an environment variable and tracks it.
159-func (e *EnvManager) setEnv(key, value string) {
160- _ = os.Setenv(key, value) // Error ignored: setenv rarely fails
161- e.appliedVars[key] = value
162-}
163-
164-// GetAppliedVars returns a map of environment variables that were set.
165-func (e *EnvManager) GetAppliedVars() map[string]string {
166- // Return a copy to prevent mutation
167- result := make(map[string]string, len(e.appliedVars))
168- for k, v := range e.appliedVars {
169- result[k] = v
170- }
171- return result
172-}
173-
174-// Ensure EnvManager implements port.GStreamerEnvManager
175-var _ port.GStreamerEnvManager = (*EnvManager)(nil)
@@ -1,175 +0,0 @@
1-// Package media provides video playback configuration and diagnostics.
2-package media
3-
4-import (
5- "context"
6- "fmt"
7- "os"
8- "path/filepath"
9- "strings"
10-
11- "github.com/bnema/dumber/internal/application/port"
12- "github.com/bnema/dumber/internal/logging"
13-)
14-
15-// EnvManager implements port.GStreamerEnvManager for configuring
16-// GStreamer environment variables based on GPU vendor and user config.
17-type EnvManager struct {
18- appliedVars map[string]string
19- gpuVendor port.GPUVendor
20-}
21-
22-// NewEnvManager creates a new GStreamer environment manager.
23-func NewEnvManager() *EnvManager {
24- return &EnvManager{
25- appliedVars: make(map[string]string),
26- gpuVendor: port.GPUVendorUnknown,
27- }
28-}
29-
30-// DetectGPUVendor identifies the primary GPU vendor from system info.
31-func (e *EnvManager) DetectGPUVendor(ctx context.Context) port.GPUVendor {
32- log := logging.FromContext(ctx)
33-
34- // Try reading from /sys/class/drm/card*/device/vendor
35- vendor := e.detectFromDRM()
36- if vendor != port.GPUVendorUnknown {
37- e.gpuVendor = vendor
38- log.Debug().Str("vendor", string(vendor)).Msg("detected GPU vendor from DRM")
39- return vendor
40- }
41-
42- log.Debug().Msg("could not detect GPU vendor, using unknown")
43- e.gpuVendor = port.GPUVendorUnknown
44- return port.GPUVendorUnknown
45-}
46-
47-// detectFromDRM reads GPU vendor from /sys/class/drm/card*/device/vendor.
48-func (*EnvManager) detectFromDRM() port.GPUVendor {
49- const drmBase = "/sys/class/drm"
50- // Check card0 first (primary GPU), then card1 if needed
51- for _, card := range []string{"card0", "card1"} {
52- vendorPath := filepath.Join(drmBase, card, "device", "vendor")
53- data, err := os.ReadFile(vendorPath)
54- if err != nil {
55- continue
56- }
57-
58- vendorID := strings.TrimSpace(string(data))
59- switch vendorID {
60- case "0x1002":
61- return port.GPUVendorAMD
62- case "0x8086":
63- return port.GPUVendorIntel
64- case "0x10de":
65- return port.GPUVendorNVIDIA
66- }
67- }
68- return port.GPUVendorUnknown
69-}
70-
71-// ApplyEnvironment sets GStreamer environment variables based on settings and GPU.
72-// Must be called before GTK/WebKit initialization.
73-func (e *EnvManager) ApplyEnvironment(ctx context.Context, settings port.GStreamerEnvSettings) error {
74- log := logging.FromContext(ctx)
75-
76- // Clear previous applied vars
77- e.appliedVars = make(map[string]string)
78-
79- // 1. Apply VSync settings
80- if settings.ForceVSync {
81- e.setEnv("__GL_SYNC_TO_VBLANK", "1")
82- e.setEnv("vblank_mode", "3") // Mesa DRI config: always sync
83- }
84-
85- // 2. Apply GL rendering mode
86- switch strings.ToLower(settings.GLRenderingMode) {
87- case "gles2":
88- e.setEnv("GST_GL_API", "gles2")
89- case "gl3":
90- e.setEnv("GST_GL_API", "opengl3")
91- case "none":
92- e.setEnv("GST_GL_API", "none")
93- case "auto", "":
94- // Don't set - let GStreamer decide
95- }
96-
97- // 3. GStreamer debug level
98- if settings.GStreamerDebugLevel > 0 {
99- level := settings.GStreamerDebugLevel
100- if level > 5 {
101- level = 5 // Cap at max verbosity
102- }
103- e.setEnv("GST_DEBUG", fmt.Sprintf("%d", level))
104- }
105-
106- // 4. GPU-specific optimizations (only if not already set by user)
107- e.applyGPUSpecificEnv()
108-
109- // 5. Video buffer size (for smoother streaming)
110- if settings.VideoBufferSizeMB > 0 {
111- bufferBytes := settings.VideoBufferSizeMB * 1024 * 1024
112- e.setEnv("GST_BUFFER_SIZE", fmt.Sprintf("%d", bufferBytes))
113- e.setEnv("GST_QUEUE2_MAX_SIZE_BYTES", fmt.Sprintf("%d", bufferBytes))
114- }
115-
116- // 6. Queue buffer time (prebuffering duration)
117- if settings.QueueBufferTimeSec > 0 {
118- // Convert seconds to nanoseconds for GStreamer
119- bufferTimeNs := settings.QueueBufferTimeSec * 1_000_000_000
120- e.setEnv("GST_QUEUE2_MAX_SIZE_TIME", fmt.Sprintf("%d", bufferTimeNs))
121- }
122-
123- log.Debug().
124- Interface("vars", e.appliedVars).
125- Str("gpu", string(e.gpuVendor)).
126- Msg("applied gstreamer environment")
127-
128- return nil
129-}
130-
131-// applyGPUSpecificEnv sets GPU vendor-specific environment variables.
132-func (e *EnvManager) applyGPUSpecificEnv() {
133- // Only set LIBVA_DRIVER_NAME if not already set by user
134- if os.Getenv("LIBVA_DRIVER_NAME") != "" {
135- return
136- }
137-
138- switch e.gpuVendor {
139- case port.GPUVendorAMD:
140- // AMD uses radeonsi driver (Mesa VA-API)
141- e.setEnv("LIBVA_DRIVER_NAME", "radeonsi")
142-
143- case port.GPUVendorIntel:
144- // Intel uses iHD (modern) or i965 (legacy)
145- // iHD is preferred for Gen 8+ (Broadwell and newer)
146- e.setEnv("LIBVA_DRIVER_NAME", "iHD")
147-
148- case port.GPUVendorNVIDIA:
149- // NVIDIA uses nvidia-vaapi-driver
150- e.setEnv("LIBVA_DRIVER_NAME", "nvidia")
151- // NVIDIA benefits from EGL platform for better performance
152- if os.Getenv("GST_GL_PLATFORM") == "" {
153- e.setEnv("GST_GL_PLATFORM", "egl")
154- }
155- }
156-}
157-
158-// setEnv sets an environment variable and tracks it.
159-func (e *EnvManager) setEnv(key, value string) {
160- _ = os.Setenv(key, value) // Error ignored: setenv rarely fails
161- e.appliedVars[key] = value
162-}
163-
164-// GetAppliedVars returns a map of environment variables that were set.
165-func (e *EnvManager) GetAppliedVars() map[string]string {
166- // Return a copy to prevent mutation
167- result := make(map[string]string, len(e.appliedVars))
168- for k, v := range e.appliedVars {
169- result[k] = v
170- }
171- return result
172-}
173-
174-// Ensure EnvManager implements port.GStreamerEnvManager
175-var _ port.GStreamerEnvManager = (*EnvManager)(nil)
M internal/infrastructure/webkit/settings.go +3 -2
@@ -62,7 +62,7 @@ func (sm *SettingsManager) applySettings(ctx context.Context, settings *webkit.S
6262
6363 log.Debug().
6464 Str("sans_font", cfg.Appearance.SansFont).
65- Str("rendering_mode", string(cfg.RenderingMode)).
65+ Str("rendering_mode", string(cfg.Rendering.Mode)).
6666 Bool("developer_extras", cfg.Debug.EnableDevTools).
6767 Msg("settings applied")
6868 }
@@ -89,7 +89,7 @@ func applyFontSettings(settings *webkit.Settings, cfg *config.Config) {
8989 }
9090
9191 func applyRenderingSettings(settings *webkit.Settings, cfg *config.Config) {
92- switch cfg.RenderingMode {
92+ switch cfg.Rendering.Mode {
9393 case config.RenderingModeGPU:
9494 settings.SetHardwareAccelerationPolicy(webkit.HardwareAccelerationPolicyAlwaysValue)
9595 case config.RenderingModeCPU:
@@ -102,6 +102,7 @@ func applyRenderingSettings(settings *webkit.Settings, cfg *config.Config) {
102102 func applyDebugSettings(settings *webkit.Settings, cfg *config.Config) {
103103 settings.SetEnableDeveloperExtras(cfg.Debug.EnableDevTools)
104104 settings.SetEnableWriteConsoleMessagesToStdout(cfg.Logging.CaptureConsole)
105+ settings.SetDrawCompositingIndicators(cfg.Rendering.DrawCompositingIndicators)
105106 }
106107
107108 func applyBrowsingSettings(settings *webkit.Settings) {
@@ -62,7 +62,7 @@ func (sm *SettingsManager) applySettings(ctx context.Context, settings *webkit.S
62 62
63 log.Debug().63 log.Debug().
64 Str("sans_font", cfg.Appearance.SansFont).64 Str("sans_font", cfg.Appearance.SansFont).
65- Str("rendering_mode", string(cfg.RenderingMode)).65+ Str("rendering_mode", string(cfg.Rendering.Mode)).
66 Bool("developer_extras", cfg.Debug.EnableDevTools).66 Bool("developer_extras", cfg.Debug.EnableDevTools).
67 Msg("settings applied")67 Msg("settings applied")
68 }68 }
@@ -89,7 +89,7 @@ func applyFontSettings(settings *webkit.Settings, cfg *config.Config) {
89 }89 }
90 90
91 func applyRenderingSettings(settings *webkit.Settings, cfg *config.Config) {91 func applyRenderingSettings(settings *webkit.Settings, cfg *config.Config) {
92- switch cfg.RenderingMode {92+ switch cfg.Rendering.Mode {
93 case config.RenderingModeGPU:93 case config.RenderingModeGPU:
94 settings.SetHardwareAccelerationPolicy(webkit.HardwareAccelerationPolicyAlwaysValue)94 settings.SetHardwareAccelerationPolicy(webkit.HardwareAccelerationPolicyAlwaysValue)
95 case config.RenderingModeCPU:95 case config.RenderingModeCPU:
@@ -102,6 +102,7 @@ func applyRenderingSettings(settings *webkit.Settings, cfg *config.Config) {
102 func applyDebugSettings(settings *webkit.Settings, cfg *config.Config) {102 func applyDebugSettings(settings *webkit.Settings, cfg *config.Config) {
103 settings.SetEnableDeveloperExtras(cfg.Debug.EnableDevTools)103 settings.SetEnableDeveloperExtras(cfg.Debug.EnableDevTools)
104 settings.SetEnableWriteConsoleMessagesToStdout(cfg.Logging.CaptureConsole)104 settings.SetEnableWriteConsoleMessagesToStdout(cfg.Logging.CaptureConsole)
105+ settings.SetDrawCompositingIndicators(cfg.Rendering.DrawCompositingIndicators)
105 }106 }
106 107
107 func applyBrowsingSettings(settings *webkit.Settings) {108 func applyBrowsingSettings(settings *webkit.Settings) {

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