v0.23.1 6c872d5

manager.go RawHistoryFolder

1// Package env provides environment variable management for rendering subsystems.
2package env
3
4import (
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// IsFlatpak returns true if the application is running inside a Flatpak sandbox.
16// The Flatpak runtime provides all required libraries, so host pkg-config checks
17// should be skipped when running in this environment.
18func IsFlatpak() bool {
19	_, err := os.Stat("/.flatpak-info")
20	return err == nil
21}
22
23// Manager implements port.RenderingEnvManager for configuring
24// environment variables for GStreamer, WebKit, and GTK/GSK.
25type Manager struct {
26	appliedVars map[string]string
27	gpuVendor   port.GPUVendor
28}
29
30// NewManager creates a new environment manager.
31func NewManager() *Manager {
32	return &Manager{
33		appliedVars: make(map[string]string),
34		gpuVendor:   port.GPUVendorUnknown,
35	}
36}
37
38// DetectGPUVendor identifies the primary GPU vendor from system info.
39func (m *Manager) DetectGPUVendor(ctx context.Context) port.GPUVendor {
40	log := logging.FromContext(ctx)
41
42	vendor := m.detectFromDRM()
43	if vendor != port.GPUVendorUnknown {
44		m.gpuVendor = vendor
45		log.Debug().Str("vendor", string(vendor)).Msg("detected GPU vendor from DRM")
46		return vendor
47	}
48
49	log.Debug().Msg("could not detect GPU vendor, using unknown")
50	m.gpuVendor = port.GPUVendorUnknown
51	return port.GPUVendorUnknown
52}
53
54// ApplyEnvironment sets all rendering environment variables.
55// Must be called before GTK/WebKit initialization.
56func (m *Manager) ApplyEnvironment(ctx context.Context, settings port.RenderingEnvSettings) error {
57	log := logging.FromContext(ctx)
58	m.appliedVars = make(map[string]string)
59
60	m.applyGStreamerEnv(settings)
61	m.applyGPUSpecificEnv()
62	m.applyWebKitEnv(settings)
63	m.applyGTKEnv(settings)
64	m.applyDebugEnv(settings)
65	m.applySkiaEnv(settings)
66
67	log.Debug().
68		Interface("vars", m.appliedVars).
69		Str("gpu", string(m.gpuVendor)).
70		Msg("applied rendering environment")
71
72	return nil
73}
74
75func (m *Manager) applyGStreamerEnv(settings port.RenderingEnvSettings) {
76	// VSync
77	if settings.ForceVSync {
78		m.setEnv("__GL_SYNC_TO_VBLANK", "1")
79		m.setEnv("vblank_mode", "3")
80	}
81
82	// GL API
83	switch strings.ToLower(settings.GLRenderingMode) {
84	case "gles2":
85		m.setEnv("GST_GL_API", "gles2")
86	case "gl3":
87		m.setEnv("GST_GL_API", "opengl3")
88	case "none":
89		m.setEnv("GST_GL_API", "none")
90	case "auto", "":
91		// Don't set - let GStreamer decide
92	}
93
94	// GStreamer debug level
95	if settings.GStreamerDebugLevel > 0 {
96		level := settings.GStreamerDebugLevel
97		if level > 5 {
98			level = 5
99		}
100		m.setEnv("GST_DEBUG", fmt.Sprintf("%d", level))
101	}
102
103	// NOTE: GST_BUFFER_SIZE, GST_QUEUE2_MAX_SIZE_BYTES, GST_QUEUE2_MAX_SIZE_TIME
104	// are NOT valid GStreamer environment variables. They are element properties
105	// that must be set programmatically on individual GStreamer elements.
106	// WebKitGTK manages its own GStreamer pipeline internally.
107}
108
109func (m *Manager) applyGPUSpecificEnv() {
110	if os.Getenv("LIBVA_DRIVER_NAME") != "" {
111		return
112	}
113
114	switch m.gpuVendor {
115	case port.GPUVendorAMD:
116		m.setEnv("LIBVA_DRIVER_NAME", "radeonsi")
117	case port.GPUVendorIntel:
118		m.setEnv("LIBVA_DRIVER_NAME", "iHD")
119	case port.GPUVendorNVIDIA:
120		m.setEnv("LIBVA_DRIVER_NAME", "nvidia")
121		if os.Getenv("GST_GL_PLATFORM") == "" {
122			m.setEnv("GST_GL_PLATFORM", "egl")
123		}
124	}
125}
126
127func (m *Manager) applyWebKitEnv(settings port.RenderingEnvSettings) {
128	if settings.DisableDMABufRenderer {
129		m.setEnv("WEBKIT_DISABLE_DMABUF_RENDERER", "1")
130	}
131	if settings.ForceCompositingMode {
132		m.setEnv("WEBKIT_FORCE_COMPOSITING_MODE", "1")
133	}
134	if settings.DisableCompositingMode {
135		m.setEnv("WEBKIT_DISABLE_COMPOSITING_MODE", "1")
136	}
137}
138
139func (m *Manager) applyGTKEnv(settings port.RenderingEnvSettings) {
140	renderer := strings.ToLower(strings.TrimSpace(settings.GSKRenderer))
141	if renderer != "" && renderer != "auto" {
142		m.setEnv("GSK_RENDERER", renderer)
143	}
144
145	var gskDisable []string
146	if settings.DisableMipmaps {
147		gskDisable = append(gskDisable, "mipmap")
148	}
149	if len(gskDisable) > 0 {
150		m.setEnv("GSK_GPU_DISABLE", strings.Join(gskDisable, ","))
151	}
152
153	var gdkDebug []string
154	if settings.PreferGL {
155		gdkDebug = append(gdkDebug, "gl-prefer-gl")
156	}
157	if settings.DebugFrames {
158		gdkDebug = append(gdkDebug, "frames")
159	}
160	if len(gdkDebug) > 0 {
161		m.setEnv("GDK_DEBUG", strings.Join(gdkDebug, ","))
162	}
163}
164
165func (m *Manager) applyDebugEnv(settings port.RenderingEnvSettings) {
166	if settings.ShowFPS {
167		m.setEnv("WEBKIT_SHOW_FPS", "1")
168	}
169	if settings.SampleMemory {
170		m.setEnv("WEBKIT_SAMPLE_MEMORY", "1")
171	}
172}
173
174func (m *Manager) applySkiaEnv(settings port.RenderingEnvSettings) {
175	// CPU painting threads: only set if > 0 and env var not already set
176	if settings.SkiaCPUPaintingThreads > 0 && os.Getenv("WEBKIT_SKIA_CPU_PAINTING_THREADS") == "" {
177		m.setEnv("WEBKIT_SKIA_CPU_PAINTING_THREADS", fmt.Sprintf("%d", settings.SkiaCPUPaintingThreads))
178	}
179
180	// GPU painting threads: only set if >= 0 (0 is valid: disables GPU tile painting)
181	if settings.SkiaGPUPaintingThreads >= 0 && os.Getenv("WEBKIT_SKIA_GPU_PAINTING_THREADS") == "" {
182		m.setEnv("WEBKIT_SKIA_GPU_PAINTING_THREADS", fmt.Sprintf("%d", settings.SkiaGPUPaintingThreads))
183	}
184
185	// CPU rendering: only set if true and env var not already set
186	if settings.SkiaEnableCPURendering && os.Getenv("WEBKIT_SKIA_ENABLE_CPU_RENDERING") == "" {
187		m.setEnv("WEBKIT_SKIA_ENABLE_CPU_RENDERING", "1")
188	}
189}
190
191func (m *Manager) setEnv(key, value string) {
192	_ = os.Setenv(key, value)
193	m.appliedVars[key] = value
194}
195
196// GetAppliedVars returns a copy of applied environment variables.
197func (m *Manager) GetAppliedVars() map[string]string {
198	result := make(map[string]string, len(m.appliedVars))
199	for k, v := range m.appliedVars {
200		result[k] = v
201	}
202	return result
203}
204
205func (*Manager) detectFromDRM() port.GPUVendor {
206	const drmBase = "/sys/class/drm"
207	for _, card := range []string{"card0", "card1"} {
208		vendorPath := filepath.Join(drmBase, card, "device", "vendor")
209		data, err := os.ReadFile(vendorPath)
210		if err != nil {
211			continue
212		}
213
214		vendorID := strings.TrimSpace(string(data))
215		switch vendorID {
216		case "0x1002":
217			return port.GPUVendorAMD
218		case "0x8086":
219			return port.GPUVendorIntel
220		case "0x10de":
221			return port.GPUVendorNVIDIA
222		}
223	}
224	return port.GPUVendorUnknown
225}
226
227var _ port.RenderingEnvManager = (*Manager)(nil)

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