Files
att_browser/README.md
T

7.0 KiB

att

A small, keyboard-driven QtWebEngine browser in the spirit of qutebrowser.

This is the skeleton: one window, one page, a qutebrowser-style status line and : command line, f link hints and adblocking. No tabs and no config file yet.

Running

nix run .            # open the default start page
nix run . -- news.ycombinator.com

Developing

nix develop
cmake -B build -G Ninja
cmake --build build
./build/att

The dev shell exports QT_PLUGIN_PATH, QML2_IMPORT_PATH and QTWEBENGINEPROCESS_PATH by hand, because wrapQtAppsHook only wraps binaries that have been installed — a binary run straight out of ./build would otherwise fail to find Qt's platform plugin.

Pinned to nixos-26.05 (current NixOS stable), which ships Qt 6.11.

Modes

Like qutebrowser, keys mean different things depending on the mode:

Mode How you get there Behaviour
normal default, Escape keys are browser bindings
insert i keys go to the page (type in forms)
command o, O, : keys go to the command line
hint f keys pick a link label

Escape (or Ctrl-[) always returns to normal mode.

Note: unlike qutebrowser, att does not yet auto-switch to insert mode when you click into a text field on a page — press i first. Doing it automatically needs a JS focus-tracking script over QWebChannel; see Next steps.

Key bindings (normal mode)

Key Action
o open the command line prefilled :open
O same, prefilled with the current URL
: open a bare command line
i enter insert mode
f hint mode: follow a link by its label
Escape back to normal mode
h j k l scroll
Ctrl-d/u scroll half a page
Ctrl-f/b scroll a full page
gg / G jump to top / bottom
H / L back / forward in history
r / R reload / reload bypassing cache
y yank the current URL to the clipboard
+ - = zoom in / out / reset

The command line keeps a history (Up/Down) and aborts on Escape, Ctrl-c, or if you delete the leading :.

While typing :open (or :o), a completion list of previously visited pages appears above it, most recent first. Every word you type must appear in the URL or the title. Tab / Shift-Tab cycle through the list and put the selected URL on the command line; Enter opens it.

Hint mode

f labels every clickable element in the viewport with home-row letters (asdfghjkl). Type a label to click it; Backspace undoes a letter, Escape cancels. The click is a real mouse event sent to the render widget, so it works on sites that ignore element.click(). Following a text field drops you into insert mode. Links that would open a new window are opened in place, since there are no tabs yet.

The hint script runs in QWebEngineScript::ApplicationWorld, so the page's own JavaScript can't see or interfere with it. It doesn't descend into iframes or shadow DOM yet.

Browsing data

Browsing history is kept in ~/.local/share/att/att/history.sqlite (tables visits, one row per page load, and urls, one row per URL for completion). A page is recorded once it finishes loading, so redirect hops and in-page #anchor jumps don't create entries. :history-clear empties it.

Cookies, local storage and Chromium's own state are kept in a persistent QtWebEngine profile under ~/.local/share/att/att/QtWebEngine/default (the cache is in ~/.cache/att/att/). Session cookies are saved too, like qutebrowser's content.cookies.store, so you stay logged in across restarts. Delete that directory to start fresh.

Adblocking

Network requests are checked against EasyList and EasyPrivacy with Brave's adblock-rust, the same engine and default lists as qutebrowser. Blocked requests never leave the browser. There is no cosmetic filtering (element hiding), so ads served from the site's own URLs still get through.

The lists are downloaded on first start and compiled into ~/.local/share/att/att/adblock-cache.dat, which later starts just load. :adblock-update re-downloads them. :adblock-toggle switches blocking off and on, for when a site breaks.

Commands

:open <url|query> (alias :o), :quit (alias :q), :reload, :back, :forward, :stop, :history-clear, :adblock-update, :adblock-toggle.

:open accepts a full URL, a bare host (example.com → https://example.com), an existing local path, or free text, which goes to DuckDuckGo.

Layout

File Role
flake.nix pinned inputs, package, dev shell
package.nix the derivation itself, usable via callPackage
src/main.cpp QApplication, CLI parsing, stylesheet
src/browserwindow the window, mode state machine, key dispatch, commands
src/commandline the : prompt
src/history SQLite browsing history
src/completionview the :open completion list
src/statusbar mode / URL / progress line
src/adblock request interceptor around the adblock engine
adblock/ Rust crate: C API over adblock-rust, built by Corrosion
src/hints the injected hint-mode script
src/urlutil "what the user typed" → QUrl

Key events are intercepted with an application-wide event filter (BrowserWindow::eventFilter) rather than QWidget::keyPressEvent, because QtWebEngine delivers key presses directly to its internal render widget; the parent window never sees them otherwise.

Next steps

  • Tabs (QTabWidget + a per-tab QWebEngineView), gt/gT, d to close.
  • Auto insert mode: inject a QWebEngineScript that reports focusin / focusout on editable elements back over QWebChannel.
  • More hint modes: F (new tab, once there are tabs), ;y to yank a link.
  • / and ? search with n/N, using QWebEnginePage::findText.
  • A config file for bindings, start page and search engine.
  • Counts before commands (3j) and a proper command parser with completion.