v0.30.0 eb3068f

defaults.go RawHistoryFolder

1package config
2
3import (
4	"os"
5	"path/filepath"
6
7	"github.com/bnema/dumber/internal/domain/entity"
8)
9
10// Default configuration constants
11const (
12	// History defaults
13	defaultMaxHistoryEntries = 10000 // entries
14	defaultRetentionDays     = 365   // 1 year
15
16	// Dmenu defaults
17	defaultMaxHistoryDays = 30 // days
18
19	// Logging defaults
20	defaultMaxLogAgeDays = 7   // days
21	defaultMaxLogFiles   = 100 // session log files
22
23	// Appearance defaults
24	defaultSidebarWidth                  = 320 // px, clamped to [280, 380]
25	defaultFontSize                      = 16  // points
26	defaultExternalThemeProvider         = "noctalia"
27	defaultExternalThemeFormat           = "colors-json"
28	defaultExternalThemeColorsFilename   = "colors.json"
29	defaultExternalThemeTemplateFormat   = "dumber-json"
30	defaultExternalThemeTemplateFilename = "noctalia-theme.json"
31
32	// Workspace defaults
33	defaultNewPaneURL = "about:blank"
34
35	// Omnibox defaults
36	defaultOmniboxInitialBehavior   = OmniboxInitialBehaviorRecent
37	defaultOmniboxMostVisitedDays   = 30
38	defaultOmniboxAutoOpenOnNewPane = false
39
40	// Workspace defaults
41	defaultPaneActivationShortcut    = "ctrl+p"
42	defaultPaneTimeoutMilliseconds   = 3000
43	defaultTabActivationShortcut     = "ctrl+t"
44	defaultTabTimeoutMilliseconds    = 3000
45	defaultResizeActivationShortcut  = "ctrl+n"
46	defaultResizeTimeoutMilliseconds = 3000
47	defaultResizeStepPercent         = 5.0
48	defaultResizeMinPanePercent      = 10.0
49	defaultTabBarPosition            = "bottom"
50	defaultPopupPlacement            = "right"
51	defaultFloatingPaneWidthPct      = 0.82
52	defaultFloatingPaneHeightPct     = 0.72
53
54	// Session defaults
55	defaultSessionActivationShortcut  = "ctrl+o"
56	defaultSessionTimeoutMilliseconds = 3000
57	defaultSnapshotIntervalMs         = 5000
58	defaultMaxExitedSessions          = 50
59	defaultMaxExitedSessionAgeDays    = 7
60
61	// Workspace styling defaults
62	// Active pane border (overlay)
63	defaultBorderWidth = 1
64	defaultBorderColor = "@theme_selected_bg_color"
65
66	// Mode border width (consolidated for all modes)
67	defaultModeBorderWidth = 4
68
69	// Mode colors (used for both borders and toaster)
70	defaultPaneModeColor    = "#4A90E2" // Blue for pane mode
71	defaultTabModeColor     = "#FFA500" // Orange for tab mode
72	defaultSessionModeColor = "#9B59B6" // Purple for session mode
73	defaultResizeModeColor  = "#00D4AA" // Cyan/teal for resize mode
74
75	// Mode indicator toaster
76	defaultModeIndicatorToasterEnabled = true
77
78	// Other styling
79	defaultTransitionDuration = 120
80	defaultUIScale            = 1.0 // UI scale multiplier (1.0 = 100%, 1.2 = 120%)
81
82	// Performance defaults
83	defaultZoomCacheSize              = 256 // domains to cache (~20KB memory)
84	defaultWebViewPoolPrewarmCount    = 4   // WebViews to pre-create at startup
85	defaultCEFWindowlessFrameRate     = 60  // static OSR frame rate when adaptive CEF pacing is disabled
86	defaultCEFWindowlessFrameRateMax  = 240 // adaptive OSR frame-rate hard cap
87	defaultCEFScrollMultiplier        = 1.0
88	defaultCEFScrollPreciseMultiplier = 2.5
89	defaultCEFScrollMaxDelta          = 0
90	defaultCEFTouchpadNavigation      = true
91	// Require a deliberate swipe before history navigation so ordinary
92	// horizontal scrolling is unlikely to cross the commit threshold.
93	defaultCEFTouchpadNavigationDelta = 320.0
94	defaultCEFTouchpadNavigationRatio = 0.5
95
96	// Skia threading defaults (0 = unset, -1 = unset for GPU threads)
97	defaultSkiaCPUPaintingThreads = 0
98	defaultSkiaGPUPaintingThreads = -1 // -1 means unset; 0 would disable GPU tile painting
99)
100
101func defaultBrowsingContextConfig() BrowsingContextConfig {
102	return BrowsingContextConfig{
103		Behavior:             PopupBehaviorSplit,
104		Placement:            defaultPopupPlacement,
105		OpenInNewPane:        true,
106		FollowPaneContext:    true,
107		BlankTargetBehavior:  "stacked",
108		EnableSmartDetection: true,
109		OAuthAutoClose:       true,
110	}
111}
112
113// getDefaultLogDir returns the default log directory, falls back to empty string on error
114func getDefaultLogDir() string {
115	logDir, err := GetLogDir()
116	if err != nil {
117		return ""
118	}
119	return logDir
120}
121
122// getDefaultExternalThemePath returns Noctalia's native colors.json path.
123func getDefaultExternalThemePath() string {
124	return getDefaultExternalThemePathForFormat(defaultExternalThemeFormat)
125}
126
127func getDefaultExternalThemePathForFormat(format string) string {
128	switch format {
129	case defaultExternalThemeTemplateFormat:
130		return getDefaultExternalThemeTemplatePath()
131	default:
132		return getDefaultNoctaliaColorsPath()
133	}
134}
135
136func getDefaultNoctaliaColorsPath() string {
137	configHome, err := os.UserConfigDir()
138	if err != nil {
139		return ""
140	}
141	return filepath.Join(configHome, defaultExternalThemeProvider, defaultExternalThemeColorsFilename)
142}
143
144func getDefaultExternalThemeTemplatePath() string {
145	configDir, err := GetConfigDir()
146	if err != nil {
147		return ""
148	}
149	return filepath.Join(configDir, defaultExternalThemeTemplateFilename)
150}
151
152// DefaultConfig returns the default configuration values for dumber.
153func DefaultConfig() *Config {
154	browsingContextDefaults := defaultBrowsingContextConfig()
155
156	return &Config{
157		Database: DatabaseConfig{
158			// Path is set dynamically in config.Load()
159		},
160		History: HistoryConfig{
161			MaxEntries:          defaultMaxHistoryEntries,
162			RetentionPeriodDays: defaultRetentionDays, // 1 year
163			CleanupIntervalDays: 1,                    // daily cleanup
164		},
165		SearchShortcuts:     GetDefaultSearchShortcuts(),
166		DefaultSearchEngine: "https://duckduckgo.com/?q=%s",
167		Dmenu: DmenuConfig{
168			MaxHistoryDays:   defaultMaxHistoryDays,
169			ShowVisitCount:   true,
170			ShowLastVisited:  true,
171			HistoryPrefix:    "🕒",
172			ShortcutPrefix:   "🔍",
173			URLPrefix:        "🌐",
174			DateFormat:       "2006-01-02 15:04",
175			SortByVisitCount: true,
176		},
177		Logging: LoggingConfig{
178			Level:          "info",
179			Format:         "text", // text or json
180			MaxAge:         defaultMaxLogAgeDays,
181			MaxFiles:       defaultMaxLogFiles,
182			LogDir:         getDefaultLogDir(),
183			EnableFileLog:  true,
184			CaptureConsole: false, // Disabled by default
185			CaptureGTKLogs: false, // Disabled by default
186		},
187		Appearance: AppearanceConfig{
188			SansFont:        "Fira Sans",
189			SerifFont:       "Fira Sans",
190			MonospaceFont:   "Fira Code",
191			GtkFont:         "Adwaita Sans",
192			DefaultFontSize: defaultFontSize,
193			LightPalette: ColorPalette{
194				Background:     "#fafafa",
195				Surface:        "#f4f4f5",
196				SurfaceVariant: "#e4e4e7",
197				Text:           "#18181b",
198				Muted:          "#71717a",
199				Accent:         "#22c55e", // Green-500 - vibrant primary
200				Border:         "#d4d4d8",
201			},
202			DarkPalette: ColorPalette{
203				Background:     "#0a0a0b",
204				Surface:        "#18181b",
205				SurfaceVariant: "#27272a",
206				Text:           "#fafafa",
207				Muted:          "#a1a1aa",
208				Accent:         "#4ade80", // Green-400 - vibrant primary
209				Border:         "#3f3f46",
210			},
211			ColorScheme: "default", // default follows system theme
212			ExternalTheme: entity.ExternalThemeConfig{
213				Enabled:  false,
214				Provider: defaultExternalThemeProvider,
215				Format:   defaultExternalThemeFormat,
216				Path:     getDefaultExternalThemePath(),
217			},
218		},
219		Debug: DebugConfig{
220			EnableDevTools: true,
221		},
222		Engine: EngineConfig{
223			Type:             EngineTypeCEF,
224			Profile:          ProfileDefault,
225			PoolPrewarmCount: defaultWebViewPoolPrewarmCount,
226			ZoomCacheSize:    defaultZoomCacheSize,
227			// With ITP enabled, WebKit ignores ACCEPT_NO_THIRD_PARTY — ITP handles
228			// third-party cookie isolation more intelligently. Using Always + ITP
229			// matches Epiphany's model and avoids a misleading setting.
230			CookiePolicy: CookiePolicyAlways,
231			CEF: CEFEngineConfig{
232				RenderStack:                 CEFRenderStackVulkan,
233				AdaptiveWindowlessFrameRate: true,
234				WindowlessFrameRate:         0,
235				WindowlessFrameRateMax:      defaultCEFWindowlessFrameRateMax,
236				LogFile:                     "",
237				EnableAudioHandler:          true,
238				Input: CEFInputConfig{
239					ScrollWheelMultiplier:              defaultCEFScrollMultiplier,
240					ScrollPreciseMultiplier:            defaultCEFScrollPreciseMultiplier,
241					ScrollHorizontalMultiplier:         defaultCEFScrollMultiplier,
242					ScrollVerticalMultiplier:           defaultCEFScrollMultiplier,
243					ScrollMaxDelta:                     defaultCEFScrollMaxDelta,
244					TouchpadNavigationEnabled:          defaultCEFTouchpadNavigation,
245					TouchpadNavigationMinDelta:         defaultCEFTouchpadNavigationDelta,
246					TouchpadNavigationMaxVerticalRatio: defaultCEFTouchpadNavigationRatio,
247				},
248			},
249			WebKit: WebKitEngineConfig{
250				ITPEnabled:             true,
251				SkiaCPUPaintingThreads: defaultSkiaCPUPaintingThreads,
252				SkiaGPUPaintingThreads: defaultSkiaGPUPaintingThreads,
253				GSKRenderer:            GSKRendererAuto,
254				GLRenderingMode:        GLRenderingModeAuto,
255			},
256		},
257		DefaultWebpageZoom: 1.2,                 // 120% default zoom for better readability
258		DefaultUIScale:     defaultUIScale,      // 1.0 = 100%, 2.0 = 200%
259		SidebarWidth:       defaultSidebarWidth, // 320px, clamped to [280, 380]
260		Workspace: WorkspaceConfig{
261			NewPaneURL:        defaultNewPaneURL,
262			SwitchToTabOnMove: true,
263			PaneMode: PaneModeConfig{
264				ActivationShortcut:  defaultPaneActivationShortcut,
265				TimeoutMilliseconds: defaultPaneTimeoutMilliseconds,
266				Actions: map[string]ActionBinding{
267					"split-right":           {Keys: []string{"arrowright", "r"}, Desc: "Split pane to the right"},
268					"split-left":            {Keys: []string{"arrowleft", "l"}, Desc: "Split pane to the left"},
269					"split-up":              {Keys: []string{"arrowup", "u"}, Desc: "Split pane upward"},
270					"split-down":            {Keys: []string{"arrowdown", "d"}, Desc: "Split pane downward"},
271					"stack-pane":            {Keys: []string{"s"}, Desc: "Stack pane with sibling"},
272					"close-pane":            {Keys: []string{"x"}, Desc: "Close current pane"},
273					"move-pane-to-tab":      {Keys: []string{"m"}, Desc: "Move pane to different tab"},
274					"move-pane-to-next-tab": {Keys: []string{"M", "shift+m"}, Desc: "Move pane to next tab"},
275					"eject-pane-to-window":  {Keys: []string{"w"}, Desc: "Eject active pane to a new window"},
276
277					"consume-or-expel-left":  {Keys: []string{"["}, Desc: "Consume/expel pane left"},
278					"consume-or-expel-right": {Keys: []string{"]"}, Desc: "Consume/expel pane right"},
279					"consume-or-expel-up":    {Keys: []string{"{"}, Desc: "Consume/expel pane up"},
280					"consume-or-expel-down":  {Keys: []string{"}"}, Desc: "Consume/expel pane down"},
281
282					"focus-right": {Keys: []string{"shift+arrowright", "shift+l"}, Desc: "Focus pane to the right"},
283					"focus-left":  {Keys: []string{"shift+arrowleft", "shift+h"}, Desc: "Focus pane to the left"},
284					"focus-up":    {Keys: []string{"shift+arrowup", "shift+k"}, Desc: "Focus pane above"},
285					"focus-down":  {Keys: []string{"shift+arrowdown", "shift+j"}, Desc: "Focus pane below"},
286					"confirm":     {Keys: []string{"enter"}, Desc: "Confirm action"},
287					"cancel":      {Keys: []string{"escape"}, Desc: "Cancel/exit mode"},
288				},
289			},
290			TabMode: TabModeConfig{
291				ActivationShortcut:  defaultTabActivationShortcut,
292				TimeoutMilliseconds: defaultTabTimeoutMilliseconds,
293				Actions: map[string]ActionBinding{
294					"new-tab":      {Keys: []string{"n", "c"}, Desc: "Create new tab"},
295					"close-tab":    {Keys: []string{"x"}, Desc: "Close current tab"},
296					"next-tab":     {Keys: []string{"l", "tab"}, Desc: "Switch to next tab"},
297					"previous-tab": {Keys: []string{"h", "shift+tab"}, Desc: "Switch to previous tab"},
298					"rename-tab":   {Keys: []string{"r"}, Desc: "Rename current tab"},
299					"confirm":      {Keys: []string{"enter"}, Desc: "Confirm action"},
300					"cancel":       {Keys: []string{"escape"}, Desc: "Cancel/exit mode"},
301				},
302			},
303			ResizeMode: ResizeModeConfig{
304				ActivationShortcut:  defaultResizeActivationShortcut,
305				TimeoutMilliseconds: defaultResizeTimeoutMilliseconds,
306				StepPercent:         defaultResizeStepPercent,
307				MinPanePercent:      defaultResizeMinPanePercent,
308				Actions: map[string]ActionBinding{
309					"resize-increase-left":  {Keys: []string{"h", "arrowleft"}, Desc: "Increase size leftward"},
310					"resize-increase-down":  {Keys: []string{"j", "arrowdown"}, Desc: "Increase size downward"},
311					"resize-increase-up":    {Keys: []string{"k", "arrowup"}, Desc: "Increase size upward"},
312					"resize-increase-right": {Keys: []string{"l", "arrowright"}, Desc: "Increase size rightward"},
313					"resize-decrease-left":  {Keys: []string{"H"}, Desc: "Decrease size leftward"},
314					"resize-decrease-down":  {Keys: []string{"J"}, Desc: "Decrease size downward"},
315					"resize-decrease-up":    {Keys: []string{"K"}, Desc: "Decrease size upward"},
316					"resize-decrease-right": {Keys: []string{"L"}, Desc: "Decrease size rightward"},
317					"resize-increase":       {Keys: []string{"+", "="}, Desc: "Increase pane size"},
318					"resize-decrease":       {Keys: []string{"-"}, Desc: "Decrease pane size"},
319					"confirm":               {Keys: []string{"enter"}, Desc: "Confirm action"},
320					"cancel":                {Keys: []string{"escape"}, Desc: "Cancel/exit mode"},
321				},
322			},
323			Shortcuts: GlobalShortcutsConfig{
324				Actions: map[string]ActionBinding{
325					"toggle-floating-pane":        {Keys: []string{"alt+f"}, Desc: "Toggle floating pane"},
326					"toggle-history-systemview":   {Keys: []string{"ctrl+h"}, Desc: "Toggle History in right split"},
327					"toggle-favorites-systemview": {Keys: []string{}, Desc: "Toggle Favorites in right split"},
328					"toggle-config-systemview":    {Keys: []string{}, Desc: "Toggle Config in right split"},
329					"close-pane":                  {Keys: []string{"ctrl+w"}, Desc: "Close active pane"},
330					"next-tab":                    {Keys: []string{"ctrl+tab"}, Desc: "Switch to next tab"},
331					"previous-tab":                {Keys: []string{"ctrl+shift+tab"}, Desc: "Switch to previous tab"},
332					"consume-or-expel-left":       {Keys: []string{"alt+["}, Desc: "Consume/expel pane left"},
333					"consume-or-expel-right":      {Keys: []string{"alt+]"}, Desc: "Consume/expel pane right"},
334					"consume-or-expel-up":         {Keys: []string{"alt+{"}, Desc: "Consume/expel pane up"},
335					"consume-or-expel-down":       {Keys: []string{"alt+}"}, Desc: "Consume/expel pane down"},
336				},
337			},
338			FloatingPane: FloatingPaneConfig{
339				WidthPct:  defaultFloatingPaneWidthPct,
340				HeightPct: defaultFloatingPaneHeightPct,
341				Profiles:  map[string]FloatingPaneProfile{},
342			},
343			TabBarPosition:          defaultTabBarPosition,
344			HideTabBarWhenSingleTab: true,
345			BrowsingContexts:        browsingContextDefaults,
346			Popups:                  browsingContextDefaults,
347			Styling: WorkspaceStylingConfig{
348				BorderWidth:                 defaultBorderWidth,
349				BorderColor:                 defaultBorderColor,
350				ModeBorderWidth:             defaultModeBorderWidth,
351				PaneModeColor:               defaultPaneModeColor,
352				TabModeColor:                defaultTabModeColor,
353				SessionModeColor:            defaultSessionModeColor,
354				ResizeModeColor:             defaultResizeModeColor,
355				ModeIndicatorToasterEnabled: defaultModeIndicatorToasterEnabled,
356				TransitionDuration:          defaultTransitionDuration,
357			},
358		},
359		ContentFiltering: ContentFilteringConfig{
360			Enabled:    true, // Ad blocking enabled by default
361			AutoUpdate: true, // Auto-update filters from GitHub releases
362		},
363		Clipboard: ClipboardConfig{
364			AutoCopyOnSelection: true, // Enabled by default (zellij-style)
365		},
366		Omnibox: OmniboxConfig{
367			InitialBehavior:   defaultOmniboxInitialBehavior,
368			MostVisitedDays:   defaultOmniboxMostVisitedDays,
369			AutoOpenOnNewPane: defaultOmniboxAutoOpenOnNewPane,
370		},
371		Session: SessionConfig{
372			AutoRestore:             false,
373			SnapshotIntervalMs:      defaultSnapshotIntervalMs,
374			MaxExitedSessions:       defaultMaxExitedSessions,
375			MaxExitedSessionAgeDays: defaultMaxExitedSessionAgeDays,
376			SessionMode: SessionModeConfig{
377				ActivationShortcut:  defaultSessionActivationShortcut,
378				TimeoutMilliseconds: defaultSessionTimeoutMilliseconds,
379				Actions: map[string]ActionBinding{
380					"session-manager": {Keys: []string{"s", "w"}, Desc: "Open session manager"},
381					"confirm":         {Keys: []string{"enter"}, Desc: "Confirm action"},
382					"cancel":          {Keys: []string{"escape"}, Desc: "Cancel/exit mode"},
383				},
384			},
385		},
386		Media: MediaConfig{
387			HardwareDecodingMode:     HardwareDecodingAuto, // auto allows sw fallback
388			PreferAV1:                false,                // Don't force codec preference, let site choose
389			ShowDiagnosticsOnStartup: false,                // Disabled - diagnostics can be noisy
390			// GStreamer fields (ForceVSync, GLRenderingMode, GStreamerDebugLevel)
391			// moved to [engine.webkit] — zero values here prevent them from being
392			// written back when marshaling the Config struct.
393		},
394		Update: UpdateConfig{
395			EnableOnStartup:     true,  // Check for updates on startup by default
396			AutoDownload:        false, // Conservative: don't auto-download by default
397			NotifyOnNewSettings: true,  // Show toast when new config settings available
398		},
399		Downloads: DownloadsConfig{
400			Path: "", // Empty = use XDG_DOWNLOAD_DIR or ~/Downloads
401		},
402	}
403}
404
405// GetDefaultSearchShortcuts returns the default search shortcuts.
406func GetDefaultSearchShortcuts() map[string]SearchShortcut {
407	return map[string]SearchShortcut{
408		"ddg": {
409			URL:         "https://duckduckgo.com/?q=%s",
410			Description: "DuckDuckGo search",
411		},
412		"g": {
413			URL:         "https://www.google.com/search?q=%s",
414			Description: "Google search",
415		},
416		"gi": {
417			URL:         "https://www.google.com/search?tbm=isch&q=%s",
418			Description: "Google Images search",
419		},
420		"gh": {
421			URL:         "https://github.com/search?q=%s",
422			Description: "GitHub search",
423		},
424		"go": {
425			URL:         "https://pkg.go.dev/search?q=%s",
426			Description: "Go package search",
427		},
428		"mdn": {
429			URL:         "https://developer.mozilla.org/en-US/search?q=%s",
430			Description: "MDN Web Docs search",
431		},
432		"npm": {
433			URL:         "https://www.npmjs.com/search?q=%s",
434			Description: "npm package search",
435		},
436		"r": {
437			URL:         "https://www.reddit.com/search?q=%s",
438			Description: "Reddit search",
439		},
440		"so": {
441			URL:         "https://stackoverflow.com/search?q=%s",
442			Description: "Stack Overflow search",
443		},
444		"w": {
445			URL:         "https://en.wikipedia.org/wiki/%s",
446			Description: "Wikipedia search",
447		},
448		"yt": {
449			URL:         "https://www.youtube.com/results?search_query=%s",
450			Description: "YouTube search",
451		},
452	}
453}

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