v0.33.1 9577fee

engine.go RawHistoryFolder

1package webkit
2
3import (
4	"context"
5	"fmt"
6
7	"github.com/bnema/dumber/internal/application/port"
8	"github.com/bnema/dumber/internal/domain/entity"
9	"github.com/bnema/dumber/internal/infrastructure/filtering"
10	"github.com/bnema/dumber/internal/infrastructure/handlers"
11	"github.com/rs/zerolog"
12)
13
14// Engine implements port.Engine for the WebKit browser engine.
15type Engine struct {
16	ctx                    context.Context
17	wkCtx                  *WebKitContext
18	settings               *SettingsManager
19	injector               *ContentInjector
20	messageRouter          *MessageRouter
21	pool                   *WebViewPool
22	factory                *WebViewFactory
23	filterManager          *filtering.Manager
24	schemeHandler          *DumbSchemeHandler
25	schemePath             string
26	logger                 zerolog.Logger
27	ctxMenuBuilder         port.ContextMenuBuilder
28	ctxMenuExecutorFactory port.ContextMenuActionExecutorFactory
29	onClipboardCopied      func(textLen int)
30	downloadPath           string
31	downloadPreparer       port.DownloadPreparer
32	clipboard              port.Clipboard // captured during RegisterHandlers for context menu wiring
33}
34
35// Compile-time check that Engine implements port.Engine.
36var _ port.Engine = (*Engine)(nil)
37
38// Factory returns the WebViewFactory wrapped as a port.WebViewFactory.
39func (e *Engine) Factory() port.WebViewFactory {
40	return &webViewFactoryAdapter{factory: e.factory}
41}
42
43// Pool returns the WebViewPool wrapped as a port.WebViewPool.
44func (e *Engine) Pool() port.WebViewPool {
45	return &webViewPoolAdapter{pool: e.pool, ctx: e.ctx}
46}
47
48// ContentInjector returns the ContentInjector implementing port.ContentInjector.
49func (e *Engine) ContentInjector() port.ContentInjector {
50	return e.injector
51}
52
53// InternalSchemePath returns the URI scheme used for internal app resources.
54func (e *Engine) InternalSchemePath() string {
55	return e.schemePath
56}
57
58// Close releases all resources held by the engine.
59func (e *Engine) Close() error {
60	if e.pool != nil {
61		e.pool.Close(e.ctx)
62	}
63	return nil
64}
65
66// SettingsApplier returns a port.SettingsApplier adapter for the SettingsManager.
67func (e *Engine) SettingsApplier() port.SettingsApplier {
68	return &settingsApplierAdapter{settings: e.settings}
69}
70
71// FilterApplier returns a port.FilterApplier adapter, or nil if filtering is disabled.
72func (e *Engine) FilterApplier() port.FilterApplier {
73	if e.filterManager == nil {
74		return nil
75	}
76	return &filterApplierAdapter{manager: e.filterManager}
77}
78
79// FaviconDatabase returns a port.FaviconDatabase adapter for async favicon lookups.
80func (e *Engine) FaviconDatabase() port.FaviconDatabase {
81	return &faviconDatabaseAdapter{wkCtx: e.wkCtx}
82}
83
84// InternalFilterManager returns the FilterManager for content filter lifecycle.
85// This is on the concrete *Engine type (not the port.Engine interface) because
86// FilterManager is a webkit-specific concern used only during dependency wiring.
87func (e *Engine) InternalFilterManager() port.FilterManager { return e.filterManager }
88
89// SetHandlerContext sets the base context for message handler dispatch.
90func (e *Engine) SetHandlerContext(ctx context.Context) {
91	if e.messageRouter != nil {
92		e.messageRouter.SetBaseContext(ctx)
93	}
94}
95
96// RegisterHandlers registers all WebUI message bridge handlers.
97func (e *Engine) RegisterHandlers(ctx context.Context, deps port.HandlerDependencies) error {
98	if e.messageRouter == nil {
99		return fmt.Errorf("message router not initialized")
100	}
101	// Capture clipboard for context menu pipeline wiring in ConfigureDownloads.
102	e.clipboard = deps.Clipboard
103	e.onClipboardCopied = deps.OnClipboardCopied
104	return handlers.RegisterAll(ctx, e.messageRouter, deps)
105}
106
107// RegisterAccentHandlers registers accent/dead-key input handlers.
108func (e *Engine) RegisterAccentHandlers(ctx context.Context, handler port.AccentKeyHandler) error {
109	if e.messageRouter == nil {
110		return fmt.Errorf("message router not initialized")
111	}
112	return handlers.RegisterAccentHandlers(ctx, e.messageRouter, handler)
113}
114
115// ConfigureDownloads sets up download handling.
116func (e *Engine) ConfigureDownloads(
117	ctx context.Context, downloadPath string,
118	eventHandler port.DownloadEventHandler, preparer port.DownloadPreparer,
119) error {
120	if e.wkCtx == nil {
121		return fmt.Errorf("webkit context not initialized")
122	}
123	if preparer == nil {
124		return fmt.Errorf("download preparer is required")
125	}
126	handler := NewDownloadHandler(downloadPath, eventHandler, preparer)
127	e.wkCtx.SetDownloadHandler(ctx, handler)
128	e.downloadPath = downloadPath
129	e.downloadPreparer = preparer
130
131	// Wire the shared context menu pipeline now that all dependencies are
132	// available. RegisterHandlers (called earlier) captured the clipboard;
133	// ConfigureDownloads supplies the download preparer and path.
134	e.installContextMenuPipeline()
135
136	return nil
137}
138
139// OnToolkitReady refreshes pooled WebViews after toolkit init.
140func (e *Engine) OnToolkitReady(ctx context.Context) error {
141	if e.pool != nil {
142		e.pool.RefreshScripts(ctx)
143	}
144	return nil
145}
146
147// UpdateAppearance updates default background color for pool and factory.
148func (e *Engine) UpdateAppearance(_ context.Context, r, g, b, alpha float64) error {
149	if e.pool != nil {
150		e.pool.SetBackgroundColor(float32(r), float32(g), float32(b), float32(alpha))
151	}
152	if e.factory != nil {
153		e.factory.SetBackgroundColor(float32(r), float32(g), float32(b), float32(alpha))
154	}
155	return nil
156}
157
158// UpdateSettings applies runtime config changes to engine settings.
159func (e *Engine) UpdateSettings(ctx context.Context, update entity.EngineSettingsUpdate) error {
160	if e.settings != nil {
161		e.settings.UpdateFromPayload(ctx, update.Settings)
162	}
163	return nil
164}
165
166// installContextMenuPipeline creates the shared context menu pipeline and
167// propagates it to the factory (and pool). This is called once during startup,
168// after both RegisterHandlers (clipboard) and ConfigureDownloads (download
169// deps) have run. Kept on the concrete *Engine type because it is a
170// WebKit-specific concern.
171func (e *Engine) installContextMenuPipeline() {
172	resolver := NewContextMenuResolver()
173	saver := NewResolvedImageSaver(e.downloadPreparer, e.downloadPath)
174
175	// WebKit's context-menu signal fires on the GTK main thread, so no
176	// dispatch wrapper is needed.
177	renderer := NewRenderer(nil)
178
179	pipeline := &contextMenuPipeline{
180		builder:         e.ctxMenuBuilder,
181		executorFactory: e.ctxMenuExecutorFactory,
182		clipboard:       e.clipboard,
183		onCopied:        e.onClipboardCopied,
184		resolver:        resolver,
185		saver:           saver,
186		renderer:        renderer,
187	}
188	e.SetContextMenuPipeline(pipeline)
189}
190
191// SetContextMenuPipeline configures the context menu pipeline for all WebViews
192// created by the factory (and pool). Call this after the use cases and renderer
193// are available in the bootstrap layer.
194func (e *Engine) SetContextMenuPipeline(pipeline *contextMenuPipeline) {
195	if e.factory != nil {
196		e.factory.SetContextMenuPipeline(pipeline)
197	}
198}

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