detached af7d4c5

lint: support golangci-lint v2.12

commit
af7d4c50c73b1b6d1e223007de11e16f4a04afc3
author
brice · · 2026-05-01 15:13 UTC
parent
44ab0c7
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
12 files · +164 -83
M .golangci.yml +3 -1
@@ -40,7 +40,9 @@ linters:
4040 statements: 50
4141 goconst:
4242 min-len: 2
43- min-occurrences: 3
43+ min-occurrences: 20
44+ ignore-string-values:
45+ - "^(bool|int|string|error|engine\\.webkit|>=0)$"
4446 gocritic:
4547 enabled-tags:
4648 - diagnostic
@@ -40,7 +40,9 @@ linters:
40 statements: 5040 statements: 50
41 goconst:41 goconst:
42 min-len: 242 min-len: 2
43- min-occurrences: 343+ min-occurrences: 20
44+ ignore-string-values:
45+ - "^(bool|int|string|error|engine\\.webkit|>=0)$"
44 gocritic:46 gocritic:
45 enabled-tags:47 enabled-tags:
46 - diagnostic48 - diagnostic
M cmd/dumber/main.go +3 -1
@@ -327,11 +327,13 @@ func applyCEFRenderStackDefault(ctx context.Context, cfg *config.Config) {
327327 if cfg == nil || cfg.Engine.ResolveEngineType() != config.EngineTypeCEF {
328328 return
329329 }
330- infracef.ApplyDefaultRenderStackEnvironment(ctx)
330+ logger := logging.NewPortLogger(ctx)
331+ infracef.ApplyDefaultRenderStackEnvironment(logger)
331332 infracef.ApplyDefaultHardwareDecodeEnvironment(ctx, infracef.HardwareDecodeEnvironmentOptions{
332333 EngineType: cfg.Engine.ResolveEngineType(),
333334 HardwareDecodingDisabled: cfg.Media.HardwareDecodingMode == config.HardwareDecodingDisable,
334335 RenderingEnvManager: renderenv.NewManager(),
336+ Logger: logger,
335337 })
336338 }
337339
@@ -327,11 +327,13 @@ func applyCEFRenderStackDefault(ctx context.Context, cfg *config.Config) {
327 if cfg == nil || cfg.Engine.ResolveEngineType() != config.EngineTypeCEF {327 if cfg == nil || cfg.Engine.ResolveEngineType() != config.EngineTypeCEF {
328 return328 return
329 }329 }
330- infracef.ApplyDefaultRenderStackEnvironment(ctx)330+ logger := logging.NewPortLogger(ctx)
331+ infracef.ApplyDefaultRenderStackEnvironment(logger)
331 infracef.ApplyDefaultHardwareDecodeEnvironment(ctx, infracef.HardwareDecodeEnvironmentOptions{332 infracef.ApplyDefaultHardwareDecodeEnvironment(ctx, infracef.HardwareDecodeEnvironmentOptions{
332 EngineType: cfg.Engine.ResolveEngineType(),333 EngineType: cfg.Engine.ResolveEngineType(),
333 HardwareDecodingDisabled: cfg.Media.HardwareDecodingMode == config.HardwareDecodingDisable,334 HardwareDecodingDisabled: cfg.Media.HardwareDecodingMode == config.HardwareDecodingDisable,
334 RenderingEnvManager: renderenv.NewManager(),335 RenderingEnvManager: renderenv.NewManager(),
336+ Logger: logger,
335 })337 })
336 }338 }
337 339
M internal/application/port/logger.go +19 -0
@@ -9,3 +9,22 @@ import (
99 // LoggerFromContext resolves the current request/session logger from context.
1010 // Use cases depend on this boundary instead of importing infrastructure logging.
1111 type LoggerFromContext func(ctx context.Context) *zerolog.Logger
12+
13+// LogField is a structured logging field carried across application boundaries.
14+type LogField struct {
15+ Key string
16+ Value any
17+}
18+
19+// Field creates a structured log field.
20+func Field(key string, value any) LogField {
21+ return LogField{Key: key, Value: value}
22+}
23+
24+// Logger is the logging boundary used by adapters that must not import a
25+// concrete logging implementation directly.
26+type Logger interface {
27+ Debug(msg string, fields ...LogField)
28+ Info(msg string, fields ...LogField)
29+ Warn(msg string, fields ...LogField)
30+}
@@ -9,3 +9,22 @@ import (
9 // LoggerFromContext resolves the current request/session logger from context.9 // LoggerFromContext resolves the current request/session logger from context.
10 // Use cases depend on this boundary instead of importing infrastructure logging.10 // Use cases depend on this boundary instead of importing infrastructure logging.
11 type LoggerFromContext func(ctx context.Context) *zerolog.Logger11 type LoggerFromContext func(ctx context.Context) *zerolog.Logger
12+
13+// LogField is a structured logging field carried across application boundaries.
14+type LogField struct {
15+ Key string
16+ Value any
17+}
18+
19+// Field creates a structured log field.
20+func Field(key string, value any) LogField {
21+ return LogField{Key: key, Value: value}
22+}
23+
24+// Logger is the logging boundary used by adapters that must not import a
25+// concrete logging implementation directly.
26+type Logger interface {
27+ Debug(msg string, fields ...LogField)
28+ Info(msg string, fields ...LogField)
29+ Warn(msg string, fields ...LogField)
30+}
M internal/infrastructure/cef/engine.go +1 -1
@@ -436,7 +436,7 @@ func (e *Engine) recordChildProcessLaunch(processType, useAngle, ozonePlatform,
436436 Str("use_angle", useAngle).
437437 Str("ozone_platform", ozonePlatform).
438438 Str("render_node_override", renderNodeOverride).
439- Str("command_line", commandLine).
439+ Strs("command_line_flags", safeChromiumCmdlineFlags(commandLine)).
440440 Uint64("renderer_launches", e.childLaunchRenderer.Load()).
441441 Uint64("gpu_launches", e.childLaunchGPU.Load()).
442442 Uint64("other_launches", e.childLaunchOther.Load()).
@@ -436,7 +436,7 @@ func (e *Engine) recordChildProcessLaunch(processType, useAngle, ozonePlatform,
436 Str("use_angle", useAngle).436 Str("use_angle", useAngle).
437 Str("ozone_platform", ozonePlatform).437 Str("ozone_platform", ozonePlatform).
438 Str("render_node_override", renderNodeOverride).438 Str("render_node_override", renderNodeOverride).
439- Str("command_line", commandLine).439+ Strs("command_line_flags", safeChromiumCmdlineFlags(commandLine)).
440 Uint64("renderer_launches", e.childLaunchRenderer.Load()).440 Uint64("renderer_launches", e.childLaunchRenderer.Load()).
441 Uint64("gpu_launches", e.childLaunchGPU.Load()).441 Uint64("gpu_launches", e.childLaunchGPU.Load()).
442 Uint64("other_launches", e.childLaunchOther.Load()).442 Uint64("other_launches", e.childLaunchOther.Load()).
M internal/infrastructure/cef/process_diagnostics.go +11 -7
@@ -206,6 +206,7 @@ func procThreadCount(pid int) int {
206206 }
207207
208208 type procThreadSummary struct {
209+ tid int
209210 text string
210211 ticks uint64
211212 }
@@ -234,18 +235,21 @@ func procThreadSummariesWithDetails(pid, limit int, detailed bool) []string {
234235 continue
235236 }
236237 text, ticks := parseThreadStat(tid, string(b))
237- if detailed {
238- text = text + " wchan=" + procThreadWchan(pid, tid)
239- if stack := procThreadKernelStack(pid, tid); stack != "" {
240- text = text + " kernel_stack=" + stack
241- }
242- }
243- items = append(items, procThreadSummary{text: text, ticks: ticks})
238+ items = append(items, procThreadSummary{tid: tid, text: text, ticks: ticks})
244239 }
245240 sort.Slice(items, func(i, j int) bool { return items[i].ticks > items[j].ticks })
246241 if limit > 0 && len(items) > limit {
247242 items = items[:limit]
248243 }
244+ if detailed {
245+ for i := range items {
246+ text := items[i].text + " wchan=" + procThreadWchan(pid, items[i].tid)
247+ if stack := procThreadKernelStack(pid, items[i].tid); stack != "" {
248+ text = text + " kernel_stack=" + stack
249+ }
250+ items[i].text = text
251+ }
252+ }
249253 out := make([]string, 0, len(items))
250254 for _, item := range items {
251255 out = append(out, item.text)
@@ -206,6 +206,7 @@ func procThreadCount(pid int) int {
206 }206 }
207 207
208 type procThreadSummary struct {208 type procThreadSummary struct {
209+ tid int
209 text string210 text string
210 ticks uint64211 ticks uint64
211 }212 }
@@ -234,18 +235,21 @@ func procThreadSummariesWithDetails(pid, limit int, detailed bool) []string {
234 continue235 continue
235 }236 }
236 text, ticks := parseThreadStat(tid, string(b))237 text, ticks := parseThreadStat(tid, string(b))
237- if detailed {238+ items = append(items, procThreadSummary{tid: tid, text: text, ticks: ticks})
238- text = text + " wchan=" + procThreadWchan(pid, tid)
239- if stack := procThreadKernelStack(pid, tid); stack != "" {
240- text = text + " kernel_stack=" + stack
241- }
242- }
243- items = append(items, procThreadSummary{text: text, ticks: ticks})
244 }239 }
245 sort.Slice(items, func(i, j int) bool { return items[i].ticks > items[j].ticks })240 sort.Slice(items, func(i, j int) bool { return items[i].ticks > items[j].ticks })
246 if limit > 0 && len(items) > limit {241 if limit > 0 && len(items) > limit {
247 items = items[:limit]242 items = items[:limit]
248 }243 }
244+ if detailed {
245+ for i := range items {
246+ text := items[i].text + " wchan=" + procThreadWchan(pid, items[i].tid)
247+ if stack := procThreadKernelStack(pid, items[i].tid); stack != "" {
248+ text = text + " kernel_stack=" + stack
249+ }
250+ items[i].text = text
251+ }
252+ }
249 out := make([]string, 0, len(items))253 out := make([]string, 0, len(items))
250 for _, item := range items {254 for _, item := range items {
251 out = append(out, item.text)255 out = append(out, item.text)
M internal/infrastructure/cef/process_diagnostics_test.go +2 -0
@@ -18,6 +18,8 @@ func TestSafeChromiumCmdlineFlagsOmitsPathBearingAndUnknownFlags(t *testing.T) {
1818 }
1919
2020 func TestReserveRenderStallProcessDiagnosticsAppliesCooldown(t *testing.T) {
21+ prev := renderStallProcessDiagnosticsLastUnixNS.Load()
22+ t.Cleanup(func() { renderStallProcessDiagnosticsLastUnixNS.Store(prev) })
2123 renderStallProcessDiagnosticsLastUnixNS.Store(0)
2224 now := time.Unix(100, 0)
2325
@@ -18,6 +18,8 @@ func TestSafeChromiumCmdlineFlagsOmitsPathBearingAndUnknownFlags(t *testing.T) {
18 }18 }
19 19
20 func TestReserveRenderStallProcessDiagnosticsAppliesCooldown(t *testing.T) {20 func TestReserveRenderStallProcessDiagnosticsAppliesCooldown(t *testing.T) {
21+ prev := renderStallProcessDiagnosticsLastUnixNS.Load()
22+ t.Cleanup(func() { renderStallProcessDiagnosticsLastUnixNS.Store(prev) })
21 renderStallProcessDiagnosticsLastUnixNS.Store(0)23 renderStallProcessDiagnosticsLastUnixNS.Store(0)
22 now := time.Unix(100, 0)24 now := time.Unix(100, 0)
23 25
M internal/infrastructure/cef/render_stack_env.go +62 -63
@@ -6,7 +6,6 @@ import (
66 "strings"
77
88 "github.com/bnema/dumber/internal/application/port"
9- "github.com/bnema/dumber/internal/logging"
109 )
1110
1211 const (
@@ -39,38 +38,35 @@ const (
3938 // Low-level env vars are treated as library-development escape hatches only.
4039 // Dumber keeps the selected stack coherent by default and overwrites conflicts;
4140 // set DUMBER_RENDER_STACK_ALLOW_SPLIT=1 to preserve explicit split-stack values.
42-func ApplyDefaultRenderStackEnvironment(ctx context.Context) string {
43- return applyDefaultRenderStackEnvironment(ctx)
41+func ApplyDefaultRenderStackEnvironment(logger port.Logger) string {
42+ return applyDefaultRenderStackEnvironment(logger)
4443 }
4544
46-func applyDefaultRenderStackEnvironment(ctx context.Context) string {
47- stack := normalizeRenderStack(ctx, os.Getenv(dumberRenderStackEnvVar))
45+func applyDefaultRenderStackEnvironment(logger port.Logger) string {
46+ stack := normalizeRenderStack(logger, os.Getenv(dumberRenderStackEnvVar))
4847 switch stack {
4948 case renderStackLegacyGL:
50- setRenderStackEnv(ctx, gskRendererEnvVar, gskRendererOpenGL)
51- setRenderStackEnv(ctx, cef2gtkBackendEnvVar, cef2gtkBackendGLArea)
52- setRenderStackEnv(ctx, cef2gtkAngleBackendVar, cef2gtkAngleGLEGL)
49+ setRenderStackEnv(logger, gskRendererEnvVar, gskRendererOpenGL)
50+ setRenderStackEnv(logger, cef2gtkBackendEnvVar, cef2gtkBackendGLArea)
51+ setRenderStackEnv(logger, cef2gtkAngleBackendVar, cef2gtkAngleGLEGL)
5352 default:
5453 stack = renderStackVulkanDMABUF
55- setRenderStackEnv(ctx, gskRendererEnvVar, gskRendererVulkan)
56- setRenderStackEnv(ctx, cef2gtkBackendEnvVar, cef2gtkBackendGDKDMABUF)
57- setRenderStackEnv(ctx, cef2gtkAngleBackendVar, cef2gtkAngleVulkan)
54+ setRenderStackEnv(logger, gskRendererEnvVar, gskRendererVulkan)
55+ setRenderStackEnv(logger, cef2gtkBackendEnvVar, cef2gtkBackendGDKDMABUF)
56+ setRenderStackEnv(logger, cef2gtkAngleBackendVar, cef2gtkAngleVulkan)
5857 }
59- logRenderStackEnvironment(ctx, stack)
58+ logRenderStackEnvironment(logger, stack)
6059 return stack
6160 }
6261
63-func logRenderStackEnvironment(ctx context.Context, stack string) {
64- if ctx == nil {
65- ctx = context.Background()
66- }
67- logging.FromContext(ctx).Info().
68- Str("render_stack", stack).
69- Str("gsk_renderer", os.Getenv(gskRendererEnvVar)).
70- Str("cef2gtk_backend", os.Getenv(cef2gtkBackendEnvVar)).
71- Str("cef2gtk_angle_backend", os.Getenv(cef2gtkAngleBackendVar)).
72- Bool("split_stack_allowed", envBoolEnabled(dumberRenderStackAllowSplitEnvVar)).
73- Msg("cef: render stack environment configured")
62+func logRenderStackEnvironment(logger port.Logger, stack string) {
63+ logInfo(logger, "cef: render stack environment configured",
64+ port.Field("render_stack", stack),
65+ port.Field("gsk_renderer", os.Getenv(gskRendererEnvVar)),
66+ port.Field("cef2gtk_backend", os.Getenv(cef2gtkBackendEnvVar)),
67+ port.Field("cef2gtk_angle_backend", os.Getenv(cef2gtkAngleBackendVar)),
68+ port.Field("split_stack_allowed", envBoolEnabled(dumberRenderStackAllowSplitEnvVar)),
69+ )
7470 }
7571
7672 // HardwareDecodeEnvironmentOptions carries config-derived CEF environment
@@ -80,6 +76,7 @@ type HardwareDecodeEnvironmentOptions struct {
8076 EngineType string
8177 HardwareDecodingDisabled bool
8278 RenderingEnvManager port.RenderingEnvManager
79+ Logger port.Logger
8380 }
8481
8582 // ApplyDefaultHardwareDecodeEnvironment maps Dumber's media config to CEF's
@@ -90,32 +87,28 @@ func ApplyDefaultHardwareDecodeEnvironment(ctx context.Context, opts HardwareDec
9087 return
9188 }
9289 if opts.HardwareDecodingDisabled {
93- setEnvDefault(ctx, cefEnableVAAPIEnvVar, "0")
90+ setEnvDefault(opts.Logger, cefEnableVAAPIEnvVar, "0")
9491 return
9592 }
96- setEnvDefault(ctx, cefEnableVAAPIEnvVar, "1")
97- applyDefaultLIBVADriverEnvironment(ctx, opts.RenderingEnvManager)
93+ setEnvDefault(opts.Logger, cefEnableVAAPIEnvVar, "1")
94+ applyDefaultLIBVADriverEnvironment(ctx, opts.RenderingEnvManager, opts.Logger)
9895 }
9996
100-func applyDefaultLIBVADriverEnvironment(ctx context.Context, manager port.RenderingEnvManager) {
97+func applyDefaultLIBVADriverEnvironment(ctx context.Context, manager port.RenderingEnvManager, logger port.Logger) {
10198 if strings.TrimSpace(os.Getenv("LIBVA_DRIVER_NAME")) != "" || manager == nil {
10299 return
103100 }
104101 switch manager.DetectGPUVendor(ctx) {
105102 case port.GPUVendorAMD:
106- setEnvDefault(ctx, "LIBVA_DRIVER_NAME", "radeonsi")
103+ setEnvDefault(logger, "LIBVA_DRIVER_NAME", "radeonsi")
107104 case port.GPUVendorIntel:
108- setEnvDefault(ctx, "LIBVA_DRIVER_NAME", "iHD")
105+ setEnvDefault(logger, "LIBVA_DRIVER_NAME", "iHD")
109106 case port.GPUVendorNVIDIA:
110- if ctx == nil {
111- ctx = context.Background()
112- }
113- logging.FromContext(ctx).Warn().
114- Msg("cef: not defaulting LIBVA_DRIVER_NAME for NVIDIA; Chromium/CEF VAAPI support is driver-dependent")
107+ logWarn(logger, "cef: not defaulting LIBVA_DRIVER_NAME for NVIDIA; Chromium/CEF VAAPI support is driver-dependent")
115108 }
116109 }
117110
118-func normalizeRenderStack(ctx context.Context, value string) string {
111+func normalizeRenderStack(logger port.Logger, value string) string {
119112 normalized := strings.ToLower(strings.TrimSpace(value))
120113 switch normalized {
121114 case "", renderStackAuto, renderStackVulkanDMABUF:
@@ -123,49 +116,55 @@ func normalizeRenderStack(ctx context.Context, value string) string {
123116 case renderStackLegacyGL:
124117 return renderStackLegacyGL
125118 default:
126- if ctx == nil {
127- ctx = context.Background()
128- }
129- logging.FromContext(ctx).Warn().
130- Str("render_stack", value).
131- Str("fallback", renderStackVulkanDMABUF).
132- Msg("cef: unknown render stack, falling back to default")
119+ logWarn(logger, "cef: unknown render stack, falling back to default",
120+ port.Field("render_stack", value),
121+ port.Field("fallback", renderStackVulkanDMABUF),
122+ )
133123 return renderStackVulkanDMABUF
134124 }
135125 }
136126
137-func setRenderStackEnv(ctx context.Context, key, value string) {
127+func setRenderStackEnv(logger port.Logger, key, value string) {
138128 if existing := strings.TrimSpace(os.Getenv(key)); existing != "" {
139- if ctx == nil {
140- ctx = context.Background()
129+ fields := []port.LogField{
130+ port.Field("key", key),
131+ port.Field("value", existing),
132+ port.Field("stack_value", value),
141133 }
142134 if envBoolEnabled(dumberRenderStackAllowSplitEnvVar) {
143- logging.FromContext(ctx).Warn().
144- Str("key", key).
145- Str("value", existing).
146- Str("stack_value", value).
147- Msg("cef: preserving explicit split render stack environment override")
135+ logWarn(logger, "cef: preserving explicit split render stack environment override", fields...)
148136 return
149137 }
150- logging.FromContext(ctx).Warn().
151- Str("key", key).
152- Str("value", existing).
153- Str("stack_value", value).
154- Msg("cef: overriding low-level render stack environment to keep Dumber stack coherent")
138+ logWarn(logger, "cef: overriding low-level render stack environment to keep Dumber stack coherent", fields...)
155139 }
156140 _ = os.Setenv(key, value)
157141 }
158142
159-func setEnvDefault(ctx context.Context, key, value string) {
143+func setEnvDefault(logger port.Logger, key, value string) {
160144 if existing := strings.TrimSpace(os.Getenv(key)); existing != "" {
161- if ctx == nil {
162- ctx = context.Background()
163- }
164- logging.FromContext(ctx).Debug().
165- Str("key", key).
166- Str("value", existing).
167- Msg("cef: preserving explicit environment override")
145+ logDebug(logger, "cef: preserving explicit environment override",
146+ port.Field("key", key),
147+ port.Field("value", existing),
148+ )
168149 return
169150 }
170151 _ = os.Setenv(key, value)
171152 }
153+
154+func logDebug(logger port.Logger, msg string, fields ...port.LogField) {
155+ if logger != nil {
156+ logger.Debug(msg, fields...)
157+ }
158+}
159+
160+func logInfo(logger port.Logger, msg string, fields ...port.LogField) {
161+ if logger != nil {
162+ logger.Info(msg, fields...)
163+ }
164+}
165+
166+func logWarn(logger port.Logger, msg string, fields ...port.LogField) {
167+ if logger != nil {
168+ logger.Warn(msg, fields...)
169+ }
170+}
@@ -6,7 +6,6 @@ import (
6 "strings"6 "strings"
7 7
8 "github.com/bnema/dumber/internal/application/port"8 "github.com/bnema/dumber/internal/application/port"
9- "github.com/bnema/dumber/internal/logging"
10 )9 )
11 10
12 const (11 const (
@@ -39,38 +38,35 @@ const (
39 // Low-level env vars are treated as library-development escape hatches only.38 // Low-level env vars are treated as library-development escape hatches only.
40 // Dumber keeps the selected stack coherent by default and overwrites conflicts;39 // Dumber keeps the selected stack coherent by default and overwrites conflicts;
41 // set DUMBER_RENDER_STACK_ALLOW_SPLIT=1 to preserve explicit split-stack values.40 // set DUMBER_RENDER_STACK_ALLOW_SPLIT=1 to preserve explicit split-stack values.
42-func ApplyDefaultRenderStackEnvironment(ctx context.Context) string {41+func ApplyDefaultRenderStackEnvironment(logger port.Logger) string {
43- return applyDefaultRenderStackEnvironment(ctx)42+ return applyDefaultRenderStackEnvironment(logger)
44 }43 }
45 44
46-func applyDefaultRenderStackEnvironment(ctx context.Context) string {45+func applyDefaultRenderStackEnvironment(logger port.Logger) string {
47- stack := normalizeRenderStack(ctx, os.Getenv(dumberRenderStackEnvVar))46+ stack := normalizeRenderStack(logger, os.Getenv(dumberRenderStackEnvVar))
48 switch stack {47 switch stack {
49 case renderStackLegacyGL:48 case renderStackLegacyGL:
50- setRenderStackEnv(ctx, gskRendererEnvVar, gskRendererOpenGL)49+ setRenderStackEnv(logger, gskRendererEnvVar, gskRendererOpenGL)
51- setRenderStackEnv(ctx, cef2gtkBackendEnvVar, cef2gtkBackendGLArea)50+ setRenderStackEnv(logger, cef2gtkBackendEnvVar, cef2gtkBackendGLArea)
52- setRenderStackEnv(ctx, cef2gtkAngleBackendVar, cef2gtkAngleGLEGL)51+ setRenderStackEnv(logger, cef2gtkAngleBackendVar, cef2gtkAngleGLEGL)
53 default:52 default:
54 stack = renderStackVulkanDMABUF53 stack = renderStackVulkanDMABUF
55- setRenderStackEnv(ctx, gskRendererEnvVar, gskRendererVulkan)54+ setRenderStackEnv(logger, gskRendererEnvVar, gskRendererVulkan)
56- setRenderStackEnv(ctx, cef2gtkBackendEnvVar, cef2gtkBackendGDKDMABUF)55+ setRenderStackEnv(logger, cef2gtkBackendEnvVar, cef2gtkBackendGDKDMABUF)
57- setRenderStackEnv(ctx, cef2gtkAngleBackendVar, cef2gtkAngleVulkan)56+ setRenderStackEnv(logger, cef2gtkAngleBackendVar, cef2gtkAngleVulkan)
58 }57 }
59- logRenderStackEnvironment(ctx, stack)58+ logRenderStackEnvironment(logger, stack)
60 return stack59 return stack
61 }60 }
62 61
63-func logRenderStackEnvironment(ctx context.Context, stack string) {62+func logRenderStackEnvironment(logger port.Logger, stack string) {
64- if ctx == nil {63+ logInfo(logger, "cef: render stack environment configured",
65- ctx = context.Background()64+ port.Field("render_stack", stack),
66- }65+ port.Field("gsk_renderer", os.Getenv(gskRendererEnvVar)),
67- logging.FromContext(ctx).Info().66+ port.Field("cef2gtk_backend", os.Getenv(cef2gtkBackendEnvVar)),
68- Str("render_stack", stack).67+ port.Field("cef2gtk_angle_backend", os.Getenv(cef2gtkAngleBackendVar)),
69- Str("gsk_renderer", os.Getenv(gskRendererEnvVar)).68+ port.Field("split_stack_allowed", envBoolEnabled(dumberRenderStackAllowSplitEnvVar)),
70- Str("cef2gtk_backend", os.Getenv(cef2gtkBackendEnvVar)).69+ )
71- Str("cef2gtk_angle_backend", os.Getenv(cef2gtkAngleBackendVar)).
72- Bool("split_stack_allowed", envBoolEnabled(dumberRenderStackAllowSplitEnvVar)).
73- Msg("cef: render stack environment configured")
74 }70 }
75 71
76 // HardwareDecodeEnvironmentOptions carries config-derived CEF environment72 // HardwareDecodeEnvironmentOptions carries config-derived CEF environment
@@ -80,6 +76,7 @@ type HardwareDecodeEnvironmentOptions struct {
80 EngineType string76 EngineType string
81 HardwareDecodingDisabled bool77 HardwareDecodingDisabled bool
82 RenderingEnvManager port.RenderingEnvManager78 RenderingEnvManager port.RenderingEnvManager
79+ Logger port.Logger
83 }80 }
84 81
85 // ApplyDefaultHardwareDecodeEnvironment maps Dumber's media config to CEF's82 // ApplyDefaultHardwareDecodeEnvironment maps Dumber's media config to CEF's
@@ -90,32 +87,28 @@ func ApplyDefaultHardwareDecodeEnvironment(ctx context.Context, opts HardwareDec
90 return87 return
91 }88 }
92 if opts.HardwareDecodingDisabled {89 if opts.HardwareDecodingDisabled {
93- setEnvDefault(ctx, cefEnableVAAPIEnvVar, "0")90+ setEnvDefault(opts.Logger, cefEnableVAAPIEnvVar, "0")
94 return91 return
95 }92 }
96- setEnvDefault(ctx, cefEnableVAAPIEnvVar, "1")93+ setEnvDefault(opts.Logger, cefEnableVAAPIEnvVar, "1")
97- applyDefaultLIBVADriverEnvironment(ctx, opts.RenderingEnvManager)94+ applyDefaultLIBVADriverEnvironment(ctx, opts.RenderingEnvManager, opts.Logger)
98 }95 }
99 96
100-func applyDefaultLIBVADriverEnvironment(ctx context.Context, manager port.RenderingEnvManager) {97+func applyDefaultLIBVADriverEnvironment(ctx context.Context, manager port.RenderingEnvManager, logger port.Logger) {
101 if strings.TrimSpace(os.Getenv("LIBVA_DRIVER_NAME")) != "" || manager == nil {98 if strings.TrimSpace(os.Getenv("LIBVA_DRIVER_NAME")) != "" || manager == nil {
102 return99 return
103 }100 }
104 switch manager.DetectGPUVendor(ctx) {101 switch manager.DetectGPUVendor(ctx) {
105 case port.GPUVendorAMD:102 case port.GPUVendorAMD:
106- setEnvDefault(ctx, "LIBVA_DRIVER_NAME", "radeonsi")103+ setEnvDefault(logger, "LIBVA_DRIVER_NAME", "radeonsi")
107 case port.GPUVendorIntel:104 case port.GPUVendorIntel:
108- setEnvDefault(ctx, "LIBVA_DRIVER_NAME", "iHD")105+ setEnvDefault(logger, "LIBVA_DRIVER_NAME", "iHD")
109 case port.GPUVendorNVIDIA:106 case port.GPUVendorNVIDIA:
110- if ctx == nil {107+ logWarn(logger, "cef: not defaulting LIBVA_DRIVER_NAME for NVIDIA; Chromium/CEF VAAPI support is driver-dependent")
111- ctx = context.Background()
112- }
113- logging.FromContext(ctx).Warn().
114- Msg("cef: not defaulting LIBVA_DRIVER_NAME for NVIDIA; Chromium/CEF VAAPI support is driver-dependent")
115 }108 }
116 }109 }
117 110
118-func normalizeRenderStack(ctx context.Context, value string) string {111+func normalizeRenderStack(logger port.Logger, value string) string {
119 normalized := strings.ToLower(strings.TrimSpace(value))112 normalized := strings.ToLower(strings.TrimSpace(value))
120 switch normalized {113 switch normalized {
121 case "", renderStackAuto, renderStackVulkanDMABUF:114 case "", renderStackAuto, renderStackVulkanDMABUF:
@@ -123,49 +116,55 @@ func normalizeRenderStack(ctx context.Context, value string) string {
123 case renderStackLegacyGL:116 case renderStackLegacyGL:
124 return renderStackLegacyGL117 return renderStackLegacyGL
125 default:118 default:
126- if ctx == nil {119+ logWarn(logger, "cef: unknown render stack, falling back to default",
127- ctx = context.Background()120+ port.Field("render_stack", value),
128- }121+ port.Field("fallback", renderStackVulkanDMABUF),
129- logging.FromContext(ctx).Warn().122+ )
130- Str("render_stack", value).
131- Str("fallback", renderStackVulkanDMABUF).
132- Msg("cef: unknown render stack, falling back to default")
133 return renderStackVulkanDMABUF123 return renderStackVulkanDMABUF
134 }124 }
135 }125 }
136 126
137-func setRenderStackEnv(ctx context.Context, key, value string) {127+func setRenderStackEnv(logger port.Logger, key, value string) {
138 if existing := strings.TrimSpace(os.Getenv(key)); existing != "" {128 if existing := strings.TrimSpace(os.Getenv(key)); existing != "" {
139- if ctx == nil {129+ fields := []port.LogField{
140- ctx = context.Background()130+ port.Field("key", key),
131+ port.Field("value", existing),
132+ port.Field("stack_value", value),
141 }133 }
142 if envBoolEnabled(dumberRenderStackAllowSplitEnvVar) {134 if envBoolEnabled(dumberRenderStackAllowSplitEnvVar) {
143- logging.FromContext(ctx).Warn().135+ logWarn(logger, "cef: preserving explicit split render stack environment override", fields...)
144- Str("key", key).
145- Str("value", existing).
146- Str("stack_value", value).
147- Msg("cef: preserving explicit split render stack environment override")
148 return136 return
149 }137 }
150- logging.FromContext(ctx).Warn().138+ logWarn(logger, "cef: overriding low-level render stack environment to keep Dumber stack coherent", fields...)
151- Str("key", key).
152- Str("value", existing).
153- Str("stack_value", value).
154- Msg("cef: overriding low-level render stack environment to keep Dumber stack coherent")
155 }139 }
156 _ = os.Setenv(key, value)140 _ = os.Setenv(key, value)
157 }141 }
158 142
159-func setEnvDefault(ctx context.Context, key, value string) {143+func setEnvDefault(logger port.Logger, key, value string) {
160 if existing := strings.TrimSpace(os.Getenv(key)); existing != "" {144 if existing := strings.TrimSpace(os.Getenv(key)); existing != "" {
161- if ctx == nil {145+ logDebug(logger, "cef: preserving explicit environment override",
162- ctx = context.Background()146+ port.Field("key", key),
163- }147+ port.Field("value", existing),
164- logging.FromContext(ctx).Debug().148+ )
165- Str("key", key).
166- Str("value", existing).
167- Msg("cef: preserving explicit environment override")
168 return149 return
169 }150 }
170 _ = os.Setenv(key, value)151 _ = os.Setenv(key, value)
171 }152 }
153+
154+func logDebug(logger port.Logger, msg string, fields ...port.LogField) {
155+ if logger != nil {
156+ logger.Debug(msg, fields...)
157+ }
158+}
159+
160+func logInfo(logger port.Logger, msg string, fields ...port.LogField) {
161+ if logger != nil {
162+ logger.Info(msg, fields...)
163+ }
164+}
165+
166+func logWarn(logger port.Logger, msg string, fields ...port.LogField) {
167+ if logger != nil {
168+ logger.Warn(msg, fields...)
169+ }
170+}
M internal/infrastructure/cef/render_stack_env_test.go +5 -5
@@ -14,7 +14,7 @@ func TestApplyDefaultRenderStackEnvironment_DefaultsToGDKDMABUFWithANGLEVulkan(t
1414 t.Setenv("PUREGO_CEF2GTK_BACKEND", "")
1515 t.Setenv("PUREGO_CEF2GTK_ANGLE_BACKEND", "")
1616
17- got := applyDefaultRenderStackEnvironment(context.Background())
17+ got := applyDefaultRenderStackEnvironment(nil)
1818
1919 if got != renderStackVulkanDMABUF {
2020 t.Fatalf("render stack = %q, want %q", got, renderStackVulkanDMABUF)
@@ -37,7 +37,7 @@ func TestApplyDefaultRenderStackEnvironment_OverridesConflictingLowLevelDefaults
3737 t.Setenv("PUREGO_CEF2GTK_BACKEND", "glarea")
3838 t.Setenv("PUREGO_CEF2GTK_ANGLE_BACKEND", "gl-egl")
3939
40- got := applyDefaultRenderStackEnvironment(context.Background())
40+ got := applyDefaultRenderStackEnvironment(nil)
4141
4242 if got != renderStackVulkanDMABUF {
4343 t.Fatalf("render stack = %q, want %q", got, renderStackVulkanDMABUF)
@@ -60,7 +60,7 @@ func TestApplyDefaultRenderStackEnvironment_AllowSplitPreservesExplicitLowLevelO
6060 t.Setenv("PUREGO_CEF2GTK_BACKEND", "glarea")
6161 t.Setenv("PUREGO_CEF2GTK_ANGLE_BACKEND", "gl-egl")
6262
63- got := applyDefaultRenderStackEnvironment(context.Background())
63+ got := applyDefaultRenderStackEnvironment(nil)
6464
6565 if got != renderStackVulkanDMABUF {
6666 t.Fatalf("render stack = %q, want %q", got, renderStackVulkanDMABUF)
@@ -82,7 +82,7 @@ func TestApplyDefaultRenderStackEnvironment_LegacyGLUsesGLArea(t *testing.T) {
8282 t.Setenv("PUREGO_CEF2GTK_BACKEND", "")
8383 t.Setenv("PUREGO_CEF2GTK_ANGLE_BACKEND", "")
8484
85- got := applyDefaultRenderStackEnvironment(context.Background())
85+ got := applyDefaultRenderStackEnvironment(nil)
8686
8787 if got != renderStackLegacyGL {
8888 t.Fatalf("render stack = %q, want %q", got, renderStackLegacyGL)
@@ -96,7 +96,7 @@ func TestApplyDefaultRenderStackEnvironment_LegacyGLUsesGLArea(t *testing.T) {
9696 }
9797
9898 func TestNormalizeRenderStackUnknownFallsBackToGDKDMABUF(t *testing.T) {
99- got := normalizeRenderStack(context.Background(), "vulkan-dmabuff")
99+ got := normalizeRenderStack(nil, "vulkan-dmabuff")
100100
101101 if got != renderStackVulkanDMABUF {
102102 t.Fatalf("render stack = %q, want %q", got, renderStackVulkanDMABUF)
@@ -14,7 +14,7 @@ func TestApplyDefaultRenderStackEnvironment_DefaultsToGDKDMABUFWithANGLEVulkan(t
14 t.Setenv("PUREGO_CEF2GTK_BACKEND", "")14 t.Setenv("PUREGO_CEF2GTK_BACKEND", "")
15 t.Setenv("PUREGO_CEF2GTK_ANGLE_BACKEND", "")15 t.Setenv("PUREGO_CEF2GTK_ANGLE_BACKEND", "")
16 16
17- got := applyDefaultRenderStackEnvironment(context.Background())17+ got := applyDefaultRenderStackEnvironment(nil)
18 18
19 if got != renderStackVulkanDMABUF {19 if got != renderStackVulkanDMABUF {
20 t.Fatalf("render stack = %q, want %q", got, renderStackVulkanDMABUF)20 t.Fatalf("render stack = %q, want %q", got, renderStackVulkanDMABUF)
@@ -37,7 +37,7 @@ func TestApplyDefaultRenderStackEnvironment_OverridesConflictingLowLevelDefaults
37 t.Setenv("PUREGO_CEF2GTK_BACKEND", "glarea")37 t.Setenv("PUREGO_CEF2GTK_BACKEND", "glarea")
38 t.Setenv("PUREGO_CEF2GTK_ANGLE_BACKEND", "gl-egl")38 t.Setenv("PUREGO_CEF2GTK_ANGLE_BACKEND", "gl-egl")
39 39
40- got := applyDefaultRenderStackEnvironment(context.Background())40+ got := applyDefaultRenderStackEnvironment(nil)
41 41
42 if got != renderStackVulkanDMABUF {42 if got != renderStackVulkanDMABUF {
43 t.Fatalf("render stack = %q, want %q", got, renderStackVulkanDMABUF)43 t.Fatalf("render stack = %q, want %q", got, renderStackVulkanDMABUF)
@@ -60,7 +60,7 @@ func TestApplyDefaultRenderStackEnvironment_AllowSplitPreservesExplicitLowLevelO
60 t.Setenv("PUREGO_CEF2GTK_BACKEND", "glarea")60 t.Setenv("PUREGO_CEF2GTK_BACKEND", "glarea")
61 t.Setenv("PUREGO_CEF2GTK_ANGLE_BACKEND", "gl-egl")61 t.Setenv("PUREGO_CEF2GTK_ANGLE_BACKEND", "gl-egl")
62 62
63- got := applyDefaultRenderStackEnvironment(context.Background())63+ got := applyDefaultRenderStackEnvironment(nil)
64 64
65 if got != renderStackVulkanDMABUF {65 if got != renderStackVulkanDMABUF {
66 t.Fatalf("render stack = %q, want %q", got, renderStackVulkanDMABUF)66 t.Fatalf("render stack = %q, want %q", got, renderStackVulkanDMABUF)
@@ -82,7 +82,7 @@ func TestApplyDefaultRenderStackEnvironment_LegacyGLUsesGLArea(t *testing.T) {
82 t.Setenv("PUREGO_CEF2GTK_BACKEND", "")82 t.Setenv("PUREGO_CEF2GTK_BACKEND", "")
83 t.Setenv("PUREGO_CEF2GTK_ANGLE_BACKEND", "")83 t.Setenv("PUREGO_CEF2GTK_ANGLE_BACKEND", "")
84 84
85- got := applyDefaultRenderStackEnvironment(context.Background())85+ got := applyDefaultRenderStackEnvironment(nil)
86 86
87 if got != renderStackLegacyGL {87 if got != renderStackLegacyGL {
88 t.Fatalf("render stack = %q, want %q", got, renderStackLegacyGL)88 t.Fatalf("render stack = %q, want %q", got, renderStackLegacyGL)
@@ -96,7 +96,7 @@ func TestApplyDefaultRenderStackEnvironment_LegacyGLUsesGLArea(t *testing.T) {
96 }96 }
97 97
98 func TestNormalizeRenderStackUnknownFallsBackToGDKDMABUF(t *testing.T) {98 func TestNormalizeRenderStackUnknownFallsBackToGDKDMABUF(t *testing.T) {
99- got := normalizeRenderStack(context.Background(), "vulkan-dmabuff")99+ got := normalizeRenderStack(nil, "vulkan-dmabuff")
100 100
101 if got != renderStackVulkanDMABUF {101 if got != renderStackVulkanDMABUF {
102 t.Fatalf("render stack = %q, want %q", got, renderStackVulkanDMABUF)102 t.Fatalf("render stack = %q, want %q", got, renderStackVulkanDMABUF)
M internal/infrastructure/cef/render_stall_watchdog.go +4 -4
@@ -192,10 +192,10 @@ func (wv *WebView) logRenderStall(bridge *Cef2gtkAdapter, now time.Time, diag re
192192 Bool("cef_io_alive", classification.CEFIOAlive).
193193 Uint64("webview_id", uint64(wv.id)).
194194 Int32("browser_id", diag.browser.GetIdentifier()).
195- Str("uri", logging.TruncateURL(diag.uri, logging.PermissionLogURLMaxLen)).
196- Str("title", diag.title).
195+ Bool("has_uri", diag.uri != "").
196+ Bool("has_title", diag.title != "").
197197 Bool("is_loading", diag.isLoading).
198- Str("pending_uri", logging.TruncateURL(diag.pendingURI, logging.PermissionLogURLMaxLen)).
198+ Bool("has_pending_uri", diag.pendingURI != "").
199199 Bool("audio_playing", diag.audioPlaying).
200200 Int64("cef_ui_heartbeat_ack_age_ms", durationMillis(uiHeartbeat.LastAckAge)).
201201 Int64("cef_ui_heartbeat_latency_ms", durationMillis(uiHeartbeat.Latency)).
@@ -233,7 +233,7 @@ func (wv *WebView) logRenderStall(bridge *Cef2gtkAdapter, now time.Time, diag re
233233 Int("pending_schedule_failures", diag.diag.PendingScheduleFailures).
234234 Int("pending_idle_callbacks", diag.diag.PendingIdleCallbacks).
235235 Str("latest_render_event_kind", diag.latestEvent.Kind).
236- Str("latest_render_event_message", diag.latestEvent.Message).
236+ Bool("latest_render_event_has_message", diag.latestEvent.Message != "").
237237 Msg("cef: accelerated rendering appears stalled")
238238 }
239239
@@ -192,10 +192,10 @@ func (wv *WebView) logRenderStall(bridge *Cef2gtkAdapter, now time.Time, diag re
192 Bool("cef_io_alive", classification.CEFIOAlive).192 Bool("cef_io_alive", classification.CEFIOAlive).
193 Uint64("webview_id", uint64(wv.id)).193 Uint64("webview_id", uint64(wv.id)).
194 Int32("browser_id", diag.browser.GetIdentifier()).194 Int32("browser_id", diag.browser.GetIdentifier()).
195- Str("uri", logging.TruncateURL(diag.uri, logging.PermissionLogURLMaxLen)).195+ Bool("has_uri", diag.uri != "").
196- Str("title", diag.title).196+ Bool("has_title", diag.title != "").
197 Bool("is_loading", diag.isLoading).197 Bool("is_loading", diag.isLoading).
198- Str("pending_uri", logging.TruncateURL(diag.pendingURI, logging.PermissionLogURLMaxLen)).198+ Bool("has_pending_uri", diag.pendingURI != "").
199 Bool("audio_playing", diag.audioPlaying).199 Bool("audio_playing", diag.audioPlaying).
200 Int64("cef_ui_heartbeat_ack_age_ms", durationMillis(uiHeartbeat.LastAckAge)).200 Int64("cef_ui_heartbeat_ack_age_ms", durationMillis(uiHeartbeat.LastAckAge)).
201 Int64("cef_ui_heartbeat_latency_ms", durationMillis(uiHeartbeat.Latency)).201 Int64("cef_ui_heartbeat_latency_ms", durationMillis(uiHeartbeat.Latency)).
@@ -233,7 +233,7 @@ func (wv *WebView) logRenderStall(bridge *Cef2gtkAdapter, now time.Time, diag re
233 Int("pending_schedule_failures", diag.diag.PendingScheduleFailures).233 Int("pending_schedule_failures", diag.diag.PendingScheduleFailures).
234 Int("pending_idle_callbacks", diag.diag.PendingIdleCallbacks).234 Int("pending_idle_callbacks", diag.diag.PendingIdleCallbacks).
235 Str("latest_render_event_kind", diag.latestEvent.Kind).235 Str("latest_render_event_kind", diag.latestEvent.Kind).
236- Str("latest_render_event_message", diag.latestEvent.Message).236+ Bool("latest_render_event_has_message", diag.latestEvent.Message != "").
237 Msg("cef: accelerated rendering appears stalled")237 Msg("cef: accelerated rendering appears stalled")
238 }238 }
239 239
A internal/logging/port_logger.go +41 -0
@@ -0,0 +1,41 @@
1+package logging
2+
3+import (
4+ "context"
5+
6+ "github.com/rs/zerolog"
7+
8+ "github.com/bnema/dumber/internal/application/port"
9+)
10+
11+// NewPortLogger adapts the zerolog logger stored in ctx to the application
12+// logging boundary.
13+func NewPortLogger(ctx context.Context) port.Logger {
14+ if ctx == nil {
15+ ctx = context.Background()
16+ }
17+ return portLogger{ctx: ctx}
18+}
19+
20+type portLogger struct {
21+ ctx context.Context
22+}
23+
24+func (l portLogger) Debug(msg string, fields ...port.LogField) {
25+ addLogFields(FromContext(l.ctx).Debug(), fields).Msg(msg)
26+}
27+
28+func (l portLogger) Info(msg string, fields ...port.LogField) {
29+ addLogFields(FromContext(l.ctx).Info(), fields).Msg(msg)
30+}
31+
32+func (l portLogger) Warn(msg string, fields ...port.LogField) {
33+ addLogFields(FromContext(l.ctx).Warn(), fields).Msg(msg)
34+}
35+
36+func addLogFields(event *zerolog.Event, fields []port.LogField) *zerolog.Event {
37+ for _, field := range fields {
38+ event = event.Interface(field.Key, field.Value)
39+ }
40+ return event
41+}
@@ -0,0 +1,41 @@
1+package logging
2+
3+import (
4+ "context"
5+
6+ "github.com/rs/zerolog"
7+
8+ "github.com/bnema/dumber/internal/application/port"
9+)
10+
11+// NewPortLogger adapts the zerolog logger stored in ctx to the application
12+// logging boundary.
13+func NewPortLogger(ctx context.Context) port.Logger {
14+ if ctx == nil {
15+ ctx = context.Background()
16+ }
17+ return portLogger{ctx: ctx}
18+}
19+
20+type portLogger struct {
21+ ctx context.Context
22+}
23+
24+func (l portLogger) Debug(msg string, fields ...port.LogField) {
25+ addLogFields(FromContext(l.ctx).Debug(), fields).Msg(msg)
26+}
27+
28+func (l portLogger) Info(msg string, fields ...port.LogField) {
29+ addLogFields(FromContext(l.ctx).Info(), fields).Msg(msg)
30+}
31+
32+func (l portLogger) Warn(msg string, fields ...port.LogField) {
33+ addLogFields(FromContext(l.ctx).Warn(), fields).Msg(msg)
34+}
35+
36+func addLogFields(event *zerolog.Event, fields []port.LogField) *zerolog.Event {
37+ for _, field := range fields {
38+ event = event.Interface(field.Key, field.Value)
39+ }
40+ return event
41+}
M internal/ui/input/global_shortcuts.go +3 -0
@@ -373,6 +373,9 @@ func (h *GlobalShortcutHandler) ReloadShortcuts(ctx context.Context, workspace *
373373 // but we clear our references here.
374374 func (h *GlobalShortcutHandler) Detach() {
375375 h.generation++
376+ if h.window != nil && h.controller != nil {
377+ h.window.RemoveController(&h.controller.EventController)
378+ }
376379 h.controller = nil
377380 h.callbacks = nil
378381 h.registered = nil
@@ -373,6 +373,9 @@ func (h *GlobalShortcutHandler) ReloadShortcuts(ctx context.Context, workspace *
373 // but we clear our references here.373 // but we clear our references here.
374 func (h *GlobalShortcutHandler) Detach() {374 func (h *GlobalShortcutHandler) Detach() {
375 h.generation++375 h.generation++
376+ if h.window != nil && h.controller != nil {
377+ h.window.RemoveController(&h.controller.EventController)
378+ }
376 h.controller = nil379 h.controller = nil
377 h.callbacks = nil380 h.callbacks = nil
378 h.registered = nil381 h.registered = nil
M scripts/cef-gpu-diagnostics.sh +10 -1
@@ -49,6 +49,14 @@ require_value() {
4949 fi
5050 }
5151
52+validate_devtools_port() {
53+ local value="$1"
54+ if [[ ! "$value" =~ ^[1-9][0-9]*$ ]] || (( value < 1 || value > 65535 )); then
55+ echo "--devtools-port must be an integer between 1 and 65535" >&2
56+ exit 2
57+ fi
58+}
59+
5260 while [[ $# -gt 0 ]]; do
5361 case "$1" in
5462 -p|--pid) require_value "$1" "${2:-}"; pid="$2"; shift 2 ;;
@@ -58,7 +66,7 @@ while [[ $# -gt 0 ]]; do
5866 --session-log) require_value "$1" "${2:-}"; session_log="$2"; shift 2 ;;
5967 --codec-stats) require_value "$1" "${2:-}"; codec_stats="$2"; shift 2 ;;
6068 --codec-stats-file) require_value "$1" "${2:-}"; codec_stats_file="$2"; shift 2 ;;
61- --devtools-port) require_value "$1" "${2:-}"; devtools_port="$2"; shift 2 ;;
69+ --devtools-port) require_value "$1" "${2:-}"; validate_devtools_port "$2"; devtools_port="$2"; shift 2 ;;
6270 -h|--help) usage; exit 0 ;;
6371 *) echo "unknown argument: $1" >&2; usage >&2; exit 2 ;;
6472 esac
@@ -188,6 +196,7 @@ EOF
188196 fi
189197
190198 if [[ -n "$devtools_port" ]]; then
199+ validate_devtools_port "$devtools_port"
191200 log "capturing DevTools metadata from port $devtools_port"
192201 if ! curl --connect-timeout 1 --max-time 3 -fsS "http://127.0.0.1:${devtools_port}/json/version" > "$out_dir/devtools_version.json" 2> "$out_dir/devtools.stderr"; then
193202 log "failed to fetch DevTools version metadata from port $devtools_port"
@@ -49,6 +49,14 @@ require_value() {
49 fi49 fi
50 }50 }
51 51
52+validate_devtools_port() {
53+ local value="$1"
54+ if [[ ! "$value" =~ ^[1-9][0-9]*$ ]] || (( value < 1 || value > 65535 )); then
55+ echo "--devtools-port must be an integer between 1 and 65535" >&2
56+ exit 2
57+ fi
58+}
59+
52 while [[ $# -gt 0 ]]; do60 while [[ $# -gt 0 ]]; do
53 case "$1" in61 case "$1" in
54 -p|--pid) require_value "$1" "${2:-}"; pid="$2"; shift 2 ;;62 -p|--pid) require_value "$1" "${2:-}"; pid="$2"; shift 2 ;;
@@ -58,7 +66,7 @@ while [[ $# -gt 0 ]]; do
58 --session-log) require_value "$1" "${2:-}"; session_log="$2"; shift 2 ;;66 --session-log) require_value "$1" "${2:-}"; session_log="$2"; shift 2 ;;
59 --codec-stats) require_value "$1" "${2:-}"; codec_stats="$2"; shift 2 ;;67 --codec-stats) require_value "$1" "${2:-}"; codec_stats="$2"; shift 2 ;;
60 --codec-stats-file) require_value "$1" "${2:-}"; codec_stats_file="$2"; shift 2 ;;68 --codec-stats-file) require_value "$1" "${2:-}"; codec_stats_file="$2"; shift 2 ;;
61- --devtools-port) require_value "$1" "${2:-}"; devtools_port="$2"; shift 2 ;;69+ --devtools-port) require_value "$1" "${2:-}"; validate_devtools_port "$2"; devtools_port="$2"; shift 2 ;;
62 -h|--help) usage; exit 0 ;;70 -h|--help) usage; exit 0 ;;
63 *) echo "unknown argument: $1" >&2; usage >&2; exit 2 ;;71 *) echo "unknown argument: $1" >&2; usage >&2; exit 2 ;;
64 esac72 esac
@@ -188,6 +196,7 @@ EOF
188 fi196 fi
189 197
190 if [[ -n "$devtools_port" ]]; then198 if [[ -n "$devtools_port" ]]; then
199+ validate_devtools_port "$devtools_port"
191 log "capturing DevTools metadata from port $devtools_port"200 log "capturing DevTools metadata from port $devtools_port"
192 if ! curl --connect-timeout 1 --max-time 3 -fsS "http://127.0.0.1:${devtools_port}/json/version" > "$out_dir/devtools_version.json" 2> "$out_dir/devtools.stderr"; then201 if ! curl --connect-timeout 1 --max-time 3 -fsS "http://127.0.0.1:${devtools_port}/json/version" > "$out_dir/devtools_version.json" 2> "$out_dir/devtools.stderr"; then
193 log "failed to fetch DevTools version metadata from port $devtools_port"202 log "failed to fetch DevTools version metadata from port $devtools_port"

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