v0.7.0 31c7fae

browser.go RawHistoryFolder

1package browser
2
3import (
4	"database/sql"
5	"embed"
6	"log"
7	"os"
8	"os/signal"
9	"runtime"
10	"syscall"
11
12	"github.com/bnema/dumber/internal/app/api"
13	"github.com/bnema/dumber/internal/app/control"
14	"github.com/bnema/dumber/internal/app/environment"
15	"github.com/bnema/dumber/internal/app/messaging"
16	"github.com/bnema/dumber/internal/config"
17	"github.com/bnema/dumber/internal/db"
18	"github.com/bnema/dumber/internal/filtering"
19	"github.com/bnema/dumber/internal/logging"
20	"github.com/bnema/dumber/pkg/webkit"
21	"github.com/bnema/dumber/services"
22)
23
24// BrowserApp represents the main browser application
25type BrowserApp struct {
26	version   string
27	commit    string
28	buildDate string
29	assets    embed.FS
30
31	// Core components
32	config   *config.Config
33	database *sql.DB
34	queries  *db.Queries
35
36	// Services
37	parserService  *services.ParserService
38	browserService *services.BrowserService
39
40	// WebView and controllers
41	webView              *webkit.WebView
42	zoomController       *control.ZoomController
43	navigationController *control.NavigationController
44	clipboardController  *control.ClipboardController
45
46	// Content filtering
47	filterManager *filtering.FilterManager
48
49	// Handlers
50	schemeHandler   *api.SchemeHandler
51	messageHandler  *messaging.Handler
52	shortcutHandler *ShortcutHandler
53}
54
55// Run starts the browser application
56func Run(assets embed.FS, version, commit, buildDate string) {
57	log.Printf("Starting GUI mode (webkit_cgo=%v)", webkit.IsNativeAvailable())
58
59	app := &BrowserApp{
60		version:   version,
61		commit:    commit,
62		buildDate: buildDate,
63		assets:    assets,
64	}
65
66	if err := app.Initialize(); err != nil {
67		log.Printf("Failed to initialize browser: %v", err)
68		if webkit.IsNativeAvailable() {
69			runtime.UnlockOSThread()
70		}
71		os.Exit(1)
72	}
73
74	app.Run()
75}
76
77// Initialize sets up all browser components
78func (app *BrowserApp) Initialize() error {
79	// Setup media pipeline hardening
80	environment.SetupMediaPipeline()
81
82	// Initialize configuration
83	if err := config.Init(); err != nil {
84		return err
85	}
86	app.config = config.Get()
87
88	// Apply environment configurations
89	environment.ApplyCodecConfiguration(app.config.CodecPreferences)
90
91	// Setup crash handler
92	logging.SetupCrashHandler()
93
94	// Setup output capture if configured
95	if err := app.setupOutputCapture(); err != nil {
96		log.Printf("Warning: failed to setup output capture: %v", err)
97	}
98
99	// Initialize WebKit log capture if configured
100	if app.config.Logging.CaptureCOutput {
101		if err := webkit.InitWebKitLogCapture(); err != nil {
102			log.Printf("Warning: failed to initialize WebKit log capture: %v", err)
103		} else {
104			defer webkit.StopWebKitLogCapture()
105		}
106	}
107
108	log.Printf("Config initialized")
109
110	// Detect keyboard layout
111	environment.DetectAndSetKeyboardLocale()
112
113	// Initialize database
114	database, err := db.InitDB(app.config.Database.Path)
115	if err != nil {
116		return err
117	}
118	app.database = database
119	app.queries = db.New(database)
120	log.Printf("Database opened at %s", app.config.Database.Path)
121
122	// Initialize services
123	app.parserService = services.NewParserService(app.config, app.queries)
124	app.browserService = services.NewBrowserService(app.config, app.queries)
125
126	// Initialize handlers
127	app.schemeHandler = api.NewSchemeHandler(app.assets, app.parserService, app.browserService)
128	app.messageHandler = messaging.NewHandler(app.parserService, app.browserService)
129
130	return nil
131}
132
133// Run executes the main browser loop
134func (app *BrowserApp) Run() {
135	defer app.cleanup()
136	defer logging.SetupPanicRecovery()
137
138	// Register custom scheme resolver
139	webkit.SetURISchemeResolver(func(uri string) (string, []byte, bool) {
140		return app.schemeHandler.Handle(uri, app.config)
141	})
142
143	// Create and setup WebView
144	if err := app.createWebView(); err != nil {
145		log.Printf("Warning: failed to create WebView: %v", err)
146		return
147	}
148
149	// Initialize content blocking
150	if err := app.setupContentBlocking(); err != nil {
151		log.Printf("Warning: failed to setup content blocking: %v", err)
152		// Continue without content blocking
153	}
154
155	// Handle browse command if present
156	app.navigationController.HandleBrowseCommand()
157
158	// Setup signal handling
159	app.setupSignalHandling()
160
161	// Run main loop
162	app.runMainLoop()
163}
164
165// cleanup handles cleanup on shutdown
166func (app *BrowserApp) cleanup() {
167	if app.database != nil {
168		if closeErr := app.database.Close(); closeErr != nil {
169			log.Printf("Warning: failed to close database: %v", closeErr)
170		}
171	}
172}
173
174// setupOutputCapture initializes stdout/stderr capture if configured
175func (app *BrowserApp) setupOutputCapture() error {
176	if app.config.Logging.CaptureStdout || app.config.Logging.CaptureStderr {
177		log.Printf("Warning: stdout/stderr capture is experimental and may interfere with normal operations")
178		if logger := logging.GetLogger(); logger != nil {
179			outputCapture := logging.NewOutputCapture(logger)
180			if err := outputCapture.Start(); err != nil {
181				return err
182			}
183			// Note: defer outputCapture.Stop() should be handled by the caller
184		}
185	}
186	return nil
187}
188
189// setupSignalHandling configures graceful shutdown on signals
190func (app *BrowserApp) setupSignalHandling() {
191	sigChan := make(chan os.Signal, 1)
192	signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
193
194	// Handle signals in a goroutine to quit the main loop
195	go func() {
196		sig := <-sigChan
197		log.Printf("Received signal %v - shutting down gracefully", sig)
198		webkit.QuitMainLoop()
199	}()
200}
201
202// runMainLoop starts the appropriate main loop based on WebKit availability
203func (app *BrowserApp) runMainLoop() {
204	if webkit.IsNativeAvailable() {
205		log.Printf("Entering GTK main loop…")
206		webkit.RunMainLoop()
207		log.Printf("GTK main loop exited")
208	} else {
209		log.Printf("Not entering GUI loop (non-CGO build)")
210		// In non-CGO mode, just wait for signals
211		sigChan := make(chan os.Signal, 1)
212		signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
213		sig := <-sigChan
214		log.Printf("Received signal %v - exiting", sig)
215	}
216}

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