v0.30.0 eb3068f

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				TouchpadNavigationEnabled:          cfg.Engine.CEF.Input.TouchpadNavigationEnabled,
103				TouchpadNavigationMinDelta:         cfg.Engine.CEF.Input.TouchpadNavigationMinDelta,
104				TouchpadNavigationMaxVerticalRatio: cfg.Engine.CEF.Input.TouchpadNavigationMaxVerticalRatio,
105			},
106			EnableAudioHandler: cfg.Engine.CEF.EnableAudioHandler,
107			TraceHandlers:      cfg.Engine.CEF.TraceHandlers,
108			ApplicationScale:   cfg.DefaultUIScale,
109		}
110		deps := cef.EngineDependencies{
111			RegisterHandlers:           handlers.RegisterAll,
112			RegisterAccentHandlers:     handlers.RegisterAccentHandlers,
113			CurrentConfigPayload:       currentConfigPayload,
114			DefaultConfigPayload:       defaultConfigPayload,
115			ContextMenuBuilder:         contextMenuBuilder,
116			ContextMenuExecutorFactory: contextMenuExecutorFactory,
117			ContextMenuRenderer:        gtkmenu.NewRenderer(nil),
118			Clipboard:                  clipboardinfra.New(),
119			ImageDataResolver:          webkit.NewContextMenuResolver(),
120		}
121		audioFactory := audiofactory.NewAudioOutputFactory()
122		return cef.NewEngine(input.Ctx, opts, cef.RuntimePaths{
123			StateRoot:     profile.CEFUserDataDir(),
124			LogFile:       profile.CEFLogFile(),
125			ProfileLogDir: profile.Shared.LogDir,
126		}, cefCfg, audioFactory, deps)
127	default:
128		return nil, fmt.Errorf("unknown engine type: %q", engineType)
129	}
130}
131
132func prefersDarkProvider(manager *theme.Manager) func() bool {
133	return func() bool {
134		if manager == nil {
135			return true
136		}
137		return manager.PrefersDark()
138	}
139}
140
141func requireRuntimeProfile(profile runtimeprofile.Profile, engineType string) (runtimeprofile.Profile, error) {
142	if profile.Engine == "" {
143		return runtimeprofile.Profile{}, fmt.Errorf("missing runtime profile for %s engine", engineType)
144	}
145	if profile.Engine != engineType {
146		return runtimeprofile.Profile{}, fmt.Errorf("runtime profile engine %q does not match %q", profile.Engine, engineType)
147	}
148	return profile, nil
149}

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