141 lines
6.2 KiB
Markdown
141 lines
6.2 KiB
Markdown
# 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 <url|query>` (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.
|