185 lines
8.8 KiB
Markdown
185 lines
8.8 KiB
Markdown
# att
|
|
|
|
A small, keyboard-driven QtWebEngine browser in the spirit of qutebrowser.
|
|
|
|
One page per window, a qutebrowser-style status line and `:` command line, `f`
|
|
link hints and adblocking. There is deliberately no tab bar: where qutebrowser
|
|
opens or closes a tab, att opens or closes a window, and your window manager
|
|
does the tabbing. 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. In normal mode, it also
|
|
leaves fullscreen when a page (e.g. a YouTube video) is fullscreen. The status bar
|
|
is hidden while a page is fullscreen.
|
|
|
|
> 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, but opens in a new window (`-w`) |
|
|
| `go` / `gO` | edit the current URL, here / new window |
|
|
| `d` | close this window |
|
|
| `:` | open a bare command line |
|
|
| `i` | enter insert mode |
|
|
| `gi` | focus the first text field, 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 for now.
|
|
|
|
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](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.
|
|
|
|
## Greasemonkey
|
|
|
|
Userscripts go in `~/.local/share/att/att/greasemonkey/` as `*.js` files. They
|
|
are loaded at startup; after changing them, run `:greasemonkey-reload` and reload
|
|
the page. Like qutebrowser, att:
|
|
|
|
- runs scripts in the page's own JavaScript context (so `unsafeWindow` is just
|
|
`window`), in iframes too unless `@noframes` is set;
|
|
- honours `@include`, `@exclude` and `@match` (checked by QtWebEngine itself; a
|
|
script with none of them runs everywhere), `@run-at document-start` /
|
|
`document-end` (the default) / `document-idle`, and `@require`, which is
|
|
downloaded once into `greasemonkey/requires/`;
|
|
- provides `GM_info`, `GM_setValue`/`GM_getValue`/`GM_deleteValue`/`GM_listValues`,
|
|
`GM_addStyle`, `GM_addElement`, `GM_xmlhttpRequest`, `GM_openInTab`,
|
|
`GM_setClipboard`, `GM_log`, `GM_notification` and the promise-based `GM.*`
|
|
versions.
|
|
|
|
Limits: stored values live in the site's `localStorage`, so they are per site
|
|
rather than shared across sites. `GM_xmlhttpRequest` is a plain XHR, so it can't
|
|
make cross-origin requests the server doesn't allow (CORS). `@resource` isn't
|
|
supported. `GM_openInTab` does nothing until new-window requests are handled.
|
|
|
|
## Commands
|
|
|
|
`:open [-w] <url|query>` (alias `:o`; `-w`, or qutebrowser's `-t`, opens a new
|
|
window), `:close` (alias `:tab-close`, closes this window), `:quit` (alias `:q`,
|
|
closes all windows), `:reload`, `:back`, `:forward`, `:stop`, `:history-clear`,
|
|
`:greasemonkey-reload`, `: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/greasemonkey` | userscript loading and the GM_* API wrapper |
|
|
| `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
|
|
|
|
- Open `target=_blank` links, `window.open` and middle-clicks in new windows
|
|
(`QWebEnginePage::newWindowRequested`).
|
|
- Auto insert mode: inject a `QWebEngineScript` that reports `focusin` /
|
|
`focusout` on editable elements back over `QWebChannel`.
|
|
- More hint modes: `F` (follow in a new window), `;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.
|