package config import ( "os" "path/filepath" "github.com/bnema/dumber/internal/domain/entity" ) // Default configuration constants const ( // History defaults defaultMaxHistoryEntries = 10000 // entries defaultRetentionDays = 365 // 1 year // Dmenu defaults defaultMaxHistoryDays = 30 // days // Logging defaults defaultMaxLogAgeDays = 7 // days defaultMaxLogFiles = 100 // session log files // Appearance defaults defaultSidebarWidth = 320 // px, clamped to [280, 380] defaultFontSize = 16 // points defaultExternalThemeProvider = "noctalia" defaultExternalThemeFormat = "colors-json" defaultExternalThemeColorsFilename = "colors.json" defaultExternalThemeTemplateFormat = "dumber-json" defaultExternalThemeTemplateFilename = "noctalia-theme.json" // Workspace defaults defaultNewPaneURL = "about:blank" // Omnibox defaults defaultOmniboxStyle = "command" defaultOmniboxInitialBehavior = OmniboxInitialBehaviorRecent defaultOmniboxMaxHistoryDays = 30 defaultOmniboxAutoOpenOnNewPane = false // Workspace defaults defaultPaneActivationShortcut = "ctrl+p" defaultPaneTimeoutMilliseconds = 3000 defaultTabActivationShortcut = "ctrl+t" defaultTabTimeoutMilliseconds = 3000 defaultResizeActivationShortcut = "ctrl+n" defaultResizeTimeoutMilliseconds = 3000 defaultPageActivationShortcut = "ctrl+y" defaultPageTimeoutMilliseconds = 0 defaultPageSequenceTimeoutMilliseconds = 500 defaultResizeStepPercent = 5.0 defaultResizeMinPanePercent = 10.0 defaultTabBarPosition = "bottom" defaultPopupPlacement = "right" defaultExternalLinkBehavior = ExternalLinkBehaviorWindowed defaultExternalLinkPlacement = ExternalLinkPlacementRight defaultFloatingPaneWidthPct = 0.82 defaultFloatingPaneHeightPct = 0.72 // Session defaults defaultSessionActivationShortcut = "ctrl+o" defaultSessionTimeoutMilliseconds = 3000 defaultSnapshotIntervalMs = 5000 defaultMaxExitedSessions = 50 defaultMaxExitedSessionAgeDays = 7 // Workspace styling defaults // Active pane border (overlay) defaultBorderWidth = 1 defaultBorderColor = "@theme_selected_bg_color" // Mode border width (consolidated for all modes) defaultModeBorderWidth = 4 // Mode colors (used for both borders and toaster) defaultPaneModeColor = "#4A90E2" // Blue for pane mode defaultTabModeColor = "#FFA500" // Orange for tab mode defaultSessionModeColor = "#9B59B6" // Purple for session mode defaultVimModeColor = "#F7768E" defaultResizeModeColor = "#00D4AA" // Cyan/teal for resize mode // Mode indicator toaster defaultModeIndicatorToasterEnabled = true // Other styling defaultTransitionDuration = 120 defaultUIScale = 1.0 // UI scale multiplier (1.0 = 100%, 1.2 = 120%) // Performance defaults defaultZoomCacheSize = 256 // domains to cache (~20KB memory) defaultWebViewPoolPrewarmCount = 4 // WebViews to pre-create at startup defaultCEFWindowlessFrameRate = 60 // static OSR frame rate when adaptive CEF pacing is disabled defaultCEFWindowlessFrameRateMax = 240 // adaptive OSR frame-rate hard cap defaultCEFScrollMultiplier = 1.0 defaultCEFScrollPreciseMultiplier = 2.5 defaultCEFScrollMaxDelta = 0 defaultCEFScrollTouchpadInertia = true defaultCEFScrollWheelSmoothing = true defaultCEFTouchpadNavigation = true // Require a deliberate swipe before history navigation so ordinary // horizontal scrolling is unlikely to cross the commit threshold. defaultCEFTouchpadNavigationDelta = 320.0 defaultCEFTouchpadNavigationRatio = 0.5 // Skia threading defaults (0 = unset, -1 = unset for GPU threads) defaultSkiaCPUPaintingThreads = 0 defaultSkiaGPUPaintingThreads = -1 // -1 means unset; 0 would disable GPU tile painting ) func defaultBrowsingContextConfig() BrowsingContextConfig { return BrowsingContextConfig{ Behavior: PopupBehaviorSplit, Placement: defaultPopupPlacement, OpenInNewPane: true, FollowPaneContext: true, BlankTargetBehavior: "stacked", EnableSmartDetection: true, OAuthAutoClose: true, } } // getDefaultLogDir returns the default log directory, falls back to empty string on error func getDefaultLogDir() string { logDir, err := GetLogDir() if err != nil { return "" } return logDir } // getDefaultExternalThemePath returns Noctalia's native colors.json path. func getDefaultExternalThemePath() string { return getDefaultExternalThemePathForFormat(defaultExternalThemeFormat) } func getDefaultExternalThemePathForFormat(format string) string { switch format { case defaultExternalThemeTemplateFormat: return getDefaultExternalThemeTemplatePath() default: return getDefaultNoctaliaColorsPath() } } func getDefaultNoctaliaColorsPath() string { configHome, err := os.UserConfigDir() if err != nil { return "" } return filepath.Join(configHome, defaultExternalThemeProvider, defaultExternalThemeColorsFilename) } func getDefaultExternalThemeTemplatePath() string { configDir, err := GetConfigDir() if err != nil { return "" } return filepath.Join(configDir, defaultExternalThemeTemplateFilename) } // DefaultConfig returns the default configuration values for dumber. func DefaultConfig() *Config { browsingContextDefaults := defaultBrowsingContextConfig() return &Config{ Database: DatabaseConfig{ // Path is set dynamically in config.Load() }, History: HistoryConfig{ MaxEntries: defaultMaxHistoryEntries, RetentionPeriodDays: defaultRetentionDays, // 1 year CleanupIntervalDays: 1, // daily cleanup }, SearchShortcuts: GetDefaultSearchShortcuts(), DefaultSearchEngine: "https://duckduckgo.com/?q=%s", Dmenu: DmenuConfig{ MaxHistoryDays: defaultMaxHistoryDays, ShowVisitCount: true, ShowLastVisited: true, HistoryPrefix: "🕒", ShortcutPrefix: "🔍", URLPrefix: "🌐", DateFormat: "2006-01-02 15:04", SortByVisitCount: true, }, Logging: LoggingConfig{ Level: "info", Format: "text", // text or json MaxAge: defaultMaxLogAgeDays, MaxFiles: defaultMaxLogFiles, LogDir: getDefaultLogDir(), EnableFileLog: true, CaptureConsole: false, // Disabled by default CaptureGTKLogs: false, // Disabled by default }, Appearance: AppearanceConfig{ SansFont: "Fira Sans", SerifFont: "Fira Sans", MonospaceFont: "Fira Code", GtkFont: "Adwaita Sans", DefaultFontSize: defaultFontSize, LightPalette: ColorPalette{ Background: "#fafafa", Surface: "#f4f4f5", SurfaceVariant: "#e4e4e7", Text: "#18181b", Muted: "#71717a", Accent: "#22c55e", // Green-500 - vibrant primary Border: "#d4d4d8", }, DarkPalette: ColorPalette{ Background: "#0a0a0b", Surface: "#18181b", SurfaceVariant: "#27272a", Text: "#fafafa", Muted: "#a1a1aa", Accent: "#4ade80", // Green-400 - vibrant primary Border: "#3f3f46", }, ColorScheme: "default", // default follows system theme ExternalTheme: entity.ExternalThemeConfig{ Enabled: false, Provider: defaultExternalThemeProvider, Format: defaultExternalThemeFormat, Path: getDefaultExternalThemePath(), }, }, Debug: DebugConfig{ EnableDevTools: true, }, Engine: EngineConfig{ Type: EngineTypeCEF, Profile: ProfileDefault, PoolPrewarmCount: defaultWebViewPoolPrewarmCount, ZoomCacheSize: defaultZoomCacheSize, // With ITP enabled, WebKit ignores ACCEPT_NO_THIRD_PARTY — ITP handles // third-party cookie isolation more intelligently. Using Always + ITP // matches Epiphany's model and avoids a misleading setting. CookiePolicy: CookiePolicyAlways, CEF: CEFEngineConfig{ RenderStack: CEFRenderStackVulkan, AdaptiveWindowlessFrameRate: true, WindowlessFrameRate: 0, WindowlessFrameRateMax: defaultCEFWindowlessFrameRateMax, LogFile: "", EnableAudioHandler: true, Input: CEFInputConfig{ ScrollWheelMultiplier: defaultCEFScrollMultiplier, ScrollPreciseMultiplier: defaultCEFScrollPreciseMultiplier, ScrollHorizontalMultiplier: defaultCEFScrollMultiplier, ScrollVerticalMultiplier: defaultCEFScrollMultiplier, ScrollMaxDelta: defaultCEFScrollMaxDelta, ScrollTouchpadInertia: defaultCEFScrollTouchpadInertia, ScrollWheelSmoothing: defaultCEFScrollWheelSmoothing, TouchpadNavigationEnabled: defaultCEFTouchpadNavigation, TouchpadNavigationMinDelta: defaultCEFTouchpadNavigationDelta, TouchpadNavigationMaxVerticalRatio: defaultCEFTouchpadNavigationRatio, }, }, WebKit: WebKitEngineConfig{ ITPEnabled: true, SkiaCPUPaintingThreads: defaultSkiaCPUPaintingThreads, SkiaGPUPaintingThreads: defaultSkiaGPUPaintingThreads, GSKRenderer: GSKRendererAuto, GLRenderingMode: GLRenderingModeAuto, }, }, DefaultWebpageZoom: 1.2, // 120% default zoom for better readability DefaultUIScale: defaultUIScale, // 1.0 = 100%, 2.0 = 200% SidebarWidth: defaultSidebarWidth, // 320px, clamped to [280, 380] Workspace: WorkspaceConfig{ NewPaneURL: defaultNewPaneURL, SwitchToTabOnMove: true, PaneMode: PaneModeConfig{ ActivationShortcut: defaultPaneActivationShortcut, TimeoutMilliseconds: defaultPaneTimeoutMilliseconds, Actions: map[string]ActionBinding{ "split-right": {Keys: []string{"arrowright", "r"}, Desc: "Split pane to the right"}, "split-left": {Keys: []string{"arrowleft", "l"}, Desc: "Split pane to the left"}, "split-up": {Keys: []string{"arrowup", "u"}, Desc: "Split pane upward"}, "split-down": {Keys: []string{"arrowdown", "d"}, Desc: "Split pane downward"}, "stack-pane": {Keys: []string{"s"}, Desc: "Stack pane with sibling"}, "close-pane": {Keys: []string{"x"}, Desc: "Close current pane"}, "move-pane-to-tab": {Keys: []string{"m"}, Desc: "Move pane to different tab"}, "move-pane-to-next-tab": {Keys: []string{"M", "shift+m"}, Desc: "Move pane to next tab"}, "eject-pane-to-window": {Keys: []string{"w"}, Desc: "Eject active pane to a new window"}, "consume-or-expel-left": {Keys: []string{"["}, Desc: "Consume/expel pane left"}, "consume-or-expel-right": {Keys: []string{"]"}, Desc: "Consume/expel pane right"}, "consume-or-expel-up": {Keys: []string{"{"}, Desc: "Consume/expel pane up"}, "consume-or-expel-down": {Keys: []string{"}"}, Desc: "Consume/expel pane down"}, "focus-right": {Keys: []string{"shift+arrowright", "shift+l"}, Desc: "Focus pane to the right"}, "focus-left": {Keys: []string{"shift+arrowleft", "shift+h"}, Desc: "Focus pane to the left"}, "focus-up": {Keys: []string{"shift+arrowup", "shift+k"}, Desc: "Focus pane above"}, "focus-down": {Keys: []string{"shift+arrowdown", "shift+j"}, Desc: "Focus pane below"}, "confirm": {Keys: []string{"enter"}, Desc: "Confirm action"}, "cancel": {Keys: []string{"escape"}, Desc: "Cancel/exit mode"}, }, }, TabMode: TabModeConfig{ ActivationShortcut: defaultTabActivationShortcut, TimeoutMilliseconds: defaultTabTimeoutMilliseconds, Actions: map[string]ActionBinding{ "new-tab": {Keys: []string{"n", "c"}, Desc: "Create new tab"}, "close-tab": {Keys: []string{"x"}, Desc: "Close current tab"}, "next-tab": {Keys: []string{"l", "tab"}, Desc: "Switch to next tab"}, "previous-tab": {Keys: []string{"h", "shift+tab"}, Desc: "Switch to previous tab"}, "rename-tab": {Keys: []string{"r"}, Desc: "Rename current tab"}, "confirm": {Keys: []string{"enter"}, Desc: "Confirm action"}, "cancel": {Keys: []string{"escape"}, Desc: "Cancel/exit mode"}, }, }, VimMode: VimModeConfig{ ActivationShortcut: defaultPageActivationShortcut, TimeoutMilliseconds: defaultPageTimeoutMilliseconds, SequenceTimeoutMilliseconds: defaultPageSequenceTimeoutMilliseconds, Actions: map[string]ActionBinding{ "vim-scroll-left": {Keys: []string{"h"}, Desc: "Scroll page left"}, "vim-scroll-down": {Keys: []string{"j"}, Desc: "Scroll page down"}, "vim-scroll-up": {Keys: []string{"k"}, Desc: "Scroll page up"}, "vim-scroll-right": {Keys: []string{"l"}, Desc: "Scroll page right"}, "vim-scroll-down-fast": {Keys: []string{"shift+j"}, Desc: "Scroll page down fast"}, "vim-scroll-up-fast": {Keys: []string{"shift+k"}, Desc: "Scroll page up fast"}, "confirm": {Keys: []string{"enter"}, Desc: "Open selected heading link and exit mode"}, "cancel": {Keys: []string{"escape"}, Desc: "Cancel/exit mode"}, "heading-next": {Keys: []string{"]]"}, Desc: "Jump to next heading"}, "heading-prev": {Keys: []string{"[["}, Desc: "Jump to previous heading"}, "focus-input": {Keys: []string{"gi"}, Desc: "Focus next page input"}, "code-next": {Keys: []string{"]c"}, Desc: "Jump to next code block"}, "code-prev": {Keys: []string{"[c"}, Desc: "Jump to previous code block"}, "table-next": {Keys: []string{"]t"}, Desc: "Jump to next table"}, "image-next": {Keys: []string{"]i"}, Desc: "Jump to next image"}, "list-next": {Keys: []string{"]l"}, Desc: "Jump to next list"}, "outline": {Keys: []string{"gO"}, Desc: "Show page outline"}, "yank-section": {Keys: []string{"yah"}, Desc: "Yank current section"}, "half-page-down": {Keys: []string{""}, Desc: "Scroll half page down"}, "half-page-up": {Keys: []string{""}, Desc: "Scroll half page up"}, }, }, ResizeMode: ResizeModeConfig{ ActivationShortcut: defaultResizeActivationShortcut, TimeoutMilliseconds: defaultResizeTimeoutMilliseconds, StepPercent: defaultResizeStepPercent, MinPanePercent: defaultResizeMinPanePercent, Actions: map[string]ActionBinding{ "resize-increase-left": {Keys: []string{"h", "arrowleft"}, Desc: "Increase size leftward"}, "resize-increase-down": {Keys: []string{"j", "arrowdown"}, Desc: "Increase size downward"}, "resize-increase-up": {Keys: []string{"k", "arrowup"}, Desc: "Increase size upward"}, "resize-increase-right": {Keys: []string{"l", "arrowright"}, Desc: "Increase size rightward"}, "resize-decrease-left": {Keys: []string{"H"}, Desc: "Decrease size leftward"}, "resize-decrease-down": {Keys: []string{"J"}, Desc: "Decrease size downward"}, "resize-decrease-up": {Keys: []string{"K"}, Desc: "Decrease size upward"}, "resize-decrease-right": {Keys: []string{"L"}, Desc: "Decrease size rightward"}, "resize-increase": {Keys: []string{"+", "="}, Desc: "Increase pane size"}, "resize-decrease": {Keys: []string{"-"}, Desc: "Decrease pane size"}, "confirm": {Keys: []string{"enter"}, Desc: "Confirm action"}, "cancel": {Keys: []string{"escape"}, Desc: "Cancel/exit mode"}, }, }, Shortcuts: GlobalShortcutsConfig{ Actions: map[string]ActionBinding{ "toggle-floating-pane": {Keys: []string{"alt+f"}, Desc: "Toggle floating pane"}, "toggle-history-systemview": {Keys: []string{"ctrl+h"}, Desc: "Toggle History in right split"}, "toggle-favorites-sidebar": {Keys: []string{"ctrl+b"}, Desc: "Toggle Favorites sidebar"}, "toggle-current-page-favorite": {Keys: []string{"ctrl+d"}, Desc: "Add/remove current page favorite"}, "toggle-config-systemview": {Keys: []string{}, Desc: "Toggle Config in right split"}, "close-pane": {Keys: []string{"ctrl+w"}, Desc: "Close active pane"}, "next-tab": {Keys: []string{"ctrl+tab"}, Desc: "Switch to next tab"}, "previous-tab": {Keys: []string{"ctrl+shift+tab"}, Desc: "Switch to previous tab"}, "consume-or-expel-left": {Keys: []string{"alt+["}, Desc: "Consume/expel pane left"}, "consume-or-expel-right": {Keys: []string{"alt+]"}, Desc: "Consume/expel pane right"}, "consume-or-expel-up": {Keys: []string{"alt+{"}, Desc: "Consume/expel pane up"}, "consume-or-expel-down": {Keys: []string{"alt+}"}, Desc: "Consume/expel pane down"}, }, }, FloatingPane: FloatingPaneConfig{ WidthPct: defaultFloatingPaneWidthPct, HeightPct: defaultFloatingPaneHeightPct, Profiles: map[string]FloatingPaneProfile{}, }, ExternalLinks: ExternalLinksConfig{ Behavior: defaultExternalLinkBehavior, Placement: defaultExternalLinkPlacement, }, TabBarPosition: defaultTabBarPosition, HideTabBarWhenSingleTab: true, BrowsingContexts: browsingContextDefaults, Popups: browsingContextDefaults, Styling: WorkspaceStylingConfig{ BorderWidth: defaultBorderWidth, BorderColor: defaultBorderColor, ModeBorderWidth: defaultModeBorderWidth, PaneModeColor: defaultPaneModeColor, TabModeColor: defaultTabModeColor, SessionModeColor: defaultSessionModeColor, ResizeModeColor: defaultResizeModeColor, VimModeColor: defaultVimModeColor, ModeLegendLinger: true, ModeIndicatorToasterEnabled: defaultModeIndicatorToasterEnabled, ModeLegend: "delay", ModeLegendDelayMs: 500, ModeLegendAnimations: true, TransitionDuration: defaultTransitionDuration, }, }, ContentFiltering: ContentFilteringConfig{ Enabled: true, // Ad blocking enabled by default AutoUpdate: true, // Auto-update filters from GitHub releases }, Clipboard: ClipboardConfig{ AutoCopyOnSelection: true, // Enabled by default (zellij-style) }, Omnibox: OmniboxConfig{ Style: defaultOmniboxStyle, InitialBehavior: defaultOmniboxInitialBehavior, MaxHistoryDays: defaultOmniboxMaxHistoryDays, AutoOpenOnNewPane: defaultOmniboxAutoOpenOnNewPane, }, Session: SessionConfig{ AutoRestore: false, SnapshotIntervalMs: defaultSnapshotIntervalMs, MaxExitedSessions: defaultMaxExitedSessions, MaxExitedSessionAgeDays: defaultMaxExitedSessionAgeDays, SessionMode: SessionModeConfig{ ActivationShortcut: defaultSessionActivationShortcut, TimeoutMilliseconds: defaultSessionTimeoutMilliseconds, Actions: map[string]ActionBinding{ "session-manager": {Keys: []string{"s", "w"}, Desc: "Open session manager"}, "confirm": {Keys: []string{"enter"}, Desc: "Confirm action"}, "cancel": {Keys: []string{"escape"}, Desc: "Cancel/exit mode"}, }, }, }, Media: MediaConfig{ HardwareDecodingMode: HardwareDecodingAuto, // auto allows sw fallback PreferAV1: false, // Don't force codec preference, let site choose ShowDiagnosticsOnStartup: false, // Disabled - diagnostics can be noisy // GStreamer fields (ForceVSync, GLRenderingMode, GStreamerDebugLevel) // moved to [engine.webkit] — zero values here prevent them from being // written back when marshaling the Config struct. }, Update: UpdateConfig{ EnableOnStartup: true, // Check for updates on startup by default AutoDownload: false, // Conservative: don't auto-download by default NotifyOnNewSettings: true, // Show toast when new config settings available }, Downloads: DownloadsConfig{ Path: "", // Empty = use XDG_DOWNLOAD_DIR or ~/Downloads }, } } // GetDefaultSearchShortcuts returns the default search shortcuts. func GetDefaultSearchShortcuts() map[string]SearchShortcut { return map[string]SearchShortcut{ "ddg": { URL: "https://duckduckgo.com/?q=%s", Description: "DuckDuckGo search", }, "g": { URL: "https://www.google.com/search?q=%s", Description: "Google search", }, "gi": { URL: "https://www.google.com/search?tbm=isch&q=%s", Description: "Google Images search", }, "gh": { URL: "https://github.com/search?q=%s", Description: "GitHub search", }, "go": { URL: "https://pkg.go.dev/search?q=%s", Description: "Go package search", }, "mdn": { URL: "https://developer.mozilla.org/en-US/search?q=%s", Description: "MDN Web Docs search", }, "npm": { URL: "https://www.npmjs.com/search?q=%s", Description: "npm package search", }, "r": { URL: "https://www.reddit.com/search?q=%s", Description: "Reddit search", }, "so": { URL: "https://stackoverflow.com/search?q=%s", Description: "Stack Overflow search", }, "w": { URL: "https://en.wikipedia.org/wiki/%s", Description: "Wikipedia search", }, "yt": { URL: "https://www.youtube.com/results?search_query=%s", Description: "YouTube search", }, } }