v0.23.2 bfd5eb4

manager.go RawHistoryFolder

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