# =============================================================================
# Qutebrowser Configuration
# Author: Kris Yotam <krisyotam@protonmail.com>
# Based on: DistroTube's config.py
# Date: 2026-02-10
# =============================================================================
# Load settings from autoconfig.yml (disabled)
config.load_autoconfig(False)
# Aliases for commands
c.aliases = {'q': 'quit', 'w': 'session-save', 'wq': 'quit --save'}
# YouTube minimal CSS is injected via Greasemonkey script
# See: ~/.local/share/qutebrowser/greasemonkey/youtube-minimal.user.js
# Cookie and session persistence
c.content.cookies.accept = 'all'
c.content.cookies.store = True
c.auto_save.session = True
# Disable page/resource cache (keeps cookies and logins)
c.content.cache.size = 0
# Chromium flags to disable aggressive caching (fixes stale HTML during dev)
c.qt.args = ['disable-http-cache']
# Cookie settings for devtools
config.set('content.cookies.accept', 'all', 'chrome-devtools://*')
config.set('content.cookies.accept', 'all', 'devtools://*')
# User agent overrides for specific sites
config.set('content.headers.user_agent', 'Mozilla/5.0 ({os_info}) AppleWebKit/{webkit_version} (KHTML, like Gecko) {upstream_browser_key}/{upstream_browser_version} Safari/{webkit_version}', 'https://web.whatsapp.com/')
config.set('content.headers.user_agent', 'Mozilla/5.0 ({os_info}; rv:71.0) Gecko/20100101 Firefox/71.0', 'https://accounts.google.com/*')
config.set('content.headers.user_agent', 'Mozilla/5.0 ({os_info}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99 Safari/537.36', 'https://*.slack.com/*')
config.set('content.headers.user_agent', 'Mozilla/5.0 ({os_info}; rv:71.0) Gecko/20100101 Firefox/71.0', 'https://docs.google.com/*')
config.set('content.headers.user_agent', 'Mozilla/5.0 ({os_info}; rv:71.0) Gecko/20100101 Firefox/71.0', 'https://drive.google.com/*')
config.set('content.headers.user_agent', 'Mozilla/5.0 ({os_info}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.208 Safari/537.36', 'https://*.openai.com/*')
config.set('content.headers.user_agent', 'Mozilla/5.0 ({os_info}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.208 Safari/537.36', 'https://chatgpt.com/*')
config.set('content.headers.user_agent', 'Mozilla/5.0 ({os_info}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.208 Safari/537.36', 'https://auth0.openai.com/*')
config.set('content.headers.user_agent', 'Mozilla/5.0 ({os_info}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.6998.208 Safari/537.36', 'https://auth.openai.com/*')
# OpenAI login requires third-party cookies (auth0 cross-domain) and DRM for some features
config.set('content.cookies.accept', 'all', 'https://*.openai.com/*')
config.set('content.cookies.accept', 'all', 'https://auth.openai.com/*')
config.set('content.cookies.accept', 'all', 'https://auth0.openai.com/*')
config.set('content.cookies.accept', 'all', 'https://chatgpt.com/*')
config.set('content.tls.certificate_errors', 'ask-block-thirdparty', 'https://*.openai.com/*')
# Load images in devtools
config.set('content.images', True, 'chrome-devtools://*')
config.set('content.images', True, 'devtools://*')
# Enable JavaScript for devtools and internal pages
config.set('content.javascript.enabled', True, 'chrome-devtools://*')
config.set('content.javascript.enabled', True, 'devtools://*')
config.set('content.javascript.enabled', True, 'chrome://*/*')
config.set('content.javascript.enabled', True, 'qute://*/*')
# Notifications
config.set('content.notifications.enabled', True, 'https://www.reddit.com')
config.set('content.notifications.enabled', True, 'https://www.youtube.com')
# Stay in insert mode until Esc is pressed
c.input.insert_mode.auto_leave = False
# Downloads directory
c.downloads.location.directory = '~/dl'
# Render PDFs in-browser instead of downloading
c.content.pdfjs = True
# Tab bar visibility
c.tabs.show = 'always'
# Start page and default page (blank)
c.url.default_page = 'about:blank'
c.url.start_pages = 'about:blank'
# Search engines (Kagi as default)
c.url.searchengines = {
'DEFAULT': 'https://kagi.com/search?q={}',
'k': 'https://kagi.com/search?q={}',
'aa': 'https://annas-archive.org/search?q={}',
'am': 'https://www.amazon.com/s?k={}',
'aw': 'https://wiki.archlinux.org/?search={}',
'ddg': 'https://duckduckgo.com/?q={}',
'goog': 'https://www.google.com/search?q={}',
'gh': 'https://github.com/search?o=desc&q={}&s=stars',
're': 'https://www.reddit.com/r/{}',
'ub': 'https://www.urbandictionary.com/define.php?term={}',
'wiki': 'https://en.wikipedia.org/wiki/{}',
'yt': 'https://www.youtube.com/results?search_query={}'
}
# =============================================================================
# Colors - Plan 9 (acme-inspired)
# =============================================================================
# Palette (from Zed Plan 9 theme)
_p9 = {
'bg': '#ffffea',
'fg': '#000000',
'grey': '#bdc1bb',
'dim_grey': '#666666',
'dark_grey': '#444444',
'panel': '#eaffff',
'surface': '#ffffca',
'yellow_bg': '#eeee9e',
'border': '#99994c',
'blue': '#000099',
'link_blue': '#005dbb',
'bright_blue':'#4993dd',
'cyan': '#008888',
'dim_cyan': '#55aaaa',
'cyan_bg': '#9eeeee',
'green': '#448844',
'green_bg': '#aaffaa',
'red': '#aa0000',
'red_bg': '#ffaaaa',
'magenta': '#8888cc',
'yellow': '#99994c',
}
# Webpage colors - do NOT override; let sites render natively
c.colors.webpage.bg = 'white'
c.colors.webpage.darkmode.enabled = False
c.colors.webpage.preferred_color_scheme = 'auto'
# Ensure no user stylesheets paint external sites
c.content.user_stylesheets = []
# Web fonts
c.fonts.web.family.standard = ''
c.fonts.web.family.serif = ''
c.fonts.web.family.sans_serif = ''
c.fonts.web.family.fixed = ''
c.fonts.web.size.default = 16
c.fonts.web.size.default_fixed = 13
c.fonts.web.size.minimum = 0
# Completion widget (multi-colored menu text)
c.colors.completion.fg = _p9['fg']
c.colors.completion.odd.bg = _p9['bg']
c.colors.completion.even.bg = _p9['surface']
c.colors.completion.category.fg = _p9['green']
c.colors.completion.category.bg = _p9['panel']
c.colors.completion.category.border.top = _p9['cyan']
c.colors.completion.category.border.bottom = _p9['cyan']
c.colors.completion.item.selected.fg = _p9['fg']
c.colors.completion.item.selected.bg = _p9['panel']
c.colors.completion.item.selected.match.fg = _p9['red']
c.colors.completion.match.fg = _p9['green']
c.colors.completion.scrollbar.fg = _p9['dark_grey']
c.colors.completion.scrollbar.bg = _p9['panel']
# Downloads
c.colors.downloads.bar.bg = _p9['panel']
c.colors.downloads.start.bg = _p9['cyan']
c.colors.downloads.start.fg = _p9['bg']
c.colors.downloads.stop.bg = _p9['green']
c.colors.downloads.stop.fg = _p9['bg']
c.colors.downloads.error.bg = _p9['red']
c.colors.downloads.error.fg = _p9['bg']
# Hints
c.colors.hints.fg = _p9['fg']
c.colors.hints.bg = _p9['yellow_bg']
c.colors.hints.match.fg = _p9['red']
# Messages
c.colors.messages.info.bg = _p9['panel']
c.colors.messages.info.fg = _p9['fg']
c.colors.messages.info.border = _p9['cyan']
c.colors.messages.warning.bg = _p9['yellow_bg']
c.colors.messages.warning.fg = _p9['fg']
c.colors.messages.warning.border = _p9['yellow']
c.colors.messages.error.bg = _p9['red_bg']
c.colors.messages.error.fg = _p9['red']
c.colors.messages.error.border = _p9['red']
# Prompts
c.colors.prompts.bg = _p9['panel']
c.colors.prompts.fg = _p9['fg']
c.colors.prompts.border = _p9['cyan']
c.colors.prompts.selected.bg = _p9['cyan_bg']
c.colors.prompts.selected.fg = _p9['fg']
# Statusbar
c.colors.statusbar.normal.bg = _p9['panel']
c.colors.statusbar.normal.fg = _p9['fg']
c.colors.statusbar.insert.fg = _p9['fg']
c.colors.statusbar.insert.bg = _p9['green_bg']
c.colors.statusbar.passthrough.bg = _p9['cyan_bg']
c.colors.statusbar.passthrough.fg = _p9['fg']
c.colors.statusbar.command.bg = _p9['panel']
c.colors.statusbar.command.fg = _p9['fg']
c.colors.statusbar.caret.bg = _p9['magenta']
c.colors.statusbar.caret.fg = _p9['bg']
c.colors.statusbar.caret.selection.bg = _p9['magenta']
c.colors.statusbar.caret.selection.fg = _p9['bg']
c.colors.statusbar.url.fg = _p9['fg']
c.colors.statusbar.url.success.http.fg = _p9['green']
c.colors.statusbar.url.success.https.fg = _p9['green']
c.colors.statusbar.url.hover.fg = _p9['link_blue']
c.colors.statusbar.url.warn.fg = _p9['yellow']
c.colors.statusbar.url.error.fg = _p9['red']
# Tabs: inactive = grey, active = cyan_bg (same blue highlight as headers)
c.colors.tabs.bar.bg = _p9['panel']
c.colors.tabs.odd.bg = _p9['grey']
c.colors.tabs.odd.fg = _p9['dark_grey']
c.colors.tabs.even.bg = _p9['grey']
c.colors.tabs.even.fg = _p9['dark_grey']
c.colors.tabs.selected.odd.bg = _p9['panel']
c.colors.tabs.selected.odd.fg = _p9['fg']
c.colors.tabs.selected.even.bg = _p9['panel']
c.colors.tabs.selected.even.fg = _p9['fg']
c.colors.tabs.pinned.odd.bg = _p9['grey']
c.colors.tabs.pinned.odd.fg = _p9['dark_grey']
c.colors.tabs.pinned.even.bg = _p9['grey']
c.colors.tabs.pinned.even.fg = _p9['dark_grey']
c.colors.tabs.pinned.selected.odd.bg = _p9['panel']
c.colors.tabs.pinned.selected.odd.fg = _p9['fg']
c.colors.tabs.pinned.selected.even.bg = _p9['panel']
c.colors.tabs.pinned.selected.even.fg = _p9['fg']
c.colors.tabs.indicator.start = _p9['cyan']
c.colors.tabs.indicator.stop = _p9['green']
c.colors.tabs.indicator.error = _p9['red']
# =============================================================================
# Aliases
# =============================================================================
c.aliases.update({
'readability': 'spawn --userscript readability-js',
'mpv': 'spawn --userscript view_in_mpv',
'feeds': 'spawn --userscript openfeeds',
'pass': 'spawn --userscript qute-pass',
'json': 'spawn --userscript format_json',
'rss': 'spawn --userscript rss',
'zotero': 'spawn --userscript qute-zotero',
})
# Node.js module path for readability-js
c.qt.environ = {"NODE_PATH": "/usr/lib/node_modules"}
# =============================================================================
# Keybindings
# =============================================================================
# dmenu integration (commented out for consideration)
# config.bind('o', 'spawn --userscript dmenu-open')
# config.bind('O', 'spawn --userscript dmenu-open --tab')
# Normal mode bindings
config.unbind('d')
config.bind('c', 'tab-close')
config.bind('b', 'back')
config.bind('f', 'forward')
config.bind('h', 'hint')
config.bind('M', 'hint links spawn mpv {hint-url}')
config.bind('Z', 'hint links spawn st -e youtube-dl {hint-url}')
config.bind('t', 'set-cmd-text -s :open -t')
config.bind('xb', 'config-cycle statusbar.show always never')
config.bind('xt', 'config-cycle tabs.show always never')
config.bind('xx', 'config-cycle statusbar.show always never;; config-cycle tabs.show always never')
# Userscript bindings
config.bind(',r', 'spawn --userscript readability-js')
config.bind(',m', 'spawn --userscript view_in_mpv')
config.bind(',f', 'spawn --userscript openfeeds')
config.bind(',p', 'spawn --userscript qute-pass')
config.bind(',j', 'spawn --userscript format_json')
config.bind(',z', 'spawn --userscript qute-zotero')
config.bind(',Z', 'hint links spawn --userscript qute-zotero')
# CSS stylesheet cycling (Solarized Everything CSS)
# https://github.com/alphapapa/solarized-everything-css
config.bind(',ap', 'config-cycle content.user_stylesheets ~/.config/qutebrowser/solarized-everything-css/css/apprentice/apprentice-all-sites.css ""')
config.bind(',dr', 'config-cycle content.user_stylesheets ~/.config/qutebrowser/solarized-everything-css/css/darculized/darculized-all-sites.css ""')
config.bind(',gr', 'config-cycle content.user_stylesheets ~/.config/qutebrowser/solarized-everything-css/css/gruvbox/gruvbox-all-sites.css ""')
config.bind(',sd', 'config-cycle content.user_stylesheets ~/.config/qutebrowser/solarized-everything-css/css/solarized-dark/solarized-dark-all-sites.css ""')
config.bind(',sl', 'config-cycle content.user_stylesheets ~/.config/qutebrowser/solarized-everything-css/css/solarized-light/solarized-light-all-sites.css ""')
# Dark mode toggle
config.bind(',dm', 'config-cycle colors.webpage.darkmode.enabled true false')