{ lib, pkgs, cfg, }: let millisecond = 1; second = 1000 * millisecond; wordlist = pkgs.runCommandNoCCLocal "wordlist" { nativeBuildInputs = [pkgs.python3]; } '' python ${ pkgs.writeText "normalize-words" # python '' import string import os # Perform qutebrowser's normalizations, to reduce our dependency closure. # See for them. with open("${pkgs.scowl}/share/dict/words.txt", encoding = "latin1") as wordfile: alphabet = set(string.ascii_lowercase) hints = set() lines = (line.rstrip().lower() for line in wordfile) for word in lines: if len(word) > 4: # we don't need words longer than 4 continue if set(word) - alphabet: # contains none-alphabetic chars continue for i in range(len(word)): # remove all prefixes of this word hints.discard(word[:i + 1]) hints.add(word) with open(os.environ["out"], "w") as out: for word in hints: out.write(word) out.write("\n") '' } ''; in { aliases = { q = "close"; qa = "quit"; w = "session-save"; wq = "quit --save"; wqa = "quit --save"; read = "spawn --userscript readability"; }; auto_save = { interval = 10 * second; session = true; # Safe/restore the session }; backend = "webengine"; bindings = { default = {}; # Disable the default key bindings commands = import ./keybindings.nix {inherit cfg lib;}; key_mappings = { "" = ""; "" = ""; "" = ""; "" = ""; "" = ""; "" = ""; "" = ""; "" = ""; "" = ""; }; }; changelog_after_upgrade = "minor"; fonts = {}; colors = { # TODO(@bpeetz): Use a nord color scheme (or stylix). <2025-05-31> # Make things readable. messages.error.fg = "black"; completion.match.fg = "black"; webpage = { darkmode = { enabled = true; algorithm = "lightness-cielab"; policy = { images = "smart"; page = "smart"; }; threshold = { background = 0; # 0 -> always foreground = 256; # 256 -> always }; }; preferred_color_scheme = "auto"; }; }; completion = { cmd_history_max_items = -1; # No limit. delay = 0; # delay before updating completions in milliseconds. favorite_paths = []; height = "50%"; min_chars = 1; open_categories = ["searchengines" "quickmarks" "bookmarks" "history" "filesystem"]; quick = true; # Select, when only one left. scrollbar = { padding = 0; width = 0; }; show = "auto"; # Only show, when selected shrink = true; use_best_match = false; web_history = { exclude = []; max_items = -1; # unlimited. }; }; confirm_quit = [ "downloads" ]; content = { autoplay = false; blocking = { enabled = true; # Sources for brave's adblocker adblock = { lists = [ "https://easylist.to/easylist/easylist.txt" "https://easylist.to/easylist/easyprivacy.txt" ]; }; # Sources for the (simpler) /etc/hosts based one. hosts = { block_subdomains = true; lists = []; }; method = "adblock"; # Only brave's whitelist = []; }; # Allow websites to read canvas elements. canvas_reading = true; cookies = { store = true; accept = "no-3rdparty"; }; default_encoding = "utf-8"; dns_prefetch = true; fullscreen = { # Fullscreen is limited to the browser window. window = true; overlay_timeout = 3 * second; }; headers = { accept_language = "en-CA,sv,en;q=0.9"; custom = {}; do_not_track = true; referer = "same-domain"; user_agent = "Mozilla/5.0 ({os_info}) AppleWebKit/{webkit_version} (KHTML, like Gecko) {upstream_browser_key}/{upstream_browser_version_short} Safari/{webkit_version}"; }; hyperlink_auditing = false; # Auto-load images. images = true; javascript = { enabled = true; can_open_tabs_automatically = false; alert = true; clipboard = "ask"; legacy_touch_events = "never"; log = { error = "debug"; warning = "debug"; info = "debug"; unknown = "debug"; }; modal_dialog = false; prompt = true; }; local_content_can_access_file_urls = true; local_content_can_access_remote_urls = false; local_storage = true; desktop_capture = "ask"; geolocation = "ask"; persistent_storage = "ask"; mouse_lock = "ask"; register_protocol_handler = "ask"; media = { audio_capture = "ask"; audio_video_capture = "ask"; video_capture = "ask"; }; mute = false; notifications = { # TODO(@bpeetz): I might change that. <2025-05-31> enabled = true; presenter = "libnotify"; show_origin = true; }; pdfjs = false; # TODO(@bpeetz): What are “plugins in Web pages”? <2025-05-31> plugins = false; prefers_reduced_motion = false; print_element_backgrounds = true; private_browsing = false; proxy = "system"; site_specific_quirks = { enabled = true; skip = []; }; tls = { certificate_errors = "ask"; }; unknown_url_scheme_policy = "allow-from-user-interaction"; user_stylesheets = []; webgl = true; webrtc_ip_handling_policy = "all-interfaces"; xss_auditing = false; }; downloads = { location = { # Use OS default. directory = null; prompt = true; remember = true; suggestion = "path"; }; # TODO(@bpeetz): I might want to set that. <2025-05-31> open_dispatcher = null; position = "top"; prevent_mixed_content = true; # Never remove downloads without my intervention. remove_finished = -1; }; editor = { command = [ "${lib.getExe pkgs.alacritty}" "--title" "floating please" "--command" "nvim" "-c" "normal {line}G{column0}|" "{file}" ]; encoding = "utf-8"; remove_file = true; }; fileselect = { handler = "default"; }; hints = { auto_follow = "unique-match"; auto_follow_timeout = 0; chars = "asdfghjkl"; dictionary = "${wordlist}"; hide_unmatched_rapid_hints = true; leave_on_load = false; min_chars = 1; next_regexes = ["\\bnext\\b" "\\bmore\\b" "\\bnewer\\b" "\\b[>→≫]\\b" "\\b(>>|»)\\b" "\\bcontinue\\b"]; prev_regexes = ["\\bprev(ious)?\\b" "\\bback\\b" "\\bolder\\b" "\\b[<←≪]\\b" "\\b(<<|«)\\b"]; scatter = true; uppercase = false; mode = "word"; }; input = { escape_quits_reporter = false; forward_unbound_keys = "auto"; insert_mode = { auto_enter = true; auto_leave = true; auto_load = false; leave_on_load = true; plugins = false; }; links_included_in_focus_chain = true; match_counts = true; media_keys = true; mode_override = null; mouse = { back_forward_buttons = true; rocker_gestures = false; }; partial_timeout = 0; spatial_navigation = false; }; keyhint = { blacklist = []; delay = 250 * millisecond; }; logging = { level = { console = "info"; ram = "debug"; }; }; messages = { timeout = 3 * second; }; new_instance_open_target = "tab"; new_instance_open_target_window = "last-focused"; prompt = { filebrowser = true; }; qt = { args = []; chromium = { experimental_web_platform_features = "auto"; low_end_device_mode = "auto"; process_model = "process-per-site-instance"; sandboxing = "enable-all"; }; environ = {}; force_platform = null; force_platformtheme = null; force_software_rendering = "none"; highdpi = true; workarounds = { disable_accelerated_2d_canvas = "auto"; disable_hangouts_extension = false; locale = false; remove_service_workers = false; }; }; scrolling = { bar = "never"; smooth = false; }; search = { ignore_case = "smart"; incremental = true; wrap = true; wrap_messages = true; }; session = { default_name = null; # TODO(@bpeetz): See https://github.com/qutebrowser/qutebrowser/issues/67 <2025-06-04> lazy_restore = false; }; spellcheck = { # TODO(@bpeetz): Installing them (reproducibly) is simply not worth the hassle, as # qutebrowser already provides quick access to an editor. <2025-06-04> # languages = ["en-GB" "sv-SE" "de-DE"]; }; statusbar = { padding = { bottom = 5; left = 5; right = 5; top = 5; }; position = "top"; show = "always"; widgets = ["keypress" "search_match" "url" "scroll" "history" "tabs" "progress"]; }; tabs = { background = true; close_mouse_button = "middle"; close_mouse_button_on_bar = "new-tab"; favicons = { scale = 1; show = "always"; }; focus_stack_size = 10; last_close = "default-page"; max_width = -1; min_width = -1; mode_on_change = "normal"; mousewheel_switching = true; new_position = { related = "next"; unrelated = "next"; stacking = true; }; pinned = { frozen = true; shrink = true; }; position = "top"; select_on_remove = "last-used"; show = "multiple"; show_switching_delay = 2 * second; tabs_are_windows = false; title = { alignment = "center"; elide = "middle"; format = "{audio}{index}: {current_title}"; format_pinned = "{index}"; }; tooltips = true; undo_stack_size = 100; width = "15%"; wrap = true; }; url = { auto_search = "naive"; default_page = "qute://start"; incdec_segments = ["path" "query"]; open_base_url = false; # Of search engine. searchengines = rec { DEFAULT = leta; leta = "https://leta.mullvad.net/search?q={}"; "@ls" = leta; # NIX "@np" = "https://search.nixos.org/packages?type=packages&query={}"; # Nix packages "@ng" = "https://noogle.dev/q?term={}"; # Nix functions "@no" = "https://search.nixos.org/options?type=options&query={}"; # NixOS options "@nh" = "https://home-manager-options.extranix.com/?query={}&release=master"; # Home-Manager options "@ni" = "https://github.com/NixOS/nixpkgs/issues?q=is%3Aissue+is%3Aopen+{}"; # Nixpkgs issues "@nr" = "https://github.com/NixOS/nixpkgs/pulls?q=is%3Apr+is%3Aopen+{}"; # Nixpkgs pull requests "@nt" = "https://nixpk.gs/pr-tracker.html?pr={}"; # Nixpkgs pull requests tracker "@nw" = "https://wiki.nixos.org/w/index.php?search={}"; # NixOS Wiki # RUST "@rs" = "https://doc.rust-lang.org/std/?search={}"; # Rust std "@rt" = "https://docs.rs/tokio/latest/tokio/index.html?search={}"; # Rust tokio # OTHER "@gs" = "https://scholar.google.com/scholar?hl=en&q={}"; # Google Scholar "@wp" = "https://en.wikipedia.org/wiki/{}"; # Wikipedia "@aw" = "https://wiki.archlinux.org/index.php?search={}"; # Arch Wiki }; start_pages = ["qute://start"]; yank_ignored_parameters = ["ref" "utm_source" "utm_medium" "utm_campaign" "utm_term" "utm_content" "utm_name"]; }; window = { hide_decoration = true; title_format = "{perc}{current_title}{title_sep}qutebrowser"; transparent = false; }; }