A Linux port of NetNewsWire (the macOS RSS reader by Brent Simmons) in Rust and GTK4.
Viaduct translates NetNewsWire's local-account architecture, parsing pipeline, and refresh engine to GTK4 on Wayland, faithfully enough that Brent's CSS themes drop in byte-for-byte. As of v3.0.0 it runs on plain GTK4 with its own flat design (no libadwaita), so it belongs equally on GNOME, Hyprland, or any Wayland desktop. The name is the metaphor: a viaduct carries something across a gap. This one carries NetNewsWire across to Linux.
A note on scope. Viaduct is a solo project, built and tested for one person's real-world use. It supports local and Inoreader accounts because those are the ones I rely on, and it has been exercised against a live subscription list of around 150 feeds on Fedora 44 (ThinkPad T14 AMD Gen 6). Contributions are welcome, but I can only meaningfully test my own configuration.
Modern feed readers tend to be Electron / browser-engine apps with sprawling memory footprints. On the same OPML file, the closest comparable Linux RSS reader idles around 600 MB; Viaduct holds a documented envelope instead (spec §10): idle in the 400–500 MB band after a warm refresh, realistic peak under 600 MB, tracked by an in-tree mem_check harness. It gets there by porting NetNewsWire's discipline (a single-writer SQLite worker with a read-only connection pool, OPML on disk as the source of truth, FTS5 search, byte-bounded per-kind image caches, and exactly one neutered WebKit instance for the article pane) into Rust + tokio + GTK4.
| Feature | Notes |
|---|---|
| NetNewsWire-faithful parsing | RSS 2.0, RDF, Atom, JSON Feed, RSS-in-JSON. Permissive DateParser ported from RSDateParser. CDATA, xhtml-typed Atom content, <media:*> namespace, MD5 synthetic IDs. |
| Local + Inoreader accounts | Local OPML-on-disk account; Inoreader sync engine ported from NNW's ReaderAPI. Other remote sync engines deliberately out of scope. |
| Smart Feeds | Today / All Unread / Starred: virtual feeds via SQLite + FTS5 queries. |
| Single-writer DB worker | Every SQLite write funnels through a dedicated tokio task. The GTK thread never blocks on I/O. |
| Locked-down WebKit pane | One WebKitWebView for article rendering. JS / WebGL / WebRTC / DevTools / LocalStorage / IndexedDB all OFF. Strict CSP routes images through a custom viaduct-img:// URI scheme so the WebView gets zero direct internet access. |
| All 8 NetNewsWire themes | Sepia, Appanoose, Biblioteca, Hyperlegible, NewsFax, Promenade, Tiqoe Dark, Verdana Revival, all bundled byte-for-byte via include_str!, plus an Adwaita reading-pane theme. |
| Owned Kanagawa design (v3.0.0) | libadwaita dropped for a viaduct-owned stylesheet: flat, square, hard-bordered, in the Kanagawa Dragon (dark) / Lotus (light) palette, loaded above any system GTK theme. Since v3.8.0 the shared vir-gtk widget kit supplies the crate-tier base stylesheet and the row/alert widgets. |
| Adaptive layout | Nested GtkPaned panes with drag-to-resize; the sidebar auto-hides on narrow windows (F9 toggles it). |
| Reader View | Local readability extraction (no Mercury / external service), in-process and capped behind a 5 MB input limit; extracted HTML is held in memory for the session, not disk-cached. |
| Video thumbnails + in-pane playback | YouTube + Vimeo detection in feed bodies. Thumbnails in the timeline; opt-in playback in a sandboxed dialog WebView (separate from the article pane's locked-down instance). |
| Keyboard shortcuts | NetNewsWire's bindings, plus Ctrl+Shift+C copy URL, Ctrl+Shift+R toggle reader, Esc close article, Ctrl+N add feed, Ctrl+? cheat sheet. |
| Add Feed dialog | Paste a feed URL or a website URL. A port of NNW's FeedFinder runs the two-pass discovery (parse-as-feed, then fall back to scanning HTML <link rel="alternate">). Optional name + folder placement. |
| Right-click context menus | Sidebar feed rows: Mark All Read / Refresh / Copy Feed URL / Delete (destructive-styled confirm). Sidebar folder rows: Mark All Read. Timeline rows: Toggle Read / Star / Open in Browser / Open Enclosure / Copy URL. |
| Auto-sync | Optional refresh-on-open + periodic refresh (Never / 15 min / 30 min / 1 h / 2 h / 6 h / Daily). Run-in-background keeps syncing with the window closed (system tray icon, portal-granted background permission). |
| OPML import/export | NetNewsWire-shaped serialization, byte-for-byte compatible with OPMLExporter. |
| The Pruning Engine | Age-based article purge, orphaned-author cleanup (NNW issue #5232), and a startup VACUUM gated on actual prune activity so steady-state launches stay fast. |
Captures in dark mode, three-pane wide layout.
Three-pane wide layout on the v3.0 flat Kanagawa design
Sepia reading-pane theme (the eight NetNewsWire article themes are unchanged in v3.0.0)
The AppStream metainfo (data/io.github.virinvictus.Viaduct.appdata.xml) also lists screenshots; those are the ones gnome-software / Flathub display on the install page, so keep both in sync when adding new captures.
Viaduct targets GTK 4.16+ / WebKitGTK 6.0 on Wayland (Hyprland, GNOME, or any Wayland compositor); libadwaita was dropped in v3.0.0. The Flatpak (when shipped to Flathub) bundles every system dependency. Source builds need the development headers below.
| Library | Version | Fedora package | Debian/Ubuntu package |
|---|---|---|---|
| GTK4 | ≥ 4.16 | gtk4-devel |
libgtk-4-dev |
| WebKitGTK | 6.0 | webkitgtk6.0-devel |
libwebkitgtk-6.0-dev |
| SQLite (bundled) | — | — | — |
| TLS | rustls (vendored) | — | — |
v4.0.0 renamed the application id to io.github.virinvictus.Viaduct, which moves the GSettings path with it. Preferences set under the old id are orphaned, not read; carry them over once with:
dconf dump /org/virinvictus/Viaduct/ | dconf load /io/github/virinvictus/Viaduct/and remove the old tree afterwards with dconf reset -r /org/virinvictus/Viaduct/. Hyprland windowrulev2 rules matching the old org.virinvictus.Viaduct class need the new id too.
Preferences live in GSettings, which needs only a D-Bus session bus and a running dconf service: both present on any mainstream Wayland session, Hyprland included. There is no GNOME Shell dependency; notifications go through whichever freedesktop notification daemon you run, and the tray icon follows the StatusNotifier spec.
WebKitGTK 6.0 powers the article reading pane. It runs in a heavily-neutered configuration (JavaScript disabled, no plugins, no local storage, strict CSP), used purely for CSS typography fidelity. See spec.md §2.2 for the threat-model writeup.
# Fedora 43+
sudo dnf install gtk4-devel webkitgtk6.0-devel
# Debian/Ubuntu (24.04+)
sudo apt install libgtk-4-dev libwebkitgtk-6.0-dev
# Build via Cargo (workspace root):
cargo build --release # binary lands at target/release/viaduct
cargo run --release # launch the GTK app
# OR build via Meson, which produces a system-style install layout for
# packagers / Flathub. Mirrors the path Flatpak takes during build:
sudo dnf install meson ninja-build glib2-devel # Fedora
sudo apt install meson ninja-build libglib2.0-bin # Debian/Ubuntu
meson setup builddir --prefix=/usr -Dbuildtype=release
meson compile -C builddir
sudo meson install -C builddir- Cargo workspace with two crates:
viaduct-core(headless: database, network, parser, models, errors, paths, runtime helpers, with no GTK deps) andviaduct(binary: GTK4 + WebKit UI, no libadwaita). The split makes architectural boundaries a compile error, not a code-review rule. - Network + data layers isolated from the UI thread via
tokiomulti-thread runtime. The GTK thread reads in-memory models and posts commands downmpscchannels; it never blocks on SQLite or the network. - Three databases.
articles.sqlite(with FTS5 virtual table),feed-settings.sqlite(per-feed cache, conditional-GET state),sync.sqlite(Inoreader sync state). All in WAL mode. OPML on disk is the source of truth for the feed/folder hierarchy, not SQL, matching NetNewsWire exactly. - Single neutered WebKit instance for the article pane, with all images routed through a custom
viaduct-img://URI scheme so the WebView never reaches the public internet directly.
Detailed design notes live in spec.md. Contributor / agent rules live in CLAUDE.md. Per-release changelog lives in patchnotes.md. Unchecked roadmap items live in roadmap.md.
This project would not exist without Brent Simmons and the NetNewsWire team. Every architectural decision in Viaduct that matters (the three-database split, OPML on disk, the conditional-GET expiry semantics, the special-case domain list, the RSDateParser corpus, the article-ID hashing scheme, the eight bundled article themes) is theirs. Viaduct is not a reimagining; it's a translation. NetNewsWire is MIT-licensed and the .netnewswire/ reference tree is intentionally kept inside this repo (gitignored) so every port decision can be traced back to the canonical source.
If you want a great RSS reader on macOS or iOS, please install NetNewsWire; it is and remains the gold standard. Viaduct is what NetNewsWire would feel like if it ran on Linux.
We also want to give a huge thanks to NewsFlash, the only other RSS reader worth using on Linux. Reviewing their codebase against ours directly informed Viaduct's v2.0 architectural plan (see roadmap.md Phase 18; the written evaluation, two-plans.md, has since been removed and the adopted/deferred/rejected verdicts there are the surviving record). The modular widget decomposition (SidebarView / TimelineView / ArticlePaneView), the dedicated ArticleRenderer GObject, and the expanded reactive-property coverage are concepts NewsFlash demonstrates beautifully in pure GTK4 idiom, and we're adopting the parts that compose with our "port don't invent" constraint. Their broader actor-model approach and Blueprint-based UI we ultimately did not adopt (we stay anchored to NetNewsWire's structure for porting traceability); what their code showed was how far pure GTK4 idiom carries, and comparing it against ours is what surfaced the decomposition questions the v2.0 plan answered.
MIT. See LICENSE.
If Viaduct's useful to you and you'd like to chip in:
- liberapay · liberapay.com/bdkl
- bitcoin
bc1qkge6zr45tzqfwfmvma2ylumt6mg7wlwmhr05yv