1package config
2
3import (
4 "fmt"
5
6 "github.com/bnema/dumber/internal/domain/entity"
7)
8
9// Section names for grouping config keys.
10const (
11 SectionAppearance = "Appearance"
12 SectionLogging = "Logging"
13 SectionHistory = "History"
14 SectionWorkspace = "Workspace"
15 SectionSession = "Session"
16 SectionOmnibox = "Omnibox"
17 SectionClipboard = "Clipboard"
18 SectionContentFiltering = "Content Filtering"
19 SectionRendering = "Rendering"
20 SectionMedia = "Media"
21 SectionPrivacy = "Privacy"
22 SectionUpdate = "Update"
23 SectionDmenu = "Dmenu"
24 SectionDebug = "Debug"
25 SectionPerformance = "Performance"
26 SectionRuntime = "Runtime"
27 SectionDatabase = "Database"
28 SectionSearch = "Search"
29 SectionDownloads = "Downloads"
30)
31
32// SchemaProvider implements port.ConfigSchemaProvider.
33type SchemaProvider struct{}
34
35// NewSchemaProvider creates a new SchemaProvider.
36func NewSchemaProvider() *SchemaProvider {
37 return &SchemaProvider{}
38}
39
40// GetSchema returns all configuration keys with their metadata.
41func (p *SchemaProvider) GetSchema() []entity.ConfigKeyInfo {
42 defaults := DefaultConfig()
43
44 keys := make([]entity.ConfigKeyInfo, 0, 100)
45
46 // Appearance section
47 keys = append(keys, p.getAppearanceKeys(defaults)...)
48
49 // Logging section
50 keys = append(keys, p.getLoggingKeys(defaults)...)
51
52 // History section
53 keys = append(keys, p.getHistoryKeys(defaults)...)
54
55 // Search section
56 keys = append(keys, p.getSearchKeys(defaults)...)
57
58 // Dmenu section
59 keys = append(keys, p.getDmenuKeys(defaults)...)
60
61 // Workspace section
62 keys = append(keys, p.getWorkspaceKeys(defaults)...)
63
64 // Session section
65 keys = append(keys, p.getSessionKeys(defaults)...)
66
67 // Omnibox section
68 keys = append(keys, p.getOmniboxKeys(defaults)...)
69
70 // Clipboard section
71 keys = append(keys, p.getClipboardKeys(defaults)...)
72
73 // Content Filtering section
74 keys = append(keys, p.getContentFilteringKeys(defaults)...)
75
76 // Rendering section
77 keys = append(keys, p.getRenderingKeys(defaults)...)
78
79 // Media section
80 keys = append(keys, p.getMediaKeys(defaults)...)
81
82 // Privacy section
83 keys = append(keys, p.getPrivacyKeys(defaults)...)
84
85 // Update section
86 keys = append(keys, p.getUpdateKeys(defaults)...)
87
88 // Debug section
89 keys = append(keys, p.getDebugKeys(defaults)...)
90
91 // Performance section
92 keys = append(keys, p.getPerformanceKeys(defaults)...)
93
94 // Runtime section
95 keys = append(keys, p.getRuntimeKeys(defaults)...)
96
97 // Database section
98 keys = append(keys, p.getDatabaseKeys()...)
99
100 // Downloads section
101 keys = append(keys, p.getDownloadsKeys(defaults)...)
102
103 return keys
104}
105
106func (*SchemaProvider) getAppearanceKeys(defaults *Config) []entity.ConfigKeyInfo {
107 return []entity.ConfigKeyInfo{
108 {
109 Key: "appearance.color_scheme",
110 Type: "string",
111 Default: defaults.Appearance.ColorScheme,
112 Description: "Theme preference (follows system by default)",
113 Values: []string{"default", "prefer-dark", "prefer-light"},
114 Section: SectionAppearance,
115 },
116 {
117 Key: "appearance.default_font_size",
118 Type: "int",
119 Default: fmt.Sprintf("%d", defaults.Appearance.DefaultFontSize),
120 Description: "Default font size in CSS pixels",
121 Range: "1-72",
122 Section: SectionAppearance,
123 },
124 {
125 Key: "appearance.sans_font",
126 Type: "string",
127 Default: defaults.Appearance.SansFont,
128 Description: "Sans-serif font family for web pages",
129 Section: SectionAppearance,
130 },
131 {
132 Key: "appearance.serif_font",
133 Type: "string",
134 Default: defaults.Appearance.SerifFont,
135 Description: "Serif font family for web pages",
136 Section: SectionAppearance,
137 },
138 {
139 Key: "appearance.monospace_font",
140 Type: "string",
141 Default: defaults.Appearance.MonospaceFont,
142 Description: "Monospace font family for code",
143 Section: SectionAppearance,
144 },
145 {
146 Key: "appearance.gtk_font",
147 Type: "string",
148 Default: defaults.Appearance.GtkFont,
149 Description: "Native GTK UI font family for app chrome",
150 Section: SectionAppearance,
151 },
152 {
153 Key: "default_webpage_zoom",
154 Type: "float64",
155 Default: fmt.Sprintf("%.1f", defaults.DefaultWebpageZoom),
156 Description: "Default zoom level for web pages (1.0 = 100%%)",
157 Range: "0.1-5.0",
158 Section: SectionAppearance,
159 },
160 {
161 Key: "default_ui_scale",
162 Type: "float64",
163 Default: fmt.Sprintf("%.1f", defaults.DefaultUIScale),
164 Description: "UI scale multiplier for GTK widgets",
165 Range: "0.5-3.0",
166 Section: SectionAppearance,
167 },
168 {
169 Key: "sidebar_width",
170 Type: "int",
171 Default: fmt.Sprintf("%d", defaults.SidebarWidth),
172 Description: "Preferred width (px) for the history sidebar. 0 = default",
173 Range: "0 or 280-380",
174 Section: SectionAppearance,
175 },
176 {
177 Key: "appearance.light_palette.*",
178 Type: "string",
179 Default: "(hex colors)",
180 Description: "Light theme color palette (background, surface, surface_variant, text, muted, accent, border)",
181 Section: SectionAppearance,
182 },
183 {
184 Key: "appearance.dark_palette.*",
185 Type: "string",
186 Default: "(hex colors)",
187 Description: "Dark theme color palette (background, surface, surface_variant, text, muted, accent, border)",
188 Section: SectionAppearance,
189 },
190 {
191 Key: "appearance.external_theme.enabled",
192 Type: "bool",
193 Default: fmt.Sprintf("%t", defaults.Appearance.ExternalTheme.Enabled),
194 Description: "Enable loading theme settings from an external provider",
195 Section: SectionAppearance,
196 },
197 {
198 Key: "appearance.external_theme.provider",
199 Type: "string",
200 Default: defaults.Appearance.ExternalTheme.Provider,
201 Description: "External theme provider",
202 Values: []string{"noctalia"},
203 Section: SectionAppearance,
204 },
205 {
206 Key: "appearance.external_theme.format",
207 Type: "string",
208 Default: defaults.Appearance.ExternalTheme.Format,
209 Description: "External theme file format",
210 Values: []string{"colors-json", "dumber-json"},
211 Section: SectionAppearance,
212 },
213 {
214 Key: "appearance.external_theme.path",
215 Type: "string",
216 Default: defaults.Appearance.ExternalTheme.Path,
217 Description: "Path to the external theme file",
218 Section: SectionAppearance,
219 },
220 }
221}
222
223func (*SchemaProvider) getLoggingKeys(defaults *Config) []entity.ConfigKeyInfo {
224 return []entity.ConfigKeyInfo{
225 {
226 Key: "logging.level",
227 Type: "string",
228 Default: defaults.Logging.Level,
229 Description: "Log verbosity level",
230 Values: []string{"trace", "debug", "info", "warn", "error", "fatal"},
231 Section: SectionLogging,
232 },
233 {
234 Key: "logging.format",
235 Type: "string",
236 Default: defaults.Logging.Format,
237 Description: "Log output format",
238 Values: []string{"text", "json", "console"},
239 Section: SectionLogging,
240 },
241 {
242 Key: "logging.max_age",
243 Type: "int",
244 Default: fmt.Sprintf("%d", defaults.Logging.MaxAge),
245 Description: "Maximum age of log files in days",
246 Range: ">=0",
247 Section: SectionLogging,
248 },
249 {
250 Key: "logging.max_files",
251 Type: "int",
252 Default: fmt.Sprintf("%d", defaults.Logging.MaxFiles),
253 Description: "Maximum number of session log files to keep (0 disables count-based cleanup)",
254 Range: ">=0",
255 Section: SectionLogging,
256 },
257 {
258 Key: "logging.log_dir",
259 Type: "string",
260 Default: defaults.Logging.LogDir,
261 Description: "Directory for log files",
262 Section: SectionLogging,
263 },
264 {
265 Key: "logging.enable_file_log",
266 Type: "bool",
267 Default: fmt.Sprintf("%t", defaults.Logging.EnableFileLog),
268 Description: "Enable logging to file",
269 Section: SectionLogging,
270 },
271 {
272 Key: "logging.capture_console",
273 Type: "bool",
274 Default: fmt.Sprintf("%t", defaults.Logging.CaptureConsole),
275 Description: "Capture browser console messages to log",
276 Section: SectionLogging,
277 },
278 {
279 Key: "logging.capture_gtk_logs",
280 Type: "bool",
281 Default: fmt.Sprintf("%t", defaults.Logging.CaptureGTKLogs),
282 Description: "Capture GTK and GLib log messages",
283 Section: SectionLogging,
284 },
285 }
286}
287
288func (*SchemaProvider) getHistoryKeys(defaults *Config) []entity.ConfigKeyInfo {
289 return []entity.ConfigKeyInfo{
290 {
291 Key: "history.max_entries",
292 Type: "int",
293 Default: fmt.Sprintf("%d", defaults.History.MaxEntries),
294 Description: "Maximum number of history entries to keep",
295 Range: ">=0",
296 Section: SectionHistory,
297 },
298 {
299 Key: "history.retention_period_days",
300 Type: "int",
301 Default: fmt.Sprintf("%d", defaults.History.RetentionPeriodDays),
302 Description: "Number of days to retain history",
303 Range: ">=0",
304 Section: SectionHistory,
305 },
306 {
307 Key: "history.cleanup_interval_days",
308 Type: "int",
309 Default: fmt.Sprintf("%d", defaults.History.CleanupIntervalDays),
310 Description: "Days between automatic history cleanup",
311 Range: ">=0",
312 Section: SectionHistory,
313 },
314 }
315}
316
317func (*SchemaProvider) getSearchKeys(defaults *Config) []entity.ConfigKeyInfo {
318 return []entity.ConfigKeyInfo{
319 {
320 Key: "default_search_engine",
321 Type: "string",
322 Default: defaults.DefaultSearchEngine,
323 Description: "Default search engine URL (must contain %s placeholder)",
324 Section: SectionSearch,
325 },
326 {
327 Key: "search_shortcuts.<key>",
328 Type: "object",
329 Default: "(see defaults)",
330 Description: "Search shortcuts map with url and description fields",
331 Section: SectionSearch,
332 },
333 }
334}
335
336func (*SchemaProvider) getDmenuKeys(defaults *Config) []entity.ConfigKeyInfo {
337 return []entity.ConfigKeyInfo{
338 {
339 Key: "dmenu.max_history_days",
340 Type: "int",
341 Default: fmt.Sprintf("%d", defaults.Dmenu.MaxHistoryDays),
342 Description: "Number of days of history to show in dmenu (0 = all)",
343 Range: ">=0",
344 Section: SectionDmenu,
345 },
346 {
347 Key: "dmenu.show_visit_count",
348 Type: "bool",
349 Default: fmt.Sprintf("%t", defaults.Dmenu.ShowVisitCount),
350 Description: "Show visit count in dmenu entries",
351 Section: SectionDmenu,
352 },
353 {
354 Key: "dmenu.show_last_visited",
355 Type: "bool",
356 Default: fmt.Sprintf("%t", defaults.Dmenu.ShowLastVisited),
357 Description: "Show last visited date in dmenu entries",
358 Section: SectionDmenu,
359 },
360 {
361 Key: "dmenu.history_prefix",
362 Type: "string",
363 Default: defaults.Dmenu.HistoryPrefix,
364 Description: "Prefix for history entries in dmenu",
365 Section: SectionDmenu,
366 },
367 {
368 Key: "dmenu.shortcut_prefix",
369 Type: "string",
370 Default: defaults.Dmenu.ShortcutPrefix,
371 Description: "Prefix for search shortcuts in dmenu",
372 Section: SectionDmenu,
373 },
374 {
375 Key: "dmenu.url_prefix",
376 Type: "string",
377 Default: defaults.Dmenu.URLPrefix,
378 Description: "Prefix for URL entries in dmenu",
379 Section: SectionDmenu,
380 },
381 {
382 Key: "dmenu.date_format",
383 Type: "string",
384 Default: defaults.Dmenu.DateFormat,
385 Description: "Go time format for dates in dmenu",
386 Section: SectionDmenu,
387 },
388 {
389 Key: "dmenu.sort_by_visit_count",
390 Type: "bool",
391 Default: fmt.Sprintf("%t", defaults.Dmenu.SortByVisitCount),
392 Description: "Sort dmenu entries by visit count",
393 Section: SectionDmenu,
394 },
395 }
396}
397
398func (*SchemaProvider) getWorkspaceKeys(defaults *Config) []entity.ConfigKeyInfo {
399 return []entity.ConfigKeyInfo{
400 {
401 Key: "workspace.new_pane_url",
402 Type: "string",
403 Default: defaults.Workspace.NewPaneURL,
404 Description: "URL loaded when creating a new pane",
405 Section: SectionWorkspace,
406 },
407 {
408 Key: "workspace.tab_bar_position",
409 Type: "string",
410 Default: defaults.Workspace.TabBarPosition,
411 Description: "Position of the tab bar",
412 Values: []string{"top", "bottom"},
413 Section: SectionWorkspace,
414 },
415 {
416 Key: "workspace.hide_tab_bar_when_single_tab",
417 Type: "bool",
418 Default: fmt.Sprintf("%t", defaults.Workspace.HideTabBarWhenSingleTab),
419 Description: "Hide tab bar when only one tab exists",
420 Section: SectionWorkspace,
421 },
422 {
423 Key: "workspace.switch_to_tab_on_move",
424 Type: "bool",
425 Default: fmt.Sprintf("%t", defaults.Workspace.SwitchToTabOnMove),
426 Description: "Switch focus to tab when moving pane to it",
427 Section: SectionWorkspace,
428 },
429 // Pane mode
430 {
431 Key: "workspace.pane_mode.activation_shortcut",
432 Type: "string",
433 Default: defaults.Workspace.PaneMode.ActivationShortcut,
434 Description: "Shortcut to enter pane mode",
435 Section: SectionWorkspace,
436 },
437 {
438 Key: "workspace.pane_mode.timeout_ms",
439 Type: "int",
440 Default: fmt.Sprintf("%d", defaults.Workspace.PaneMode.TimeoutMilliseconds),
441 Description: "Pane mode timeout in milliseconds (0 = no timeout)",
442 Range: ">=0",
443 Section: SectionWorkspace,
444 },
445 {
446 Key: "workspace.pane_mode.actions.<action>",
447 Type: "[]string",
448 Default: "(see defaults)",
449 Description: "Key bindings for pane mode actions",
450 Section: SectionWorkspace,
451 },
452 // Tab mode
453 {
454 Key: "workspace.tab_mode.activation_shortcut",
455 Type: "string",
456 Default: defaults.Workspace.TabMode.ActivationShortcut,
457 Description: "Shortcut to enter tab mode",
458 Section: SectionWorkspace,
459 },
460 {
461 Key: "workspace.tab_mode.timeout_ms",
462 Type: "int",
463 Default: fmt.Sprintf("%d", defaults.Workspace.TabMode.TimeoutMilliseconds),
464 Description: "Tab mode timeout in milliseconds (0 = no timeout)",
465 Range: ">=0",
466 Section: SectionWorkspace,
467 },
468 {
469 Key: "workspace.tab_mode.actions.<action>",
470 Type: "[]string",
471 Default: "(see defaults)",
472 Description: "Key bindings for tab mode actions",
473 Section: SectionWorkspace,
474 },
475 // Resize mode
476 {
477 Key: "workspace.resize_mode.activation_shortcut",
478 Type: "string",
479 Default: defaults.Workspace.ResizeMode.ActivationShortcut,
480 Description: "Shortcut to enter resize mode",
481 Section: SectionWorkspace,
482 },
483 {
484 Key: "workspace.resize_mode.timeout_ms",
485 Type: "int",
486 Default: fmt.Sprintf("%d", defaults.Workspace.ResizeMode.TimeoutMilliseconds),
487 Description: "Resize mode timeout in milliseconds (0 = no timeout)",
488 Range: ">=0",
489 Section: SectionWorkspace,
490 },
491 {
492 Key: "workspace.resize_mode.step_percent",
493 Type: "float64",
494 Default: fmt.Sprintf("%.1f", defaults.Workspace.ResizeMode.StepPercent),
495 Description: "Percentage to resize panes per step",
496 Section: SectionWorkspace,
497 },
498 {
499 Key: "workspace.resize_mode.min_pane_percent",
500 Type: "float64",
501 Default: fmt.Sprintf("%.1f", defaults.Workspace.ResizeMode.MinPanePercent),
502 Description: "Minimum pane size as percentage",
503 Section: SectionWorkspace,
504 },
505 {
506 Key: "workspace.resize_mode.actions.<action>",
507 Type: "[]string",
508 Default: "(see defaults)",
509 Description: "Key bindings for resize mode actions",
510 Section: SectionWorkspace,
511 },
512 // Global shortcuts - managed via dedicated keybindings UI
513 {
514 Key: "workspace.shortcuts.actions",
515 Type: "object",
516 Default: "(see defaults)",
517 Description: "Global keyboard shortcuts (use Keybindings tab to edit)",
518 Section: SectionWorkspace,
519 },
520 // Floating pane
521 {
522 Key: "workspace.floating_pane.width_pct",
523 Type: "float64",
524 Default: fmt.Sprintf("%.2f", defaults.Workspace.FloatingPane.WidthPct),
525 Description: "Floating pane width as a fraction of workspace width",
526 Range: "(0,1]",
527 Section: SectionWorkspace,
528 },
529 {
530 Key: "workspace.floating_pane.height_pct",
531 Type: "float64",
532 Default: fmt.Sprintf("%.2f", defaults.Workspace.FloatingPane.HeightPct),
533 Description: "Floating pane height as a fraction of workspace height",
534 Range: "(0,1]",
535 Section: SectionWorkspace,
536 },
537 {
538 Key: "workspace.floating_pane.profiles.<name>",
539 Type: "object",
540 Default: "(empty)",
541 Description: "Named floating pane URL profiles with keys, url, and optional desc",
542 Section: SectionWorkspace,
543 },
544 // Browsing contexts
545 {
546 Key: "workspace.browsing_contexts.behavior",
547 Type: "string",
548 Default: string(defaults.Workspace.BrowsingContexts.Behavior),
549 Description: "How to handle JavaScript popup windows",
550 Values: []string{"split", "stacked", "tabbed", "windowed"},
551 Section: SectionWorkspace,
552 },
553 {
554 Key: "workspace.browsing_contexts.placement",
555 Type: "string",
556 Default: defaults.Workspace.BrowsingContexts.Placement,
557 Description: "Placement direction for split popups",
558 Values: []string{"right", "left", "top", "bottom"},
559 Section: SectionWorkspace,
560 },
561 {
562 Key: "workspace.browsing_contexts.blank_target_behavior",
563 Type: "string",
564 Default: defaults.Workspace.BrowsingContexts.BlankTargetBehavior,
565 Description: "How to handle target=\"_blank\" links",
566 Values: []string{"split", "stacked", "tabbed"},
567 Section: SectionWorkspace,
568 },
569 {
570 Key: "workspace.browsing_contexts.open_in_new_pane",
571 Type: "bool",
572 Default: fmt.Sprintf("%t", defaults.Workspace.BrowsingContexts.OpenInNewPane),
573 Description: "Open popups in workspace instead of blocking",
574 Section: SectionWorkspace,
575 },
576 {
577 Key: "workspace.browsing_contexts.follow_pane_context",
578 Type: "bool",
579 Default: fmt.Sprintf("%t", defaults.Workspace.BrowsingContexts.FollowPaneContext),
580 Description: "Popup placement follows parent pane context",
581 Section: SectionWorkspace,
582 },
583 {
584 Key: "workspace.browsing_contexts.enable_smart_detection",
585 Type: "bool",
586 Default: fmt.Sprintf("%t", defaults.Workspace.BrowsingContexts.EnableSmartDetection),
587 Description: "Use window properties to detect popup intent",
588 Section: SectionWorkspace,
589 },
590 {
591 Key: "workspace.browsing_contexts.oauth_auto_close",
592 Type: "bool",
593 Default: fmt.Sprintf("%t", defaults.Workspace.BrowsingContexts.OAuthAutoClose),
594 Description: "Auto-close OAuth popups after success",
595 Section: SectionWorkspace,
596 },
597 // Styling
598 {
599 Key: "workspace.styling.border_width",
600 Type: "int",
601 Default: fmt.Sprintf("%d", defaults.Workspace.Styling.BorderWidth),
602 Description: "Active pane border width in pixels",
603 Range: ">=0",
604 Section: SectionWorkspace,
605 },
606 {
607 Key: "workspace.styling.border_color",
608 Type: "string",
609 Default: defaults.Workspace.Styling.BorderColor,
610 Description: "Active pane border color (CSS or @theme variable)",
611 Section: SectionWorkspace,
612 },
613 {
614 Key: "workspace.styling.mode_border_width",
615 Type: "int",
616 Default: fmt.Sprintf("%d", defaults.Workspace.Styling.ModeBorderWidth),
617 Description: "Border width for modal mode indicators",
618 Range: ">=0",
619 Section: SectionWorkspace,
620 },
621 {
622 Key: "workspace.styling.pane_mode_color",
623 Type: "string",
624 Default: defaults.Workspace.Styling.PaneModeColor,
625 Description: "Color for pane mode indicator",
626 Section: SectionWorkspace,
627 },
628 {
629 Key: "workspace.styling.tab_mode_color",
630 Type: "string",
631 Default: defaults.Workspace.Styling.TabModeColor,
632 Description: "Color for tab mode indicator",
633 Section: SectionWorkspace,
634 },
635 {
636 Key: "workspace.styling.session_mode_color",
637 Type: "string",
638 Default: defaults.Workspace.Styling.SessionModeColor,
639 Description: "Color for session mode indicator",
640 Section: SectionWorkspace,
641 },
642 {
643 Key: "workspace.styling.resize_mode_color",
644 Type: "string",
645 Default: defaults.Workspace.Styling.ResizeModeColor,
646 Description: "Color for resize mode indicator",
647 Section: SectionWorkspace,
648 },
649 {
650 Key: "workspace.styling.mode_indicator_toaster_enabled",
651 Type: "bool",
652 Default: fmt.Sprintf("%t", defaults.Workspace.Styling.ModeIndicatorToasterEnabled),
653 Description: "Show toaster notification for modal modes",
654 Section: SectionWorkspace,
655 },
656 {
657 Key: "workspace.styling.transition_duration",
658 Type: "int",
659 Default: fmt.Sprintf("%d", defaults.Workspace.Styling.TransitionDuration),
660 Description: "Border animation duration in milliseconds",
661 Range: ">=0",
662 Section: SectionWorkspace,
663 },
664 }
665}
666
667func (*SchemaProvider) getSessionKeys(defaults *Config) []entity.ConfigKeyInfo {
668 return []entity.ConfigKeyInfo{
669 {
670 Key: "session.auto_restore",
671 Type: "bool",
672 Default: fmt.Sprintf("%t", defaults.Session.AutoRestore),
673 Description: "Automatically restore last session on startup",
674 Section: SectionSession,
675 },
676 {
677 Key: "session.snapshot_interval_ms",
678 Type: "int",
679 Default: fmt.Sprintf("%d", defaults.Session.SnapshotIntervalMs),
680 Description: "Minimum interval between session snapshots",
681 Range: ">=0",
682 Section: SectionSession,
683 },
684 {
685 Key: "session.max_exited_sessions",
686 Type: "int",
687 Default: fmt.Sprintf("%d", defaults.Session.MaxExitedSessions),
688 Description: "Maximum number of exited sessions to keep",
689 Range: ">=0",
690 Section: SectionSession,
691 },
692 {
693 Key: "session.max_exited_session_age_days",
694 Type: "int",
695 Default: fmt.Sprintf("%d", defaults.Session.MaxExitedSessionAgeDays),
696 Description: "Maximum age of exited sessions in days",
697 Range: ">=0",
698 Section: SectionSession,
699 },
700 {
701 Key: "session.session_mode.activation_shortcut",
702 Type: "string",
703 Default: defaults.Session.SessionMode.ActivationShortcut,
704 Description: "Shortcut to enter session mode",
705 Section: SectionSession,
706 },
707 {
708 Key: "session.session_mode.timeout_ms",
709 Type: "int",
710 Default: fmt.Sprintf("%d", defaults.Session.SessionMode.TimeoutMilliseconds),
711 Description: "Session mode timeout in milliseconds",
712 Range: ">=0",
713 Section: SectionSession,
714 },
715 {
716 Key: "session.session_mode.actions.<action>",
717 Type: "[]string",
718 Default: "(see defaults)",
719 Description: "Key bindings for session mode actions",
720 Section: SectionSession,
721 },
722 }
723}
724
725func (*SchemaProvider) getOmniboxKeys(defaults *Config) []entity.ConfigKeyInfo {
726 return []entity.ConfigKeyInfo{
727 {
728 Key: "omnibox.initial_behavior",
729 Type: "string",
730 Default: string(defaults.Omnibox.InitialBehavior),
731 Description: "What to show when omnibox opens with empty input",
732 Values: []string{
733 string(OmniboxInitialBehaviorRecent),
734 string(OmniboxInitialBehaviorMostVisited),
735 string(OmniboxInitialBehaviorNone),
736 },
737 Section: SectionOmnibox,
738 },
739 {
740 Key: "omnibox.most_visited_days",
741 Type: "int",
742 Default: fmt.Sprintf("%d", defaults.Omnibox.MostVisitedDays),
743 Description: "Days of history to consider for most_visited (0 = all history)",
744 Range: ">=0",
745 Section: SectionOmnibox,
746 },
747 {
748 Key: "omnibox.auto_open_on_new_pane",
749 Type: "bool",
750 Default: fmt.Sprintf("%t", defaults.Omnibox.AutoOpenOnNewPane),
751 Description: "Auto-open omnibox when creating new pane",
752 Section: SectionOmnibox,
753 },
754 }
755}
756
757func (*SchemaProvider) getContentFilteringKeys(defaults *Config) []entity.ConfigKeyInfo {
758 return []entity.ConfigKeyInfo{
759 {
760 Key: "content_filtering.enabled",
761 Type: "bool",
762 Default: fmt.Sprintf("%t", defaults.ContentFiltering.Enabled),
763 Description: "Enable ad blocking and content filtering",
764 Section: SectionContentFiltering,
765 },
766 {
767 Key: "content_filtering.auto_update",
768 Type: "bool",
769 Default: fmt.Sprintf("%t", defaults.ContentFiltering.AutoUpdate),
770 Description: "Auto-update filter lists from GitHub",
771 Section: SectionContentFiltering,
772 },
773 }
774}
775
776func (*SchemaProvider) getClipboardKeys(defaults *Config) []entity.ConfigKeyInfo {
777 return []entity.ConfigKeyInfo{
778 {
779 Key: "clipboard.auto_copy_on_selection",
780 Type: "bool",
781 Default: fmt.Sprintf("%t", defaults.Clipboard.AutoCopyOnSelection),
782 Description: "Auto-copy selected text to clipboard (zellij-style)",
783 Section: SectionClipboard,
784 },
785 }
786}
787
788func (*SchemaProvider) getRenderingKeys(defaults *Config) []entity.ConfigKeyInfo {
789 return []entity.ConfigKeyInfo{
790 {
791 Key: "engine.type",
792 Type: "string",
793 Default: defaults.Engine.Type,
794 Description: "Browser engine selection; CEF is the default and WebKitGTK is the fallback",
795 Values: []string{"cef", "webkit"},
796 Section: SectionRendering,
797 },
798 {
799 Key: "engine.webkit.gsk_renderer",
800 Type: "string",
801 Default: string(defaults.Engine.WebKit.GSKRenderer),
802 Description: "GTK Scene Kit renderer selection",
803 Values: []string{"auto", "opengl", "vulkan", "cairo"},
804 Section: SectionRendering,
805 },
806 {
807 Key: "engine.webkit.disable_dmabuf_renderer",
808 Type: "bool",
809 Default: fmt.Sprintf("%t", defaults.Engine.WebKit.DisableDMABufRenderer),
810 Description: "Disable DMA-BUF accelerated renderer",
811 Section: SectionRendering,
812 },
813 {
814 Key: "engine.webkit.force_compositing_mode",
815 Type: "bool",
816 Default: fmt.Sprintf("%t", defaults.Engine.WebKit.ForceCompositingMode),
817 Description: "Force accelerated compositing",
818 Section: SectionRendering,
819 },
820 {
821 Key: "engine.webkit.disable_compositing_mode",
822 Type: "bool",
823 Default: fmt.Sprintf("%t", defaults.Engine.WebKit.DisableCompositingMode),
824 Description: "Disable accelerated compositing",
825 Section: SectionRendering,
826 },
827 {
828 Key: "engine.webkit.disable_mipmaps",
829 Type: "bool",
830 Default: fmt.Sprintf("%t", defaults.Engine.WebKit.DisableMipmaps),
831 Description: "Disable mipmap generation",
832 Section: SectionRendering,
833 },
834 {
835 Key: "engine.webkit.prefer_gl",
836 Type: "bool",
837 Default: fmt.Sprintf("%t", defaults.Engine.WebKit.PreferGL),
838 Description: "Force OpenGL over GLES",
839 Section: SectionRendering,
840 },
841 {
842 Key: "engine.webkit.draw_compositing_indicators",
843 Type: "bool",
844 Default: fmt.Sprintf("%t", defaults.Engine.WebKit.DrawCompositingIndicators),
845 Description: "Show compositing layer borders",
846 Section: SectionRendering,
847 },
848 {
849 Key: "engine.webkit.show_fps",
850 Type: "bool",
851 Default: fmt.Sprintf("%t", defaults.Engine.WebKit.ShowFPS),
852 Description: "Display WebKit FPS counter",
853 Section: SectionRendering,
854 },
855 {
856 Key: "engine.webkit.sample_memory",
857 Type: "bool",
858 Default: fmt.Sprintf("%t", defaults.Engine.WebKit.SampleMemory),
859 Description: "Enable memory sampling",
860 Section: SectionRendering,
861 },
862 {
863 Key: "engine.webkit.debug_frames",
864 Type: "bool",
865 Default: fmt.Sprintf("%t", defaults.Engine.WebKit.DebugFrames),
866 Description: "Enable frame timing debug output",
867 Section: SectionRendering,
868 },
869 }
870}
871
872func (*SchemaProvider) getMediaKeys(defaults *Config) []entity.ConfigKeyInfo {
873 return []entity.ConfigKeyInfo{
874 {
875 Key: "media.hardware_decoding",
876 Type: "string",
877 Default: string(defaults.Media.HardwareDecodingMode),
878 Description: "Hardware vs software video decoding",
879 Values: []string{"auto", "force", "disable"},
880 Section: SectionMedia,
881 },
882 {
883 Key: "media.prefer_av1",
884 Type: "bool",
885 Default: fmt.Sprintf("%t", defaults.Media.PreferAV1),
886 Description: "Prioritize AV1 codec when available",
887 Section: SectionMedia,
888 },
889 {
890 Key: "media.show_diagnostics",
891 Type: "bool",
892 Default: fmt.Sprintf("%t", defaults.Media.ShowDiagnosticsOnStartup),
893 Description: "Show media capability warnings at startup",
894 Section: SectionMedia,
895 },
896 {
897 Key: "engine.webkit.force_vsync",
898 Type: "bool",
899 Default: fmt.Sprintf("%t", defaults.Engine.WebKit.ForceVSync),
900 Description: "Force VSync for WebKit fallback video playback",
901 Section: SectionMedia,
902 },
903 {
904 Key: "engine.webkit.gl_rendering_mode",
905 Type: "string",
906 Default: string(defaults.Engine.WebKit.GLRenderingMode),
907 Description: "WebKit fallback GStreamer/OpenGL API selection",
908 Values: []string{"auto", "gles2", "gl3", "none"},
909 Section: SectionMedia,
910 },
911 {
912 Key: "engine.webkit.gstreamer_debug_level",
913 Type: "int",
914 Default: fmt.Sprintf("%d", defaults.Engine.WebKit.GStreamerDebugLevel),
915 Description: "WebKit fallback GStreamer debug verbosity",
916 Range: "0-5",
917 Section: SectionMedia,
918 },
919 }
920}
921
922func (*SchemaProvider) getPrivacyKeys(defaults *Config) []entity.ConfigKeyInfo {
923 return []entity.ConfigKeyInfo{
924 {
925 Key: "engine.cookie_policy",
926 Type: "string",
927 Default: string(defaults.Engine.CookiePolicy),
928 Description: "Cookie acceptance policy",
929 Values: []string{"always", "no_third_party", "never"},
930 Section: SectionPrivacy,
931 },
932 {
933 Key: "engine.webkit.itp_enabled",
934 Type: "bool",
935 Default: fmt.Sprintf("%t", defaults.Engine.WebKit.ITPEnabled),
936 Description: "Enable WebKit fallback Intelligent Tracking Prevention",
937 Section: SectionPrivacy,
938 },
939 }
940}
941
942func (*SchemaProvider) getUpdateKeys(defaults *Config) []entity.ConfigKeyInfo {
943 return []entity.ConfigKeyInfo{
944 {
945 Key: "update.enable_on_startup",
946 Type: "bool",
947 Default: fmt.Sprintf("%t", defaults.Update.EnableOnStartup),
948 Description: "Check for updates on browser startup",
949 Section: SectionUpdate,
950 },
951 {
952 Key: "update.auto_download",
953 Type: "bool",
954 Default: fmt.Sprintf("%t", defaults.Update.AutoDownload),
955 Description: "Auto-download updates in background",
956 Section: SectionUpdate,
957 },
958 {
959 Key: "update.notify_on_new_settings",
960 Type: "bool",
961 Default: fmt.Sprintf("%t", defaults.Update.NotifyOnNewSettings),
962 Description: "Show toast when new config settings available",
963 Section: SectionUpdate,
964 },
965 }
966}
967
968func (*SchemaProvider) getDebugKeys(defaults *Config) []entity.ConfigKeyInfo {
969 return []entity.ConfigKeyInfo{
970 {
971 Key: "debug.enable_devtools",
972 Type: "bool",
973 Default: fmt.Sprintf("%t", defaults.Debug.EnableDevTools),
974 Description: "Enable browser developer tools (F12)",
975 Section: SectionDebug,
976 },
977 {
978 Key: "engine.cef.log_file",
979 Type: "string",
980 Default: defaults.Engine.CEF.LogFile,
981 Description: "Path to the CEF runtime log file written during startup and browser process execution",
982 Section: SectionDebug,
983 },
984 {
985 Key: "engine.cef.log_severity",
986 Type: "int32",
987 Default: fmt.Sprintf("%d", defaults.Engine.CEF.LogSeverity),
988 Description: "CEF internal log verbosity (0=default, 1=verbose, 2=info, 3=warning, 4=error, 99=disable)",
989 Section: SectionDebug,
990 },
991 {
992 Key: "engine.cef.trace_handlers",
993 Type: "bool",
994 Default: fmt.Sprintf("%t", defaults.Engine.CEF.TraceHandlers),
995 Description: "Trace purego-cef handler wrapping and refcount activity",
996 Section: SectionDebug,
997 },
998 {
999 Key: "engine.cef.enable_audio_handler",
1000 Type: "bool",
1001 Default: fmt.Sprintf("%t", defaults.Engine.CEF.EnableAudioHandler),
1002 Description: "Enable the experimental CEF audio handler bridge",
1003 Section: SectionDebug,
1004 },
1005 }
1006}
1007
1008func (*SchemaProvider) getPerformanceKeys(defaults *Config) []entity.ConfigKeyInfo {
1009 return []entity.ConfigKeyInfo{
1010 {
1011 Key: "engine.profile",
1012 Type: "string",
1013 Default: string(defaults.Engine.Profile),
1014 Description: "Performance profile preset (custom enables manual tuning)",
1015 Values: []string{"default", "lite", "balanced", "max", "custom"},
1016 Section: SectionPerformance,
1017 },
1018 {
1019 Key: "engine.zoom_cache_size",
1020 Type: "int",
1021 Default: fmt.Sprintf("%d", defaults.Engine.ZoomCacheSize),
1022 Description: "Number of domain zoom levels to cache",
1023 Range: ">=0",
1024 Section: SectionPerformance,
1025 },
1026 {
1027 Key: "engine.pool_prewarm_count",
1028 Type: "int",
1029 Default: fmt.Sprintf("%d", defaults.Engine.PoolPrewarmCount),
1030 Description: "WebViews to pre-create at startup",
1031 Range: ">=0",
1032 Section: SectionPerformance,
1033 },
1034 {
1035 Key: "engine.cef.render_stack",
1036 Type: "string",
1037 Default: string(defaults.Engine.CEF.CEFRenderStack()),
1038 Description: "CEF GPU render stack",
1039 Values: []string{"vulkan", "egl"},
1040 Section: SectionPerformance,
1041 },
1042 {
1043 Key: "engine.cef.adaptive_windowless_frame_rate",
1044 Type: "bool",
1045 Default: fmt.Sprintf("%t", defaults.Engine.CEF.AdaptiveWindowlessFrameRate),
1046 Description: "Adapt CEF OSR frame rate to the active Wayland monitor refresh rate when no explicit windowless_frame_rate is set",
1047 Section: SectionPerformance,
1048 },
1049 {
1050 Key: "engine.cef.windowless_frame_rate",
1051 Type: "int32",
1052 Default: fmt.Sprintf("%d", defaults.Engine.CEF.WindowlessFrameRate),
1053 Description: "Explicit maximum off-screen frame rate for the CEF engine; 0 enables adaptive mode when configured",
1054 Range: ">=0",
1055 Section: SectionPerformance,
1056 },
1057 {
1058 Key: "engine.cef.windowless_frame_rate_max",
1059 Type: "int32",
1060 Default: fmt.Sprintf("%d", defaults.Engine.CEF.CEFWindowlessFrameRateMax()),
1061 Description: "Hard cap for adaptive CEF OSR frame rate; 0 uses the built-in default cap",
1062 Range: ">=0",
1063 Section: SectionPerformance,
1064 },
1065 {
1066 Key: "engine.cef.input.scroll_wheel_multiplier",
1067 Type: "float64",
1068 Default: fmt.Sprintf("%.2f", defaults.Engine.CEF.Input.ScrollWheelMultiplier),
1069 Description: "CEF mouse wheel scroll delta multiplier",
1070 Range: ">0",
1071 Section: SectionPerformance,
1072 },
1073 {
1074 Key: "engine.cef.input.scroll_precise_multiplier",
1075 Type: "float64",
1076 Default: fmt.Sprintf("%.2f", defaults.Engine.CEF.Input.ScrollPreciseMultiplier),
1077 Description: "CEF precise/surface scroll delta multiplier for touchpads and high-resolution wheels",
1078 Range: ">0",
1079 Section: SectionPerformance,
1080 },
1081 {
1082 Key: "engine.cef.input.scroll_horizontal_multiplier",
1083 Type: "float64",
1084 Default: fmt.Sprintf("%.2f", defaults.Engine.CEF.Input.ScrollHorizontalMultiplier),
1085 Description: "CEF horizontal scroll delta multiplier",
1086 Range: ">0",
1087 Section: SectionPerformance,
1088 },
1089 {
1090 Key: "engine.cef.input.scroll_vertical_multiplier",
1091 Type: "float64",
1092 Default: fmt.Sprintf("%.2f", defaults.Engine.CEF.Input.ScrollVerticalMultiplier),
1093 Description: "CEF vertical scroll delta multiplier; touchpad vertical speed uses this together with scroll_precise_multiplier",
1094 Range: ">0",
1095 Section: SectionPerformance,
1096 },
1097 {
1098 Key: "engine.cef.input.scroll_max_delta",
1099 Type: "int32",
1100 Default: fmt.Sprintf("%d", defaults.Engine.CEF.Input.ScrollMaxDelta),
1101 Description: "CEF maximum absolute scroll delta after scaling; 0 disables clamping",
1102 Range: ">=0",
1103 Section: SectionPerformance,
1104 },
1105 {
1106 Key: "engine.cef.input.touchpad_navigation_enabled",
1107 Type: "bool",
1108 Default: fmt.Sprintf("%t", defaults.Engine.CEF.Input.TouchpadNavigationEnabled),
1109 Description: "Enable deliberate two-finger touchpad swipe back/forward navigation for CEF views",
1110 Section: SectionPerformance,
1111 },
1112 {
1113 Key: "engine.cef.input.touchpad_navigation_min_delta",
1114 Type: "float64",
1115 Default: fmt.Sprintf("%.2f", defaults.Engine.CEF.Input.TouchpadNavigationMinDelta),
1116 Description: "Minimum accumulated horizontal touchpad swipe delta required before release triggers CEF back/forward navigation",
1117 Range: ">0",
1118 Section: SectionPerformance,
1119 },
1120 {
1121 Key: "engine.cef.input.touchpad_navigation_max_vertical_ratio",
1122 Type: "float64",
1123 Default: fmt.Sprintf("%.2f", defaults.Engine.CEF.Input.TouchpadNavigationMaxVerticalRatio),
1124 Description: "Maximum vertical-to-horizontal delta ratio allowed for CEF touchpad back/forward swipes; " +
1125 "does not affect vertical scroll speed",
1126 Range: ">0",
1127 Section: SectionPerformance,
1128 },
1129 {
1130 Key: "engine.webkit.skia_cpu_painting_threads",
1131 Type: "int",
1132 Default: fmt.Sprintf("%d", defaults.Engine.WebKit.SkiaCPUPaintingThreads),
1133 Description: "Skia CPU rendering threads (0=unset, custom profile only)",
1134 Range: ">=0",
1135 Section: SectionPerformance,
1136 },
1137 {
1138 Key: "engine.webkit.skia_gpu_painting_threads",
1139 Type: "int",
1140 Default: fmt.Sprintf("%d", defaults.Engine.WebKit.SkiaGPUPaintingThreads),
1141 Description: "Skia GPU rendering threads (-1=unset, 0=disable, custom profile only)",
1142 Range: ">=-1",
1143 Section: SectionPerformance,
1144 },
1145 {
1146 Key: "engine.webkit.skia_enable_cpu_rendering",
1147 Type: "bool",
1148 Default: fmt.Sprintf("%t", defaults.Engine.WebKit.SkiaEnableCPURendering),
1149 Description: "Force CPU rendering (custom profile only)",
1150 Section: SectionPerformance,
1151 },
1152 {
1153 Key: "engine.webkit.web_process_memory_limit_mb",
1154 Type: "int",
1155 Default: fmt.Sprintf("%d", defaults.Engine.WebKit.WebProcessMemoryLimitMB),
1156 Description: "Web process memory limit in MB (0=unset, custom profile only)",
1157 Range: ">=0",
1158 Section: SectionPerformance,
1159 },
1160 {
1161 Key: "engine.webkit.web_process_memory_poll_interval_sec",
1162 Type: "float64",
1163 Default: fmt.Sprintf("%.1f", defaults.Engine.WebKit.WebProcessMemoryPollIntervalSec),
1164 Description: "Memory check interval in seconds (0=default 30s, custom profile only)",
1165 Range: ">=0",
1166 Section: SectionPerformance,
1167 },
1168 {
1169 Key: "engine.webkit.web_process_memory_conservative_threshold",
1170 Type: "float64",
1171 Default: fmt.Sprintf("%.2f", defaults.Engine.WebKit.WebProcessMemoryConservativeThreshold),
1172 Description: "Conservative memory cleanup threshold (0-1, custom profile only)",
1173 Range: "0-1",
1174 Section: SectionPerformance,
1175 },
1176 {
1177 Key: "engine.webkit.web_process_memory_strict_threshold",
1178 Type: "float64",
1179 Default: fmt.Sprintf("%.2f", defaults.Engine.WebKit.WebProcessMemoryStrictThreshold),
1180 Description: "Strict memory cleanup threshold (0-1, custom profile only)",
1181 Range: "0-1",
1182 Section: SectionPerformance,
1183 },
1184 {
1185 Key: "engine.webkit.network_process_memory_limit_mb",
1186 Type: "int",
1187 Default: fmt.Sprintf("%d", defaults.Engine.WebKit.NetworkProcessMemoryLimitMB),
1188 Description: "Network process memory limit in MB (0=unset, custom profile only)",
1189 Range: ">=0",
1190 Section: SectionPerformance,
1191 },
1192 {
1193 Key: "engine.webkit.network_process_memory_poll_interval_sec",
1194 Type: "float64",
1195 Default: fmt.Sprintf("%.1f", defaults.Engine.WebKit.NetworkProcessMemoryPollIntervalSec),
1196 Description: "Network memory check interval in seconds (custom profile only)",
1197 Range: ">=0",
1198 Section: SectionPerformance,
1199 },
1200 {
1201 Key: "engine.webkit.network_process_memory_conservative_threshold",
1202 Type: "float64",
1203 Default: fmt.Sprintf("%.2f", defaults.Engine.WebKit.NetworkProcessMemoryConservativeThreshold),
1204 Description: "Network conservative memory threshold (0-1, custom profile only)",
1205 Range: "0-1",
1206 Section: SectionPerformance,
1207 },
1208 {
1209 Key: "engine.webkit.network_process_memory_strict_threshold",
1210 Type: "float64",
1211 Default: fmt.Sprintf("%.2f", defaults.Engine.WebKit.NetworkProcessMemoryStrictThreshold),
1212 Description: "Network strict memory threshold (0-1, custom profile only)",
1213 Range: "0-1",
1214 Section: SectionPerformance,
1215 },
1216 }
1217}
1218
1219func (*SchemaProvider) getRuntimeKeys(defaults *Config) []entity.ConfigKeyInfo {
1220 return []entity.ConfigKeyInfo{
1221 {
1222 Key: "engine.cef.cef_dir",
1223 Type: "string",
1224 Default: defaults.Engine.CEF.CEFDir,
1225 Description: "Optional CEF runtime directory containing libcef.so and Resources; overrides the default " +
1226 "runtime discovery order (CEF_DIR env, /usr/lib/cef, ~/.local/share/cef)",
1227 Section: SectionRuntime,
1228 },
1229 {
1230 Key: "engine.webkit.prefix",
1231 Type: "string",
1232 Default: defaults.Engine.WebKit.Prefix,
1233 Description: "Custom runtime prefix for WebKitGTK/GTK",
1234 Section: SectionRuntime,
1235 },
1236 }
1237}
1238
1239func (*SchemaProvider) getDatabaseKeys() []entity.ConfigKeyInfo {
1240 // Get actual default path for display
1241 dbPath := "$XDG_DATA_HOME/" + appName + "/" + databaseName
1242 if path, err := GetDatabaseFile(); err == nil {
1243 dbPath = path
1244 }
1245
1246 return []entity.ConfigKeyInfo{
1247 {
1248 Key: "database.path",
1249 Type: "string",
1250 Default: dbPath,
1251 Description: "Path to SQLite database file",
1252 Section: SectionDatabase,
1253 },
1254 }
1255}
1256
1257func (*SchemaProvider) getDownloadsKeys(_ *Config) []entity.ConfigKeyInfo {
1258 return []entity.ConfigKeyInfo{
1259 {
1260 Key: "downloads.path",
1261 Type: "string",
1262 Default: "(empty = $XDG_DOWNLOAD_DIR or ~/Downloads)",
1263 Description: "Directory where downloads are saved",
1264 Section: SectionDownloads,
1265 },
1266 }
1267}