engine.go RawHistoryFolder

1package bootstrap
2
3import (
4	"context"
5	"encoding/json"
6	"fmt"
7
8	"github.com/bnema/dumber/internal/application/dto"
9	"github.com/bnema/dumber/internal/application/port"
10	"github.com/bnema/dumber/internal/application/usecase"
11	audiofactory "github.com/bnema/dumber/internal/infrastructure/audio/factory"
12	"github.com/bnema/dumber/internal/infrastructure/cef"
13	clipboardinfra "github.com/bnema/dumber/internal/infrastructure/clipboard"
14	"github.com/bnema/dumber/internal/infrastructure/config"
15	"github.com/bnema/dumber/internal/infrastructure/env"
16	"github.com/bnema/dumber/internal/infrastructure/gtkmenu"
17	"github.com/bnema/dumber/internal/infrastructure/handlers"
18	"github.com/bnema/dumber/internal/infrastructure/runtimeprofile"
19	"github.com/bnema/dumber/internal/infrastructure/webkit"
20	"github.com/bnema/dumber/internal/ui/theme"
21	"github.com/rs/zerolog"
22)
23
24// EngineInput holds the input for BuildEngine.
25type EngineInput struct {
26	Ctx                 context.Context
27	Config              *config.Config
28	RuntimeProfile      runtimeprofile.Profile
29	ThemeManager        *theme.Manager
30	ExternalThemeSource port.ExternalThemeSource
31	ColorResolver       port.ColorSchemeResolver
32	Logger              zerolog.Logger
33}
34
35// BuildEngine constructs a port.Engine for the engine type specified in cfg.Engine.Type.
36func BuildEngine(input EngineInput) (port.Engine, error) {
37	cfg := input.Config
38	systemviewReader := config.NewSystemviewConfigReader(env.NewHardwareSurveyor())
39	systemviewUC := usecase.NewReadSystemviewConfigUseCase(
40		systemviewReader,
41		usecase.WithSystemviewResolvedAppearance(
42			input.ExternalThemeSource,
43			input.ColorResolver,
44			prefersDarkProvider(input.ThemeManager),
45		),
46	)
47	buildConfigPayload := func(read func(context.Context) (dto.SystemviewConfigPayload, error)) func() ([]byte, error) {
48		return func() ([]byte, error) {
49			payload, err := read(input.Ctx)
50			if err != nil {
51				return nil, err
52			}
53			return json.Marshal(payload)
54		}
55	}
56	currentConfigPayload := buildConfigPayload(systemviewUC.Current)
57	defaultConfigPayload := buildConfigPayload(systemviewUC.Default)
58	contextMenuBuilder := usecase.NewBuildContextMenuUseCase()
59	contextMenuExecutorFactory := &usecase.ContextMenuActionExecutorFactory{}
60	engineType := cfg.Engine.ResolveEngineType()
61	switch engineType {
62	case config.EngineTypeWebKit:
63		profile, err := requireRuntimeProfile(input.RuntimeProfile, engineType)
64		if err != nil {
65			return nil, err
66		}
67		opts := port.EngineOptions{
68			CookiePolicy: port.CookiePolicy(cfg.Engine.CookiePolicy),
69		}
70		wkCfg := webkit.EngineConfigFromConfig(cfg.Engine.WebKit)
71
72		return webkit.NewEngine(
73			input.Ctx, cfg, opts, profile, wkCfg,
74			EngineSettingsPayloadFromConfig(cfg),
75			currentConfigPayload, defaultConfigPayload,
76			input.ThemeManager, input.ColorResolver,
77			contextMenuBuilder, contextMenuExecutorFactory,
78			input.Logger,
79		)
80	case config.EngineTypeCEF:
81		profile, err := requireRuntimeProfile(input.RuntimeProfile, engineType)
82		if err != nil {
83			return nil, err
84		}
85		opts := port.EngineOptions{
86			CookiePolicy: port.CookiePolicy(cfg.Engine.CookiePolicy),
87		}
88		cefCfg := cef.RuntimeConfig{
89			CEFDir:                      cfg.Engine.CEF.CEFDir,
90			LogFile:                     cfg.Engine.CEF.LogFile,
91			LogSeverity:                 cfg.Engine.CEF.LogSeverity,
92			RenderStack:                 string(cfg.Engine.CEF.CEFRenderStack()),
93			AdaptiveWindowlessFrameRate: cfg.Engine.CEF.CEFAdaptiveWindowlessFrameRate(),
94			WindowlessFrameRate:         cfg.Engine.CEF.WindowlessFrameRate,
95			WindowlessFrameRateMax:      cfg.Engine.CEF.CEFWindowlessFrameRateMax(),
96			Input: cef.RuntimeInputConfig{
97				ScrollWheelMultiplier:              cfg.Engine.CEF.Input.ScrollWheelMultiplier,
98				ScrollPreciseMultiplier:            cfg.Engine.CEF.Input.ScrollPreciseMultiplier,
99				ScrollHorizontalMultiplier:         cfg.Engine.CEF.Input.ScrollHorizontalMultiplier,
100				ScrollVerticalMultiplier:           cfg.Engine.CEF.Input.ScrollVerticalMultiplier,
101				ScrollMaxDelta:                     cfg.Engine.CEF.Input.ScrollMaxDelta,
102				ScrollTouchpadInertia:              cfg.Engine.CEF.Input.ScrollTouchpadInertia,
103				ScrollWheelSmoothing:               cfg.Engine.CEF.Input.ScrollWheelSmoothing,
104				TouchpadNavigationEnabled:          cfg.Engine.CEF.Input.TouchpadNavigationEnabled,
105				TouchpadNavigationMinDelta:         cfg.Engine.CEF.Input.TouchpadNavigationMinDelta,
106				TouchpadNavigationMaxVerticalRatio: cfg.Engine.CEF.Input.TouchpadNavigationMaxVerticalRatio,
107			},
108			EnableAudioHandler: cfg.Engine.CEF.EnableAudioHandler,
109			TraceHandlers:      cfg.Engine.CEF.TraceHandlers,
110			ApplicationScale:   cfg.DefaultUIScale,
111		}
112		deps := cef.EngineDependencies{
113			RegisterHandlers:           handlers.RegisterAll,
114			RegisterAccentHandlers:     handlers.RegisterAccentHandlers,
115			CurrentConfigPayload:       currentConfigPayload,
116			DefaultConfigPayload:       defaultConfigPayload,
117			ContextMenuBuilder:         contextMenuBuilder,
118			ContextMenuExecutorFactory: contextMenuExecutorFactory,
119			ContextMenuRenderer:        gtkmenu.NewRenderer(nil),
120			Clipboard:                  clipboardinfra.New(),
121			ImageDataResolver:          webkit.NewContextMenuResolver(),
122		}
123		audioFactory := audiofactory.NewAudioOutputFactory()
124		paths := cef.RuntimePaths{
125			StateRoot:     profile.CEFUserDataDir(),
126			LogFile:       profile.CEFLogFile(),
127			ProfileLogDir: profile.Shared.LogDir,
128		}
129		if profile.InstanceRoot != "" {
130			paths.IsolatedRoot = profile.CEFUserDataDir()
131		}
132		return cef.NewEngine(input.Ctx, opts, paths, cefCfg, audioFactory, deps)
133	default:
134		return nil, fmt.Errorf("unknown engine type: %q", engineType)
135	}
136}
137
138func prefersDarkProvider(manager *theme.Manager) func() bool {
139	return func() bool {
140		if manager == nil {
141			return true
142		}
143		return manager.PrefersDark()
144	}
145}
146
147func requireRuntimeProfile(profile runtimeprofile.Profile, engineType string) (runtimeprofile.Profile, error) {
148	if profile.Engine == "" {
149		return runtimeprofile.Profile{}, fmt.Errorf("missing runtime profile for %s engine", engineType)
150	}
151	if profile.Engine != engineType {
152		return runtimeprofile.Profile{}, fmt.Errorf("runtime profile engine %q does not match %q", profile.Engine, engineType)
153	}
154	return profile, nil
155}

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