Skip to content

Repository files navigation

Hangar

Hangar

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.

License Status Rust GTK4 AT Protocol Platform CI GitHub Release GitHub Downloads GitHub last commit GitHub issues Repo size

What is Hangar?

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.

Goals

  • 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

Current Status

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.

What Works

  • 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

What's Next

  • 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

Authentication

OAuth (Recommended)

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 (Fallback)

App password authentication is also supported as a fallback:

  1. Go to bsky.app/settings/app-passwords
  2. Create a new app password
  3. Use that password to log in to Hangar

App passwords can be revoked at any time without affecting your main account password.

Session Storage

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.

Installation

AppImage

Download the latest AppImage from the Releases page, make it executable, and run:

chmod +x Hangar-x86_64.AppImage
./Hangar-x86_64.AppImage

The 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 hangar

Updating

Hangar 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.

Flatpak (sideload)

Download the .flatpak bundle from the Releases page and install:

flatpak install hangar.flatpak

Video 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.

Build from Source

Dependencies

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-config

Ubuntu/Debian:

sudo apt install libgtk-4-dev libadwaita-1-dev libgstreamer1.0-dev \
  libgstreamer-plugins-base1.0-dev build-essential pkg-config

Arch:

sudo pacman -S gtk4 libadwaita gstreamer gst-plugins-base base-devel

Runtime plugins for video

Playing 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.

Build & Run

cargo build --release
cargo run --release

Architecture Overview

┌─────────────────────────────────────────────┐
│                  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           │
└─────────────────────────────────────────────┘

Threading Model

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.

Technology Stack

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

Project Structure

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

Support Hangar

If you find Hangar useful, consider supporting its development:

GitHub Sponsors Liberapay Buy Me a Coffee Ko-fi PayPal Stripe

Contributing

Hangar is open source under the MPL-2.0 license. Contributions are welcome.

Before contributing:

  • Run cargo fmt (required)
  • Address clippy warnings
  • Follow existing code patterns
  • Keep changes focused, one feature per PR

Resources:

License

Mozilla Public License 2.0

About

Hangar is your Bluesky home base—built natively for Linux. ✈️

Topics

Resources

Stars

23 stars

Watchers

0 watching

Forks

Releases

Used by

Contributors

Languages