rename: att -> att_browser

This commit is contained in:
2026-10-17 11:38:55 +02:00
parent 495a0696ba
commit 483b3f87ba
11 changed files with 97 additions and 49 deletions
+8 -7
View File
@@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## What this is
`att` is a small, keyboard-driven QtWebEngine browser modelled on qutebrowser, in C++20 / Qt 6 (pinned nixpkgs ships Qt 6.11). The README is the user-facing reference for key bindings, modes and commands. When you add a binding, mode or command, update its tables there.
`att_browser` is a small, keyboard-driven QtWebEngine browser modelled on qutebrowser, in C++20 / Qt 6 (pinned nixpkgs ships Qt 6.11). The README is the user-facing reference for key bindings, modes and commands. When you add a binding, mode or command, update its tables there.
## Build and run
@@ -12,12 +12,12 @@ Everything goes through the Nix flake:
```sh
nix develop -c sh -c 'cmake -B build -G Ninja && cmake --build build' # dev build
nix develop -c ./build/att [url] # run the dev build
nix develop -c ./build/att_browser [url] # run the dev build
nix build # package build (→ ./result)
nix run . -- example.com
```
Run `./build/att` from inside `nix develop`. The shell hook exports `QT_PLUGIN_PATH`, `QML2_IMPORT_PATH` and `QTWEBENGINEPROCESS_PATH`, because `wrapQtAppsHook` only wraps *installed* binaries. Run outside the dev shell, the binary can't find Qt's plugins.
Run `./build/att_browser` from inside `nix develop`. The shell hook exports `QT_PLUGIN_PATH`, `QML2_IMPORT_PATH` and `QTWEBENGINEPROCESS_PATH`, because `wrapQtAppsHook` only wraps *installed* binaries. Run outside the dev shell, the binary can't find Qt's plugins.
`package.nix` uses a `lib.fileset` source: `CMakeLists.txt`, `src/` and the listed `adblock/` files. Every new source file must also be added to `qt_add_executable` in `CMakeLists.txt`, and `git add`ed: the flake only sees tracked files, so `nix build` fails on untracked sources (`nix build path:.` works around that). The crate's cargo dependencies are vendored from `adblock/Cargo.lock` (`importCargoLock` + `cargoSetupHook`) for the offline sandbox build, so commit `Cargo.lock` changes along with `Cargo.toml` ones.
@@ -39,7 +39,7 @@ The only tests are the Rust crate's (`nix develop -c sh -c 'cd adblock && cargo
- Open the command line only through `openCommandLine`, which hides the status bar *before* showing the command line. Showing a widget re-runs the layout at once, so the other order shrinks and regrows the page.
- **`setMode` is where entering and leaving a mode takes effect.** It updates the status line and focus. Leaving `Hint` also runs the hint-clear script.
- **Page interaction mostly goes through `page()->runJavaScript`**, except for input that should feel native:
- Scrolling (`scrollBy`, used by `j/k/h/l`, `Ctrl-d/u/f/b` and `gg/G`) sends a pixel-delta `QWheelEvent` to `m_view->focusProxy()` at the pointer, which is what a touchpad sends. Chromium then scrolls whatever is under the pointer. `window.scrollBy` only moves the document, so it did nothing on sites that scroll an inner element.
- Scrolling (`scrollBy`, used by `j/k/h/l`, `Ctrl-d/u/f/b` and `gg/G`) sends a pixel-delta `QWheelEvent` to `m_view->focusProxy()` at the pointer, which is what a touchpad sends. Chromium then scrolls whatever is under the pointer. Wheel deltas are screen pixels, so the page zoom shrinks them; `j`/`k` go through `scrollByCssPx`, which multiplies by the zoom so `config::kScrollStepPx` means CSS px like qutebrowser's `scroll-px`. `window.scrollBy` only moves the document, so it did nothing on sites that scroll an inner element.
- Hint clicks are real mouse events (see below).
- **Hint mode** (`src/hints.cpp`):
- The state lives in JS (`window.__attHints`), injected into `QWebEngineScript::ApplicationWorld`. That world shares the DOM with the page but keeps its JS globals isolated from the page's scripts.
@@ -48,6 +48,7 @@ The only tests are the Rust crate's (`nix develop -c sh -c 'cd adblock && cargo
- Following a hint sends a real `QMouseEvent` press/release to `m_view->focusProxy()` (the render widget). The position is in CSS px multiplied by the zoom factor.
- **`CommandLine`** keeps the leading `:` in its text; deleting it cancels. It emits `commandEntered(cmd)` without the `:`, and `BrowserWindow::runCommand` parses it as name plus the rest of the line.
- **`urlutil::fromUserInput`** turns what the user typed into a URL. In order, it tries an existing local path, a full URL, then something that looks like a host (gets `https://`). Anything else becomes a DuckDuckGo search.
- **Data paths**: the application name is `att_browser` and there is deliberately no organization name, so `AppDataLocation` is `~/.local/share/att_browser` (and `CacheLocation` `~/.cache/att_browser`). `migrateOldDataDirs` in `main.cpp` moves the pre-rename `att/att` directories there once, before anything opens a file.
- **The `QWebEngineProfile` is created in `main.cpp`** and passed into `BrowserWindow`. It is a named, on-disk profile with `ForcePersistentCookies`, because Qt 6's default profile is off-the-record. It is parented to the `QApplication` so it outlives the view's page. Qt warns if a profile is destroyed while a page still uses it.
- **Adblocking** is split across two parts:
- `adblock/` is a Rust staticlib exposing a hand-written C API (`att_adblock.h`, kept in sync with `src/lib.rs` by hand) over Brave's `adblock` crate. Corrosion builds it as part of `cmake --build`, always in release. Its `single-thread` default feature is off, which makes `Engine` `Send + Sync`.
@@ -58,14 +59,14 @@ The only tests are the Rust crate's (`nix develop -c sh -c 'cd adblock && cargo
- **`:open` completion:** `BrowserWindow::updateCompletion` runs on the command line's `textEdited` and on entering command mode. It parses `:open`/`:o` and shows `History::search` results in `CompletionView`, which never takes focus. `CommandLine::event` intercepts Tab/Backtab, because `QWidget::event` would otherwise turn them into focus changes. Picking an entry uses `setCommandText`, which doesn't emit `textEdited`, so it doesn't re-filter.
- **Greasemonkey** (`Greasemonkey`, `src/greasemonkey.cpp`, part of the `Session`) inserts each `greasemonkey/*.js` into `profile->scripts()` as a `QWebEngineScript` named `att-greasemonkey:<file>`, in `MainWorld`.
- URL matching is left to QtWebEngine, which parses the `==UserScript==` block during `setSourceCode` and checks `@include/@exclude/@match` in Chromium. So the block must stay in the source. `setSourceCode` also overwrites the script's name (with `@name`) and injection point, so `setName` and `setInjectionPoint` must come *after* it. `install()` finds the old scripts by the `att-greasemonkey:` name prefix to remove them; a lost prefix makes scripts run twice after a reload.
- att's own `parseMetadata` is for run-at, noframes, `@require` and `GM_info`. A `@require` is cached by the SHA-1 of its URL. It must be `https`, and an optional `#sha256=` pin is checked against the cached file on every install (`requireProblem`).
- att_browser's own `parseMetadata` is for run-at, noframes, `@require` and `GM_info`. A `@require` is cached by the SHA-1 of its URL. It must be `https`, and an optional `#sha256=` pin is checked against the cached file on every install (`requireProblem`).
- Scripts are wrapped in an IIFE (`kWrapperHead`/`kWrapperTail`) that defines the GM API as locals.
- At `document-start`, `document.documentElement` is still null, which the GM helpers must tolerate.
- **Hardening** (`src/hardening.cpp`): `applyToProfile` sets the restrictive `QWebEngineSettings` and the third-party cookie filter. The filter runs on the network thread and must stay a pure function. `main.cpp` applies it right after creating the profile, and warns in the first window if `sandboxDisabledReason` finds the sandbox off. `StatusBar::setUrl` takes a `QUrl` so it can mark plain-HTTP pages (`insecure` property).
- **User agent** (`src/useragent.cpp`): `useragent::apply` sets the profile's UA string *and* its `QWebEngineClientHints`, so the two don't contradict each other. Chromium sends the low-entropy hints (brands, platform, mobile) even with `setAllClientHintsEnabled(false)`, so every preset sets a platform; the brand ("Google Chrome", "Microsoft Edge") goes in through `setFullVersionList`. Chrome-based presets use the engine's own major version. `:useragent` completion reuses `CompletionView` with name / string columns.
- **Settings** live as constants in `src/config.h` (`config::kZoomDefault`, `config::kZoomLevels`, `kBlockThirdPartyCookies`, `kPdfViewer`, `kUserAgent`), named after qutebrowser's settings, until there's a config file. Zoom is per window: `setZoom` / `zoomStep` in `BrowserWindow`, applied before the first load. QtWebEngine keeps a view's zoom across navigations, so it doesn't need re-applying.
- **Settings** live as constants in `src/config.h` (`config::kZoomDefault`, `config::kZoomLevels`, `kBlockThirdPartyCookies`, `kPdfViewer`, `kUserAgent`, `kScrollStepPx`), named after qutebrowser's settings, until there's a config file. Zoom is per window: `setZoom` / `zoomStep` in `BrowserWindow`, applied before the first load. QtWebEngine keeps a view's zoom across navigations, so it doesn't need re-applying.
- The UI stylesheet is the `kStyleSheet` string in `src/main.cpp`. Widgets are styled by `objectName` and by dynamic properties: `StatusBar` toggles `error` and re-polishes the label to pick up the change.
**Windows, not tabs.** att deliberately has no tab bar, because the user's window manager does the tabbing. Map qutebrowser's tab features onto top-level windows: `d` closes the window, `O` / `:open -w` (or `-t`) opens a new one. Each `BrowserWindow` is one page, with `WA_DeleteOnClose`. All windows share a `Session` (profile, `AdBlocker`, `History`) that `main()` owns. After `app.exec()`, `main()` deletes any window still alive before the history and profile go away. `:quit` closes all windows; `:close` closes one.
**Windows, not tabs.** att_browser deliberately has no tab bar, because the user's window manager does the tabbing. Map qutebrowser's tab features onto top-level windows: `d` closes the window, `O` / `:open -w` (or `-t`) opens a new one. Each `BrowserWindow` is one page, with `WA_DeleteOnClose`. All windows share a `Session` (profile, `AdBlocker`, `History`) that `main()` owns. After `app.exec()`, `main()` deletes any window still alive before the history and profile go away. `:quit` closes all windows; `:close` closes one.
`newWindowRequested` isn't handled yet, so `target=_blank` and `window.open` go nowhere. Hint mode rewrites link targets to `_self` to work around this.
+5 -5
View File
@@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.21)
project(att
project(att_browser
VERSION 0.1.0
DESCRIPTION "Minimal keyboard-driven QtWebEngine browser"
LANGUAGES CXX
@@ -26,7 +26,7 @@ corrosion_import_crate(
PROFILE release
)
qt_add_executable(att
qt_add_executable(att_browser
src/adblock.cpp
src/adblock.h
src/main.cpp
@@ -53,9 +53,9 @@ qt_add_executable(att
src/useragent.h
)
target_include_directories(att PRIVATE src adblock)
target_include_directories(att_browser PRIVATE src adblock)
target_link_libraries(att PRIVATE
target_link_libraries(att_browser PRIVATE
att_adblock
Qt6::Core
Qt6::Gui
@@ -67,6 +67,6 @@ target_link_libraries(att PRIVATE
Qt6::WebEngineWidgets
)
install(TARGETS att
install(TARGETS att_browser
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
+20 -15
View File
@@ -1,10 +1,10 @@
# att
# att_browser
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
opens or closes a tab, att_browser opens or closes a window, and your window manager
does the tabbing. No config file yet.
## Running
@@ -20,7 +20,7 @@ nix run . -- news.ycombinator.com
nix develop
cmake -B build -G Ninja
cmake --build build
./build/att
./build/att_browser
```
The dev shell exports `QT_PLUGIN_PATH`, `QML2_IMPORT_PATH` and
@@ -45,7 +45,7 @@ Like qutebrowser, keys mean different things depending on the mode:
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
> Note: unlike qutebrowser, att_browser 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*.
@@ -62,7 +62,8 @@ is hidden while a page is fullscreen.
| `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 |
| `j` `k` | scroll down / up 25 CSS px (`scroll-px 0 ±25`) |
| `h` `l` | scroll left / right |
| `Ctrl-d/u` | scroll half a page |
| `Ctrl-f/b` | scroll a full page |
| `gg` / `G` | jump to top / bottom |
@@ -94,17 +95,20 @@ shadow DOM yet.
## Browsing data
Browsing history is kept in `~/.local/share/att/att/history.sqlite` (tables
Browsing history is kept in `~/.local/share/att_browser/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
under `~/.local/share/att_browser/QtWebEngine/default` (the cache is in
`~/.cache/att_browser/`). Session cookies are saved too, like qutebrowser's
`content.cookies.store`, so you stay logged in across restarts. Delete that
directory to start fresh.
Before the rename to att_browser, this data lived in `~/.local/share/att/att` and
`~/.cache/att/att`. The first start moves it to the new locations.
## Adblocking
Network requests are checked against EasyList and EasyPrivacy with Brave's
@@ -114,15 +118,15 @@ 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.
`~/.local/share/att_browser/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
Userscripts go in `~/.local/share/att_browser/greasemonkey/` as `*.js` files. They
are loaded at startup; after changing them, run `:greasemonkey-reload` and reload
the page. Like qutebrowser, att:
the page. Like qutebrowser, att_browser:
- runs scripts in the page's own JavaScript context (so `unsafeWindow` is just
`window`), in iframes too unless `@noframes` is set;
@@ -144,7 +148,7 @@ supported. `GM_openInTab` does nothing until new-window requests are handled.
## Security
On top of Chromium's own protections, att:
On top of Chromium's own protections, att_browser:
- blocks third-party cookies, which stops most cross-site tracking. A few embedded
logins (e.g. some comment widgets) may stop working.
@@ -186,13 +190,13 @@ and platform.
Chrome and Edge claim the engine's real Chromium major version (as qutebrowser
does), so sites don't use features the engine lacks. The Firefox and Safari
versions are pinned in `src/useragent.cpp`. Those presets only change what att
versions are pinned in `src/useragent.cpp`. Those presets only change what att_browser
says, not how it behaves: Chromium's low-entropy client hints still name
Chromium, and feature detection still sees Chromium.
## Settings
att has no config file yet. The settings live in `src/config.h`, named after
att_browser has no config file yet. The settings live in `src/config.h`, named after
their qutebrowser equivalents; change them there and rebuild:
| Setting | Default | Meaning |
@@ -201,7 +205,8 @@ their qutebrowser equivalents; change them there and rebuild:
| `zoom.levels` | 25%, 33%, 50%, … 125%, 150%, … 500% | the steps `+` / `-` move through |
| `content.cookies.accept` | no third-party | `kBlockThirdPartyCookies` |
| `content.pdfjs` | on | `kPdfViewer`: Chromium's PDF viewer |
| `content.headers.user_agent` | `chrome-linux` | `kUserAgent`: the preset att starts with |
| `j` / `k` step (`scroll-px`) | 25 CSS px | `kScrollStepPx`; grows with the zoom |
| `content.headers.user_agent` | `chrome-linux` | `kUserAgent`: the preset att_browser starts with |
## Commands
+8 -8
View File
@@ -1,5 +1,5 @@
{
description = "att - a minimal, qutebrowser-like QtWebEngine browser";
description = "att_browser - a minimal, qutebrowser-like QtWebEngine browser";
inputs = {
# Pinned to the current NixOS stable release.
@@ -14,21 +14,21 @@
in
{
packages = forAllSystems (pkgs: rec {
att = pkgs.callPackage ./package.nix { };
default = att;
att_browser = pkgs.callPackage ./package.nix { };
default = att_browser;
});
apps = forAllSystems (pkgs: rec {
att = {
att_browser = {
type = "app";
program = "${self.packages.${pkgs.stdenv.hostPlatform.system}.att}/bin/att";
program = "${self.packages.${pkgs.stdenv.hostPlatform.system}.att_browser}/bin/att_browser";
};
default = att;
default = att_browser;
});
devShells = forAllSystems (pkgs: {
default = pkgs.mkShell {
inputsFrom = [ self.packages.${pkgs.stdenv.hostPlatform.system}.att ];
inputsFrom = [ self.packages.${pkgs.stdenv.hostPlatform.system}.att_browser ];
packages = with pkgs; [
clang-tools
@@ -42,7 +42,7 @@
export QT_PLUGIN_PATH="${qtbase}/${qtbase.qtPluginPrefix}:${qtwebengine}/${qtbase.qtPluginPrefix}"
export QML2_IMPORT_PATH="${qtdeclarative}/${qtbase.qtQmlPrefix}:${qtwebengine}/${qtbase.qtQmlPrefix}"
export QTWEBENGINEPROCESS_PATH="${qtwebengine}/libexec/QtWebEngineProcess"
echo "att dev shell - cmake -B build -G Ninja && cmake --build build && ./build/att"
echo "att_browser dev shell - cmake -B build -G Ninja && cmake --build build && ./build/att_browser"
'';
};
});
+2 -2
View File
@@ -10,7 +10,7 @@
}:
stdenv.mkDerivation (finalAttrs: {
pname = "att";
pname = "att_browser";
version = "0.1.0";
src = lib.fileset.toSource {
@@ -49,7 +49,7 @@ stdenv.mkDerivation (finalAttrs: {
meta = {
description = "Minimal keyboard-driven QtWebEngine browser";
mainProgram = "att";
mainProgram = "att_browser";
platforms = lib.platforms.linux;
license = lib.licenses.mit;
};
+14 -6
View File
@@ -88,7 +88,7 @@ BrowserWindow::BrowserWindow(const Session &session, const QUrl &startUrl, QWidg
void BrowserWindow::setupUi()
{
setWindowTitle(QStringLiteral("att"));
setWindowTitle(QStringLiteral("att_browser"));
resize(1280, 800);
auto *central = new QWidget(this);
@@ -134,8 +134,8 @@ void BrowserWindow::connectView()
setPageFullScreen(request.toggleOn());
});
connect(m_view, &QWebEngineView::titleChanged, this, [this](const QString &title) {
setWindowTitle(title.isEmpty() ? QStringLiteral("att")
: QStringLiteral("%1 - att").arg(title));
setWindowTitle(title.isEmpty() ? QStringLiteral("att_browser")
: QStringLiteral("%1 - att_browser").arg(title));
m_session.history->updateTitle(m_view->url(), title);
});
connect(m_view, &QWebEngineView::loadFinished, this, [this](bool ok) {
@@ -344,11 +344,11 @@ bool BrowserWindow::handleNormalModeKey(QKeyEvent *event)
return true;
}
if (text == QLatin1String("j")) {
scrollBy(0, kScrollStep);
scrollByCssPx(0, config::kScrollStepPx);
return true;
}
if (text == QLatin1String("k")) {
scrollBy(0, -kScrollStep);
scrollByCssPx(0, -config::kScrollStepPx);
return true;
}
if (text == QLatin1String("h")) {
@@ -500,7 +500,7 @@ void BrowserWindow::runCommand(const QString &command)
} else if (name == QLatin1String("version")) {
// The Chromium security patch level is what says how far behind
// Chrome's fixes this build is; "nix flake update" moves it forward.
m_status->showMessage(QStringLiteral("att %1, Qt %2, Chromium %3, security patches %4")
m_status->showMessage(QStringLiteral("att_browser %1, Qt %2, Chromium %3, security patches %4")
.arg(QCoreApplication::applicationVersion(),
QString::fromLatin1(qVersion()),
QString::fromLatin1(qWebEngineChromiumVersion()),
@@ -655,6 +655,14 @@ void BrowserWindow::scrollBy(int dx, int dy)
QCoreApplication::sendEvent(target, &event);
}
void BrowserWindow::scrollByCssPx(int dx, int dy)
{
// Wheel deltas are screen pixels, which the page zoom shrinks (at 125% a
// 25px wheel moves 20 CSS px). qutebrowser's scroll-px counts CSS pixels.
const double zoom = m_view->zoomFactor();
scrollBy(qRound(dx * zoom), qRound(dy * zoom));
}
void BrowserWindow::scrollPages(double factor)
{
scrollBy(0, qRound(m_view->height() * factor));
+1
View File
@@ -66,6 +66,7 @@ private:
void placeCompletion();
void openUrl(const QString &input, bool newWindow);
void scrollBy(int dx, int dy);
void scrollByCssPx(int dx, int dy);
void scrollPages(double factor);
void scrollToEnd(bool bottom);
void focusView();
+4
View File
@@ -17,6 +17,10 @@ inline constexpr std::array kZoomLevels = {
1.25, 1.50, 1.75, 2.00, 2.50, 3.00, 4.00, 5.00,
};
// What "j" / "k" do: qutebrowser's "scroll-px 0 25" / "scroll-px 0 -25". In
// CSS pixels, so the step grows with the zoom like qutebrowser's does.
inline constexpr int kScrollStepPx = 25;
// content.cookies.accept = no-3rdparty: refuse cookies a page's third-party
// requests try to set or send. Blocks most cross-site tracking; a few embedded
// logins (e.g. some comment widgets) stop working.
+1 -1
View File
@@ -230,7 +230,7 @@ QString wrap(const Greasemonkey::Metadata &meta, const QString &requiredCode, co
QJsonObject info{
{QStringLiteral("script"), script},
{QStringLiteral("scriptMetaStr"), meta.block},
{QStringLiteral("scriptHandler"), QStringLiteral("att")},
{QStringLiteral("scriptHandler"), QStringLiteral("att_browser")},
{QStringLiteral("version"), QCoreApplication::applicationVersion()},
};
+1 -1
View File
@@ -44,7 +44,7 @@ QString sandboxDisabledReason(int argc, char **argv)
return QStringLiteral("QTWEBENGINE_CHROMIUM_FLAGS contains --no-sandbox");
for (int i = 1; i < argc; ++i) {
if (QLatin1String(argv[i]) == QLatin1String("--no-sandbox"))
return QStringLiteral("att was started with --no-sandbox");
return QStringLiteral("att_browser was started with --no-sandbox");
}
return {};
}
+33 -4
View File
@@ -9,6 +9,9 @@
#include <QApplication>
#include <QCommandLineParser>
#include <QDir>
#include <QFileInfo>
#include <QStandardPaths>
#include <QWebEngineProfile>
namespace {
@@ -48,18 +51,44 @@ constexpr auto kStyleSheet = R"(
}
)";
// Before the rename, the browser was "att" with organization "att", so its
// data was in ~/.local/share/att/att and ~/.cache/att/att. Move those to the
// new locations once, before anything opens a file there, so cookies, history
// and userscripts survive. A new directory that already exists is left alone.
void migrateOldDataDirs()
{
const auto migrate = [](QStandardPaths::StandardLocation generic,
QStandardPaths::StandardLocation app) {
const QString base = QStandardPaths::writableLocation(generic);
const QString oldPath = base + QStringLiteral("/att/att");
const QString newPath = QStandardPaths::writableLocation(app);
if (!QFileInfo::exists(oldPath) || QFileInfo::exists(newPath))
return;
QDir().mkpath(QFileInfo(newPath).path());
if (QDir().rename(oldPath, newPath))
QDir(base).rmdir(QStringLiteral("att")); // only if now empty
else
qWarning("att_browser: could not move %s to %s", qPrintable(oldPath),
qPrintable(newPath));
};
migrate(QStandardPaths::GenericDataLocation, QStandardPaths::AppDataLocation);
migrate(QStandardPaths::GenericCacheLocation, QStandardPaths::CacheLocation);
}
} // namespace
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QCoreApplication::setApplicationName(QStringLiteral("att"));
// No organization name, so the data lives in ~/.local/share/att_browser
// rather than a doubled att_browser/att_browser.
QCoreApplication::setApplicationName(QStringLiteral("att_browser"));
QCoreApplication::setApplicationVersion(QStringLiteral("0.1.0"));
QCoreApplication::setOrganizationName(QStringLiteral("att"));
migrateOldDataDirs();
QCommandLineParser parser;
parser.setApplicationDescription(
QStringLiteral("att - a minimal, keyboard-driven browser"));
QStringLiteral("att_browser - a minimal, keyboard-driven browser"));
parser.addHelpOption();
parser.addVersionOption();
parser.addPositionalArgument(QStringLiteral("url"), QStringLiteral("URL to open."));
@@ -97,7 +126,7 @@ int main(int argc, char *argv[])
firstWindow->show();
const QString sandboxOff = hardening::sandboxDisabledReason(argc, argv);
if (!sandboxOff.isEmpty()) {
qWarning("att: Chromium's sandbox is disabled: %s", qPrintable(sandboxOff));
qWarning("att_browser: Chromium's sandbox is disabled: %s", qPrintable(sandboxOff));
firstWindow->showMessage(QStringLiteral("Sandbox disabled: %1").arg(sandboxOff), true);
}
// After the window exists, so its status bar shows their messages. The