A native Bluesky client for Linux, built with Rust, GTK4, and Libadwaita. Learn more at hangar.blue.
Technical Preview: Hangar is in early development. The foundations are being built feature by feature. Expect rough edges, missing functionality, and breaking changes.
Hangar is a desktop Bluesky client designed specifically for Linux and the GNOME desktop environment. No Electron, no web views, no hybrid UI. Just native GTK4 with Libadwaita for a fast, integrated experience.
- Purpose-built: Built for Linux, feels at home in GNOME, with each feature being meticulously crafted
- Performance: Instant startup, smooth scrolling, efficient memory usage, optimized networking
- Full Bluesky support: Timeline, feeds, posts, interactions, notifications, and DMs
This is a technical preview. The app is functional but incomplete. Development follows a slice-by-slice methodology. One feature at a time, end-to-end.
- Authentication: OAuth (PKCE + DPoP) with persistent sessions, app password fallback
- Timeline: Home feed with infinite scroll, cursor-based pagination, pull-to-refresh
- Custom Feeds: Feed selector with Following, Discover, and pinned feeds
- Live Updates: Background polling with seamless new post insertion
- Rich Embeds: Image grids (1–4+), external link cards, quote posts
- Video: In-app HLS playback (GStreamer
playbin3) with seek, mute, and a persistent volume level - Interactions: Like/unlike, repost/unrepost, quote, reply
- Compose: Rich text highlighting (mentions, hashtags, URLs), mention autocomplete, image attachments (up to 4 with alt text), link card preview (Open Graph), language selection, per-post content warnings, interaction settings, thread composer
- Navigation: Home, Mentions, Activity, Chat, Profile, Likes, Search tabs with drill-down views
- Thread View: Full thread with parent posts and replies
- Profile View: Own profile with banner, bio, follower/following/post counts; drill-down profiles from clicking avatars
- Notifications: Mentions tab with filtered notifications; Activity tab with badge overlays and embedded post cards
- Chat: Conversation list with unread badges and last message preview
- Search: Search tab with results list and interactions
- Settings: Display (post text size, color scheme), Accessibility (reduce motion), Account (content safety link, clear cache)
- Caching: SQLite cache for posts, feeds, profiles, images, notifications with per-user isolation and automatic eviction
- Accessibility: Accessible roles and labels, keyboard shortcuts (F5/Ctrl+R), reduced motion support, focus ring visibility, theme variable usage, non-color state indicators
- Follow/unfollow
- Chat message thread view and sending
- Keyboard shortcuts for navigation and post actions
- Enhanced screen reader support (composite labels, live regions, focus management)
- Image lightbox, loading skeletons
- Inline video playback in the timeline, with an autoplay preference
- Full profile view on drill-down, profile editing
- Internationalization (i18n) and Flatpak distribution
- Bookmarks, moderation tools, desktop notifications, multi-account support
- Lots of polish
Hangar uses OAuth (PKCE + DPoP) for authentication. When you sign in, a browser window opens to complete the authorization flow via your Bluesky PDS. Sessions are persisted locally and automatically refreshed.
App password authentication is also supported as a fallback:
- Go to bsky.app/settings/app-passwords
- Create a new app password
- Use that password to log in to Hangar
App passwords can be revoked at any time without affecting your main account password.
OAuth sessions are stored in a local file-based session store. App password sessions are stored securely using libsecret (the GNOME keyring). If libsecret/D-Bus is unavailable, app password session persistence will fail gracefully and you'll need to log in each time.
Download the latest AppImage from the Releases page, make it executable, and run:
chmod +x Hangar-x86_64.AppImage
./Hangar-x86_64.AppImageThe AppImage bundles no libraries, so the host supplies everything: GTK 4.14+,
libadwaita 1.5+ and glibc 2.39+, which means GNOME 46+ (Fedora 40+, Ubuntu
24.04+). It also needs the host's GStreamer 1.20+: libgstreamer1.0-0,
libgstreamer-plugins-base1.0-0 and libgstreamer-gl1.0-0 on Debian/Ubuntu,
gstreamer1 and gstreamer1-plugins-base on Fedora, plus the playback
plugins listed under Dependencies for video. These are linked
in rather than loaded on demand, so the AppImage refuses to start without them
instead of merely losing video.
The AppImage supports delta updates via zsync and is also available through the AM AppImage package manager:
am -i hangarHangar checks for new releases at launch and offers them in a toast. To
update in place with a delta download instead, point
AppImageUpdate or
Gear Lever at the file;
the update information is embedded. AM users can run am -u hangar.
Download the .flatpak bundle from the Releases page and install:
flatpak install hangar.flatpakVideo decoding relies on org.freedesktop.Platform.codecs-extra, which flatpak
installs alongside org.gnome.Platform by default. The base runtime's
libavcodec has no H.264 decoder, so a runtime installed with --no-related
plays audio and shows nothing.
Bundle installs have no update remote, so flatpak update cannot find new
versions. Hangar checks for new releases at launch and offers them in a
toast; installing the new bundle over the old one keeps your data. Flathub
submission is planned, and once it lands updates become automatic through
the store.
Building requires GTK 4.14+ and libadwaita 1.5+ (GNOME 46+, Fedora 40+,
Ubuntu 24.04+), and the resulting binary needs the same versions at runtime.
It links gtk_graphics_offload and the AdwDialog family directly, so there
is no older floor to fall back to. A build also imposes its own glibc on
whoever runs the binary; the released AppImage is built on Ubuntu 24.04 and
therefore needs glibc 2.39+.
Fedora/RHEL:
sudo dnf install gtk4-devel libadwaita-devel gstreamer1-devel \
gstreamer1-plugins-base-devel gcc pkg-configUbuntu/Debian:
sudo apt install libgtk-4-dev libadwaita-1-dev libgstreamer1.0-dev \
libgstreamer-plugins-base1.0-dev build-essential pkg-configArch:
sudo pacman -S gtk4 libadwaita gstreamer gst-plugins-base base-develPlaying Bluesky video needs GStreamer 1.20+ with these plugin sets installed at
runtime. -good carries souphttpsrc and hlsdemux2, -bad carries
tsdemux, and gst-libav carries avdec_h264.
- Ubuntu/Debian:
gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-libav - Fedora:
gstreamer1-plugins-good gstreamer1-plugins-bad-free gstreamer1-libav - Arch:
gst-plugins-good gst-plugins-bad gst-libav
The GTK video sink is compiled into Hangar, so gstreamer1.0-gtk4 is not
needed. When a stream cannot be played the viewer says so and offers to open
the post in a browser; it never does that on its own.
cargo build --release
cargo run --release┌─────────────────────────────────────────────┐
│ UI Layer │
│ GTK4 + Libadwaita widgets │
│ (window, sidebar, post_row, dialogs) │
└─────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ Application Layer │
│ Orchestrates login, data fetching, │
│ navigation, state management │
└─────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ AT Protocol Layer │
│ HangarClient wraps atrium │
│ Converts atrium types → app types │
└─────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ External Crates │
│ atrium-api, reqwest, tokio │
└─────────────────────────────────────────────┘
GTK runs on the main thread. Network I/O runs on background threads with a shared Tokio runtime. Results are sent back to the main thread via glib::timeout_add_local polling or glib::spawn_future_local. A semaphore limits concurrent API requests to 4.
| Component | Technology |
|---|---|
| Language | Rust (2024 edition) |
| UI Toolkit | GTK4 + Libadwaita |
| AT Protocol | atrium-api + atrium-oauth |
| HTTP Client | reqwest (native-tls, so OpenSSL) |
| Async Runtime | Tokio |
| Cache | rusqlite (SQLite, bundled) |
| Media | gstreamer-rs + gst-plugin-gtk4 (playbin3, gtk4paintablesink registered statically) |
| Secrets | secret-service (libsecret bindings) |
| Serialization | serde |
src/
├── main.rs # Entry point
├── app.rs # Application lifecycle, data fetching, navigation
├── config.rs # Constants (APP_ID, PDS URL)
├── runtime.rs # Shared Tokio runtime
├── media/
│ └── mod.rs # GStreamer init + playbin3 pipeline construction
├── atproto/
│ ├── client.rs # HangarClient, the AT Protocol wrapper
│ ├── facets.rs # Rich text facet parsing (mentions, links, hashtags)
│ └── types.rs # Post, Profile, Session, Notification types
├── cache/
│ ├── db.rs # SQLite database setup
│ ├── schema.rs # Table definitions
│ ├── posts.rs # Post cache operations
│ ├── feeds.rs # Feed cache operations
│ └── profiles.rs # Profile cache operations
├── state/
│ ├── oauth.rs # OAuth flow (PKCE + DPoP, localhost callback)
│ ├── session.rs # App password session persistence via libsecret
│ ├── session_store.rs # File-based OAuth session store
│ └── settings.rs # AppSettings + FontSize (persistent JSON)
└── ui/
├── window.rs # Main window, stack navigation, settings page
├── sidebar.rs # Navigation rail with avatar menu
├── post_row.rs # Post widget with embeds and actions
├── compose_dialog.rs # Rich compose (posts, replies, quotes, threads)
├── login_dialog.rs # Sign-in dialog
├── media_viewer.rs # Full-size image and video dialogs
├── video_player.rs # Video widget: picture, transport controls, error page
├── avatar_cache.rs # Image loading with LRU + SQLite caching
└── style.css # Custom CSS styles
If you find Hangar useful, consider supporting its development:
Hangar is open source under the MPL-2.0 license. Contributions are welcome.
Before contributing:
- Run
cargo fmt(required) - Address
clippywarnings - Follow existing code patterns
- Keep changes focused, one feature per PR
Resources: