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 ( | |||
| 17 | 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 | 21 | // 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. | |
| 24 | 25 | ForceVSync bool | |
| 25 | - | // GLRenderingMode controls OpenGL API selection: "auto", "gles2", "gl3", "none" | |
| 26 | + | // GLRenderingMode controls OpenGL API selection: "auto", "gles2", "gl3", "none". | |
| 26 | 27 | GLRenderingMode string | |
| 27 | - | // GStreamerDebugLevel sets GStreamer debug verbosity (0-5) | |
| 28 | + | // GStreamerDebugLevel sets GStreamer debug verbosity (0-5). | |
| 28 | 29 | GStreamerDebugLevel int | |
| 29 | - | // VideoBufferSizeMB sets buffer size in megabytes for video streaming | |
| 30 | + | // VideoBufferSizeMB sets buffer size in megabytes for video streaming. | |
| 30 | 31 | VideoBufferSizeMB int | |
| 31 | - | // QueueBufferTimeSec sets queue buffer time in seconds for prebuffering | |
| 32 | + | // QueueBufferTimeSec sets queue buffer time in seconds for prebuffering. | |
| 32 | 33 | 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 variables | |
| 36 | - | // based on system configuration and detected GPU vendor. | |
| 51 | + | // RenderingEnvManager configures rendering environment variables | |
| 52 | + | // for GStreamer, WebKit, and GTK/GSK. | |
| 37 | 53 | // Environment variables must be set BEFORE GTK/WebKit initialization. | |
| 38 | - | type GStreamerEnvManager interface { | |
| 54 | + | type RenderingEnvManager interface { | |
| 39 | 55 | // DetectGPUVendor identifies the primary GPU vendor from system info. | |
| 40 | 56 | // Uses /sys/class/drm/card*/device/vendor as primary detection method. | |
| 41 | 57 | DetectGPUVendor(ctx context.Context) GPUVendor | |
| 42 | 58 | ||
| 43 | - | // ApplyEnvironment sets GStreamer environment variables based on | |
| 44 | - | // the provided settings and detected GPU vendor. | |
| 59 | + | // ApplyEnvironment sets all rendering environment variables. | |
| 45 | 60 | // Must be called before any GTK/GStreamer initialization. | |
| 46 | - | ApplyEnvironment(ctx context.Context, settings GStreamerEnvSettings) error | |
| 61 | + | ApplyEnvironment(ctx context.Context, settings RenderingEnvSettings) error | |
| 47 | 62 | ||
| 48 | 63 | // GetAppliedVars returns a map of environment variables that were set. | |
| 49 | 64 | // 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 playback | 23 | + | // --- GStreamer settings (from MediaConfig) --- |
| 24 | + | // ForceVSync enables vertical sync for video playback. | |||
| 24 | ForceVSync bool | 25 | ForceVSync bool | ||
| 25 | - | // GLRenderingMode controls OpenGL API selection: "auto", "gles2", "gl3", "none" | 26 | + | // GLRenderingMode controls OpenGL API selection: "auto", "gles2", "gl3", "none". |
| 26 | GLRenderingMode string | 27 | GLRenderingMode string | ||
| 27 | - | // GStreamerDebugLevel sets GStreamer debug verbosity (0-5) | 28 | + | // GStreamerDebugLevel sets GStreamer debug verbosity (0-5). |
| 28 | GStreamerDebugLevel int | 29 | GStreamerDebugLevel int | ||
| 29 | - | // VideoBufferSizeMB sets buffer size in megabytes for video streaming | 30 | + | // VideoBufferSizeMB sets buffer size in megabytes for video streaming. |
| 30 | VideoBufferSizeMB int | 31 | VideoBufferSizeMB int | ||
| 31 | - | // QueueBufferTimeSec sets queue buffer time in seconds for prebuffering | 32 | + | // QueueBufferTimeSec sets queue buffer time in seconds for prebuffering. |
| 32 | QueueBufferTimeSec int | 33 | 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 variables | 51 | + | // 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) GPUVendor | 57 | DetectGPUVendor(ctx context.Context) GPUVendor | ||
| 42 | 58 | ||||
| 43 | - | // ApplyEnvironment sets GStreamer environment variables based on | 59 | + | // 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) error | 61 | + | 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 ( | |||
| 8 | 8 | "github.com/bnema/dumber/internal/application/port" | |
| 9 | 9 | "github.com/bnema/dumber/internal/application/usecase" | |
| 10 | 10 | "github.com/bnema/dumber/internal/infrastructure/config" | |
| 11 | + | "github.com/bnema/dumber/internal/infrastructure/env" | |
| 11 | 12 | "github.com/bnema/dumber/internal/infrastructure/filtering" | |
| 12 | - | "github.com/bnema/dumber/internal/infrastructure/media" | |
| 13 | 13 | "github.com/bnema/dumber/internal/infrastructure/webkit" | |
| 14 | 14 | "github.com/bnema/dumber/internal/ui/theme" | |
| 15 | 15 | "github.com/rs/zerolog" | |
| @@ -32,24 +32,40 @@ func BuildWebKitStack( | |||
| 32 | 32 | themeManager *theme.Manager, | |
| 33 | 33 | logger zerolog.Logger, | |
| 34 | 34 | ) 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 | |
| 40 | 41 | ForceVSync: cfg.Media.ForceVSync, | |
| 41 | 42 | GLRenderingMode: string(cfg.Media.GLRenderingMode), | |
| 42 | 43 | GStreamerDebugLevel: cfg.Media.GStreamerDebugLevel, | |
| 43 | 44 | VideoBufferSizeMB: cfg.Media.VideoBufferSizeMB, | |
| 44 | 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 { | |
| 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") | |
| 48 | 64 | } | |
| 49 | 65 | logger.Info(). | |
| 50 | 66 | Str("gpu", string(gpuVendor)). | |
| 51 | - | Interface("vars", gstEnv.GetAppliedVars()). | |
| 52 | - | Msg("gstreamer environment configured") | |
| 67 | + | Interface("vars", renderEnv.GetAppliedVars()). | |
| 68 | + | Msg("rendering environment configured") | |
| 53 | 69 | ||
| 54 | 70 | wkCtx, err := webkit.NewWebKitContext(ctx, dataDir, cacheDir) | |
| 55 | 71 | 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 { | |||
| 112 | 112 | Debug: DebugConfig{ | |
| 113 | 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 | 128 | DefaultWebpageZoom: 1.2, // 120% default zoom for better readability | |
| 117 | 129 | DefaultUIScale: defaultUIScale, // 1.0 = 100%, 2.0 = 200% | |
| 118 | 130 | 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 readability | 128 | 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) { | |||
| 46 | 46 | // The explicit bindings below are only for special cases with different naming patterns. | |
| 47 | 47 | ||
| 48 | 48 | // 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 | 49 | if err := v.BindEnv("default_webpage_zoom", "DUMBER_DEFAULT_WEBPAGE_ZOOM"); err != nil { | |
| 53 | 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 | 157 | func normalizeConfig(config *Config) { | |
| 161 | - | switch strings.ToLower(string(config.RenderingMode)) { | |
| 158 | + | switch strings.ToLower(string(config.Rendering.Mode)) { | |
| 162 | 159 | case "", string(RenderingModeAuto): | |
| 163 | - | config.RenderingMode = RenderingModeAuto | |
| 160 | + | config.Rendering.Mode = RenderingModeAuto | |
| 164 | 161 | case string(RenderingModeGPU): | |
| 165 | - | config.RenderingMode = RenderingModeGPU | |
| 162 | + | config.Rendering.Mode = RenderingModeGPU | |
| 166 | 163 | 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 | |
| 168 | 178 | default: | |
| 169 | - | config.RenderingMode = RenderingModeAuto | |
| 179 | + | config.Rendering.GSKRenderer = GSKRendererAuto | |
| 170 | 180 | } | |
| 171 | 181 | ||
| 172 | 182 | switch config.Appearance.ColorScheme { | |
| @@ -352,7 +362,17 @@ func (m *Manager) setAppearanceDefaults(defaults *Config) { | |||
| 352 | 362 | } | |
| 353 | 363 | ||
| 354 | 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 | 376 | m.viper.SetDefault("default_webpage_zoom", defaults.DefaultWebpageZoom) | |
| 357 | 377 | m.viper.SetDefault("default_ui_scale", defaults.DefaultUIScale) | |
| 358 | 378 | } | |
| @@ -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 vars | 48 | // 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 = RenderingModeAuto | 160 | + | config.Rendering.Mode = RenderingModeAuto |
| 164 | case string(RenderingModeGPU): | 161 | case string(RenderingModeGPU): | ||
| 165 | - | config.RenderingMode = RenderingModeGPU | 162 | + | config.Rendering.Mode = RenderingModeGPU |
| 166 | case string(RenderingModeCPU): | 163 | 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 | |||
| 168 | default: | 178 | default: | ||
| 169 | - | config.RenderingMode = RenderingModeAuto | 179 | + | 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 { | |||
| 11 | 11 | Logging LoggingConfig `mapstructure:"logging" yaml:"logging" toml:"logging"` | |
| 12 | 12 | Appearance AppearanceConfig `mapstructure:"appearance" yaml:"appearance" toml:"appearance"` | |
| 13 | 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 | 14 | // DefaultWebpageZoom sets the default zoom level for pages without saved zoom settings (1.0 = 100%, 1.2 = 120%) | |
| 17 | 15 | DefaultWebpageZoom float64 `mapstructure:"default_webpage_zoom" yaml:"default_webpage_zoom" toml:"default_webpage_zoom"` | |
| 18 | 16 | // DefaultUIScale sets the default UI scale for GTK widgets (1.0 = 100%, 2.0 = 200%) | |
| @@ -23,6 +21,8 @@ type Config struct { | |||
| 23 | 21 | ContentFiltering ContentFilteringConfig `mapstructure:"content_filtering" yaml:"content_filtering" toml:"content_filtering"` | |
| 24 | 22 | // Omnibox controls the omnibox behavior (initial history display) | |
| 25 | 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 | 26 | // Media controls video playback and hardware acceleration | |
| 27 | 27 | Media MediaConfig `mapstructure:"media" yaml:"media" toml:"media"` | |
| 28 | 28 | // Runtime configures optional runtime overrides (e.g., /opt prefix for WebKitGTK/GTK). | |
| @@ -38,6 +38,61 @@ const ( | |||
| 38 | 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 | 96 | // HardwareDecodingMode controls video hardware acceleration. | |
| 42 | 97 | type HardwareDecodingMode string | |
| 43 | 98 | ||
| @@ -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 acceleration | 26 | // 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 string | 97 | type HardwareDecodingMode string | ||
| 43 | 98 | ||||
M internal/infrastructure/config/validation.go +27 -3
| @@ -258,12 +258,36 @@ func validateOmnibox(config *Config) []string { | |||
| 258 | 258 | } | |
| 259 | 259 | ||
| 260 | 260 | func validateRendering(config *Config) []string { | |
| 261 | - | switch config.RenderingMode { | |
| 261 | + | var validationErrors []string | |
| 262 | + | ||
| 263 | + | switch config.Rendering.Mode { | |
| 262 | 264 | case RenderingModeAuto, RenderingModeGPU, RenderingModeCPU, "": | |
| 263 | - | return nil | |
| 264 | 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 | 293 | 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 | |||
| 3 | 3 | import ( | |
| 4 | 4 | "fmt" | |
| 5 | 5 | "os" | |
| 6 | - | "strings" | |
| 7 | 6 | ||
| 8 | 7 | "github.com/fsnotify/fsnotify" | |
| 9 | 8 | ) | |
| @@ -69,29 +68,7 @@ func (m *Manager) reload() error { | |||
| 69 | 68 | config.Database.Path = dbPath | |
| 70 | 69 | } | |
| 71 | 70 | ||
| 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) | |
| 95 | 72 | ||
| 96 | 73 | // Validate all config values | |
| 97 | 74 | 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 = dbPath | 68 | config.Database.Path = dbPath | ||
| 70 | } | 69 | } | ||
| 71 | 70 | ||||
| 72 | - | // Normalize/validate rendering mode using switch statement | 71 | + | 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 values | 73 | // 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 | |||
| 62 | 62 | ||
| 63 | 63 | log.Debug(). | |
| 64 | 64 | Str("sans_font", cfg.Appearance.SansFont). | |
| 65 | - | Str("rendering_mode", string(cfg.RenderingMode)). | |
| 65 | + | Str("rendering_mode", string(cfg.Rendering.Mode)). | |
| 66 | 66 | Bool("developer_extras", cfg.Debug.EnableDevTools). | |
| 67 | 67 | Msg("settings applied") | |
| 68 | 68 | } | |
| @@ -89,7 +89,7 @@ func applyFontSettings(settings *webkit.Settings, cfg *config.Config) { | |||
| 89 | 89 | } | |
| 90 | 90 | ||
| 91 | 91 | func applyRenderingSettings(settings *webkit.Settings, cfg *config.Config) { | |
| 92 | - | switch cfg.RenderingMode { | |
| 92 | + | switch cfg.Rendering.Mode { | |
| 93 | 93 | case config.RenderingModeGPU: | |
| 94 | 94 | settings.SetHardwareAccelerationPolicy(webkit.HardwareAccelerationPolicyAlwaysValue) | |
| 95 | 95 | case config.RenderingModeCPU: | |
| @@ -102,6 +102,7 @@ func applyRenderingSettings(settings *webkit.Settings, cfg *config.Config) { | |||
| 102 | 102 | func applyDebugSettings(settings *webkit.Settings, cfg *config.Config) { | |
| 103 | 103 | settings.SetEnableDeveloperExtras(cfg.Debug.EnableDevTools) | |
| 104 | 104 | settings.SetEnableWriteConsoleMessagesToStdout(cfg.Logging.CaptureConsole) | |
| 105 | + | settings.SetDrawCompositingIndicators(cfg.Rendering.DrawCompositingIndicators) | |
| 105 | 106 | } | |
| 106 | 107 | ||
| 107 | 108 | 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) { | ||