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