# 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 ```sh nix run . # open the default start page nix run . -- news.ycombinator.com ``` ## Developing ```sh 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 `:`. ## 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 Cookies, local storage and history 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](https://github.com/brave/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 ` (alias `:o`), `:quit` (alias `:q`), `:reload`, `:back`, `:forward`, `:stop`, `: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/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.