111 KiB
Driftwood - Modern AppImage Manager
A GTK4/libadwaita application for managing, integrating, updating, and auditing AppImages on modern Linux desktops.
Last updated: February 26, 2026
Table of Contents
- Problem Statement
- Landscape Analysis
- Project Vision & Goals
- Target Users
- Architecture Overview
- Core Features - Detailed Design
- UI/UX Design
- Technical Stack & Dependencies
- AppImage Internals & Integration Points
- FUSE Compatibility Layer
- Wayland Awareness Engine
- Update System
- Sandboxing & Permissions
- Bundled Library Security Scanner
- Usage Tracking & Disk Space Reclamation
- Orphaned Config & Data Detection
- Duplicate & Multi-Version Detection
- Desktop Integration Manager
- Background Daemon
- CLI Interface
- Distribution & Self-Updating
- KDE/Plasma Support Strategy
- Accessibility
- Internationalization
- Testing Strategy
- Development Phases & Roadmap
- Project Naming & Branding
- Risk Analysis
- Community & Contribution Model
- Appendix: Technical Reference
1. Problem Statement
AppImages are the only Linux packaging format that delivers true single-file, distro-agnostic, no-root-required application distribution. In theory, you download one file, make it executable, and run it. In practice, the experience falls apart:
- No central management UI. Users accumulate AppImages in
~/Downloadswith no way to see what they have, what version it is, or whether an update exists. Gear Lever and the newer AppManager address parts of this but remain limited in scope. - Desktop integration is manual or fragmented. Users must either manually create
.desktopfiles and extract icons, or rely onappimaged(a daemon with no UI), AppImageLauncher (v3.0.0 now supports the new static runtime but still uses Qt5 and has a dated UX), Gear Lever (GNOME-only, Flatpak-distributed), or AppManager (newer, Vala/libadwaita, macOS-style - but still missing key features). - The Wayland-only era is here. GNOME 50 (March 2026) completely removes X11 backend code from Mutter and GNOME Shell. Ubuntu 26.04 LTS ships Wayland-only. KDE Plasma 6.8 (October 2026) drops its X11 session. Many AppImages still bundle older toolkits that fall back to XWayland - causing blurry HiDPI rendering, broken clipboard, missing gestures - and users have no idea why.
- FUSE2 deprecation broke everything. Ubuntu 22.04+ removed
libfuse2from default installs (renamed tolibfuse2t64on Ubuntu 24.04+). Every AppImage using FUSE mounting simply fails to launch with a cryptic error. The new type2-runtime attempts to support both fuse2 and fuse3 but has compatibility issues with stable AppImageLauncher installations. The workaround (--appimage-extract-and-run) is obscure and per-invocation. - Updates are invisible. AppImageUpdate (latest release October 2025) and zsync2 (v2.0.0-alpha, September 2025) exist but have no user-facing GUI integrated into a management workflow. Gear Lever v4.4.6 and AppManager v3.0.0 now offer some update support, but neither provides transparent delta updates or system-wide update orchestration.
- Orphaned desktop entries accumulate. Delete an AppImage file and the
.desktopentry and icon remain forever, cluttering menus with broken launchers.
This is the gap Pedro Innecco identified in his "Linux Deserves Better: The Future of AppImage Integration" blog post (September 2025): AppImage has all the right technical primitives but no cohesive user experience layer. Innecco argues that AppImage deserves to be elevated to a standard, freedesktop-level service - shipped by default, with a clear convention that every user can rely on - rather than remaining an optional add-on that leaves the format as a second-class citizen compared to Flatpak or Snap.
Driftwood is that missing layer.
2. Landscape Analysis
2.1 AppImageLauncher
| Aspect | Status |
|---|---|
| Repository | github.com/TheAssassin/AppImageLauncher |
| Toolkit | Qt5 (with libappindicator for tray) |
| Latest release | v3.0.0 (released 2025, after long beta cycle); v2.2.0 remains widely deployed |
| Maintenance | Active again after a long stall; v3.0.0 was a major effort to modernize |
| Wayland | Tray icon relies on StatusNotifierItem - works on KDE, still problematic on GNOME Wayland without extensions; integration dialogs functional but not native-feeling |
| FUSE | v3.0.0 adds support for the new static runtime (which can use fuse2 or fuse3); older v2.2.0 requires libfuse2 only |
| Distribution | Ubuntu PPAs are deprecated; GitHub releases only now |
What it does well: Intercepts AppImage launches via binfmt_misc and offers "integrate or run once" dialogs. Moves AppImages to ~/Applications. Creates .desktop files and extracts icons automatically. v3.0.0 now handles the new static runtime, ARM64 support, and fixes many daemon issues.
What's broken/missing: No update management. No version tracking. No Wayland awareness. The binfmt_misc hook is fragile (breaks on kernel updates, doesn't work in containers, conflicts with other handlers). The new type2-runtime has known compatibility issues with stable AppImageLauncher versions. UI looks dated (Qt5, not following any modern HIG). No sandbox integration. i386 support dropped in v3.0.0.
2.2 Gear Lever
| Aspect | Status |
|---|---|
| Repository | github.com/mijorus/gearlever |
| Toolkit | GTK4 / libadwaita (Python) |
| Latest version | v4.4.6 (February 2026) |
| Distribution | Flatpak via Flathub (primary) |
| Maintenance | Actively maintained, single maintainer |
What it does well: Modern GNOME-native UI. Drag-and-drop AppImage integration. Clean UX for the "open this AppImage and add it to my menu" workflow. As of v4.4.6: supports configuring update URLs per AppImage, adding environment variables and CLI arguments, offline caching of the AppImage index, Arabic/RTL localization. v4.3.0 added safety checks to block updates while an app is running.
What's missing: No automatic discovery/scanning of existing AppImages across directories. No delta updates (zsync). No Wayland audit or status reporting. No FUSE compatibility management or detection. No background daemon for watching directories. No bulk operations. No orphan desktop entry cleanup. No sandboxing. Limited to GNOME - no KDE adaptations. Being a Flatpak creates a philosophical tension (Flatpak managing AppImages) and portal permission issues for filesystem access.
2.3 AppManager
| Aspect | Status |
|---|---|
| Repository | github.com/kem-a/AppManager |
| Toolkit | GTK4 / libadwaita (Vala) |
| Latest version | v3.0.0 (February 2026) |
| Distribution | AppImage, Flatpak |
| Maintenance | Active |
What it does well: macOS-style drag-and-drop installer UX. Maintains an install registry. Supports both SquashFS and DwarFS AppImage formats. Has zsync delta update support and background auto-updates. Per-app configuration (CLI args, env vars, update servers). Can extract AppImages for faster launches. Multi-language support (15+ languages).
What's missing: No directory scanning/auto-discovery. No Wayland audit. No FUSE compatibility detection or management. No background daemon for file watching. No orphan cleanup. No sandboxing or permission auditing. No bulk operations across all managed AppImages. No CLI interface for scripting.
Why this matters for Driftwood: AppManager is the closest existing tool to what Driftwood aims to be. It validates the market but leaves significant gaps - particularly Wayland awareness, FUSE management, background daemon functionality, and the "system health" perspective that Driftwood provides.
2.4 appimaged
The official appimaged daemon from the AppImage project. Watches directories for AppImages and auto-creates desktop entries. No GUI. No update support. No FUSE handling. Useful as a reference implementation for the inotify-based file watching pattern but not a user-facing solution.
2.5 AM / AppMan
Community-driven CLI tool (github.com/ivan-hc/AM) that manages AppImage installation and updates via shell scripts. Since v5 (December 2023), AM and AppMan merged into a single script with two behaviors (system-wide vs local install). Features an extensible database of 2000+ portable apps, AppImage sandboxing, drag-and-drop integration, conversion of old AppImage types, and bulk update-all functionality. Comprehensive but entirely CLI-based - no GUI.
2.6 bauh
Multi-format package manager GUI (AppImage, Flatpak, Snap, native). Jack-of-all-trades, master of none. AppImage support is surface-level - wraps basic install/remove. Qt5-based, aging UI.
2.7 Gap Summary
| Feature | AppImageLauncher | Gear Lever | AppManager | appimaged | AM | Driftwood |
|---|---|---|---|---|---|---|
| Modern toolkit (GTK4/Adw) | No (Qt5) | Yes | Yes (Vala) | N/A | N/A | Yes (Rust) |
| Auto-discovery | Yes | No | No | Yes | No | Yes |
| Desktop integration | Yes | Yes | Yes | Yes | Partial | Yes |
| Delta updates (zsync) | No | No | Yes | No | No | Yes |
| Version tracking | No | Partial | Yes | No | Partial | Yes |
| Wayland audit | No | No | No | No | No | Yes |
| FUSE compat mgmt | Partial (v3) | No | No | No | No | Yes |
| Orphan cleanup | No | No | No | No | No | Yes |
| Sandboxing | No | No | No | No | Yes | Yes |
| Background daemon | Partial | No | No | Yes | No | Yes |
| KDE support | Partial | No | No | Yes | N/A | Yes |
| Self-updating AppImage | No | N/A | Yes | N/A | N/A | Yes |
| DwarFS support | No | No | Yes | No | No | Planned |
| Bundled library CVE scan | No | No | No | No | No | Yes |
| Usage tracking / disk reclaim | No | No | No | No | No | Yes |
| Orphaned config detection | No | No | No | No | No | Yes |
| Duplicate/version detection | No | No | No | No | No | Yes |
3. Project Vision & Goals
Vision
Make AppImage a first-class citizen on modern Linux desktops - as easy to manage as Flatpak, as lightweight as the format promises, and as well-integrated as native packages. Built for the Wayland-only era that GNOME 50 and KDE Plasma 6.8 are bringing in 2026.
Goals
- Zero-friction onboarding. Launch Driftwood, it finds all your AppImages, shows their status, and offers to integrate them - in one click.
- Wayland-native, Wayland-aware. Driftwood itself runs natively on Wayland, and it tells you which of your AppImages don't. With GNOME 50 (March 2026) and KDE Plasma 6.8 (October 2026) both going Wayland-only, this information is no longer optional - it's essential.
- Update without re-downloading. Leverage zsync2/AppImageUpdate for delta updates where available; fall back to full re-download via GitHub Releases API or OCS where needed.
- Clean system, always. No orphaned
.desktopfiles, no ghost icons, no stale entries. Driftwood owns the lifecycle. - FUSE just works. Detect the FUSE situation, offer to install
libfuse2, or transparently use--appimage-extract-and-runas needed. - Security-conscious. Optional sandboxing via bubblewrap/firejail, permission auditing, hash verification.
- Itself an AppImage. Dogfooding. Driftwood is distributed as an AppImage that can update itself.
Non-Goals
- Not a package manager. Driftwood doesn't download AppImages from a catalog/store (that's a separate concern - AM/AppMan covers it well). It manages what's already on disk.
- Not a Flatpak/Snap replacement. No opinion on other formats. Coexists peacefully.
- Not a sandboxing framework. Integrates with existing tools, doesn't reinvent containment.
4. Target Users
Primary
- Intermediate Linux users who download AppImages from project websites and want them properly integrated without manual
.desktopfile editing. - Users migrating from Windows/macOS who expect a "programs list" experience.
- Power users managing 10-50+ AppImages who need bulk operations, version tracking, and update management.
Secondary
- System administrators deploying AppImages in managed environments (multi-user, kiosk).
- AppImage developers testing their own packages' integration, Wayland behavior, and update metadata.
5. Architecture Overview
┌──────────────────────────────────────────┐
│ Driftwood GUI │
│ (GTK4 / libadwaita) │
│ │
│ ┌─────────┐ ┌──────────┐ ┌───────────┐ │
│ │ Library │ │ Details │ │ Settings │ │
│ │ View │ │ View │ │ View │ │
│ └────┬────┘ └────┬─────┘ └─────┬─────┘ │
│ │ │ │ │
│ ┌────┴───────────┴─────────────┴─────┐ │
│ │ Application Controller │ │
│ └────────────────┬────────────────────┘ │
└───────────────────┼───────────────────────┘
│
┌───────────────────┼───────────────────────┐
│ Core Services Layer │
│ │
│ ┌──────────┐ ┌──────────┐ ┌───────────┐ │
│ │Discovery │ │ Desktop │ │ Update │ │
│ │ Engine │ │Integrator│ │ Manager │ │
│ └──────────┘ └──────────┘ └───────────┘ │
│ ┌──────────┐ ┌──────────┐ ┌───────────┐ │
│ │ FUSE │ │ Wayland │ │ Sandbox │ │
│ │ Manager │ │ Auditor │ │ Manager │ │
│ └──────────┘ └──────────┘ └───────────┘ │
│ ┌──────────┐ ┌──────────┐ ┌───────────┐ │
│ │ Security │ │ Usage │ │ Duplicate │ │
│ │ Scanner │ │ Tracker │ │ Detector │ │
│ └──────────┘ └──────────┘ └───────────┘ │
│ ┌──────────────────┐ ┌─────────────────┐ │
│ │ AppImage │ │ Config/Data │ │
│ │ Inspector │ │ Tracker │ │
│ │ (ELF, squashfs) │ │ (fanotify/proc) │ │
│ └──────────────────┘ └─────────────────┘ │
└──────────────────┬────────────────────────┘
│
┌──────────────────┼────────────────────────┐
│ Data Layer │
│ ┌──────────┐ ┌──────────────────────┐ │
│ │ SQLite │ │ XDG Config/Data dirs │ │
│ │ Database │ │ (~/.local/share/ │ │
│ │ │ │ driftwood/) │ │
│ └──────────┘ └──────────────────────┘ │
└──────────────────┬────────────────────────┘
│
┌──────────────────┼────────────────────────┐
│ Background Daemon (driftwoodd) │
│ - inotify file watcher │
│ - periodic update checker │
│ - D-Bus interface │
│ - desktop notification sender │
└───────────────────────────────────────────┘
Language Choice: Python vs C vs Rust vs Vala
| Language | Pros | Cons | Verdict |
|---|---|---|---|
| Python + PyGObject | Fast dev, Gear Lever proves GTK4/Adw works, large contributor pool | Slower for ELF parsing, packaging complexity | Good for MVP |
| Vala | First-class GObject/GTK support, compiles to C, GNOME ecosystem native | Smaller community, fewer contributors | Good for GNOME-native |
| Rust + gtk4-rs | Memory safety, performance, growing GNOME adoption (Loupe, Glycin) | Steeper contributor barrier, longer compile times | Best long-term |
| C + GTK4 | Maximum performance, traditional GNOME | Memory safety burden, slow development | Not recommended |
Recommendation: Rust with gtk4-rs and libadwaita-rs.
Rationale:
- GNOME is increasingly adopting Rust (Loupe image viewer, Glycin, numerous new apps). Ubuntu 26.04 LTS itself is introducing Rust-based core utilities.
- Memory safety matters for a tool that parses untrusted ELF binaries and squashfs images
gtk4-rsandlibadwaita-rsare mature and actively maintained (last updated February 2026)- Performance for file scanning, ELF parsing, and squashfs inspection is important
- Attracts contributors from the growing Rust-for-Linux-desktop community
- Compiles to a single binary - perfect for AppImage distribution
Note on AppManager's approach: AppManager (the closest competitor) chose Vala, which validates the "compile to native code" instinct but Rust offers stronger safety guarantees for the ELF/squashfs parsing that Driftwood does heavily.
Fallback recommendation: Python + PyGObject for a faster MVP if contributor velocity is prioritized over performance. Gear Lever proves this stack works well for GTK4/Adw apps.
6. Core Features - Detailed Design
6.1 AppImage Discovery Engine
Purpose: Find all AppImages on the system, regardless of where the user dropped them.
Default scan locations (configurable):
~/Applications/ # XDG convention for user-installed apps
~/Downloads/ # Where browsers drop files
~/.local/bin/ # Common PATH location
~/Desktop/ # Some users put apps here
/opt/appimages/ # System-wide convention (requires root)
Detection methods (layered, in order):
-
Magic bytes check. AppImages (Type 2) contain the magic bytes
AI\x02at ELF offset. Type 1 uses ISO 9660 magic. Check the ELF header for the AppImage magic at the section header offset field. -
File extension.
.AppImage,.appimage,.app- but don't rely on this alone; many AppImages have no extension. -
ELF + squashfs heuristic. If magic bytes are absent, check if the file is an ELF binary with an embedded squashfs filesystem (read the ELF section headers, look for the squashfs magic
hsqsat the offset indicated by the AppImage runtime). -
Executable bit check. Non-executable AppImages are flagged as "discovered but not runnable" - offer to
chmod +x.
Performance considerations:
- Initial scan: parallel directory walk using
tokioorrayon(Rust) /concurrent.futures(Python) - Subsequent updates:
inotifywatches on configured directories - Cache results in SQLite; rescan only on file change events
- Skip known non-AppImage files based on extension blacklist (
.txt,.pdf, etc.) - Set reasonable file size minimum (AppImages are typically > 1MB)
Discovery output (per AppImage):
struct DiscoveredAppImage {
path: PathBuf,
filename: String,
appimage_type: AppImageType, // Type1 | Type2 | Unknown
size_bytes: u64,
modified_time: SystemTime,
is_executable: bool,
sha256: Option<String>, // computed on demand
// Extracted from embedded .desktop / AppStream:
app_name: Option<String>,
app_version: Option<String>,
app_icon: Option<Icon>,
categories: Vec<String>,
desktop_entry: Option<DesktopEntry>,
update_info: Option<UpdateInfo>,
// Runtime analysis:
fuse_requirement: FuseRequirement, // Fuse2 | Fuse3 | ExtractAndRun
wayland_status: Option<WaylandStatus>,
integrated: bool, // has matching .desktop file
}
6.2 AppImage Inspector
Purpose: Extract all available metadata from an AppImage without running it.
Extraction methods:
For Type 2 AppImages (vast majority, squashfs-based):
- Read the ELF header to find the AppImage runtime offset
- Read the
.appimage_update_infosection from the ELF (update URL) - Mount (via FUSE) or extract (via
unsquashfs/squashfuse) the embedded filesystem - Parse
.desktopfile from the squashfs root - Extract icon (
.DirIcon, or icon referenced in.desktop) - Parse
AppStreammetadata XML if present (usr/share/metainfo/*.appdata.xml) - Check for
AppRunscript to determine runtime behavior - Detect runtime type: legacy fuse2-only runtime vs new static runtime (fuse2/fuse3 capable)
For Type 1 AppImages (legacy, ISO 9660-based):
- Mount as ISO or extract with
bsdtar - Same metadata extraction from the contained filesystem
For DwarFS-based AppImages (emerging format, used by some projects):
- Detect DwarFS magic bytes instead of squashfs
- Use
dwarfsextractordwarfsFUSE mount for inspection - Same metadata extraction from the contained filesystem
- Note: AppManager v3.0.0 already supports DwarFS; Driftwood should match this
Inspection without mounting (preferred path for FUSE2-less systems):
- Use
--appimage-extract --appimage-offsetto find the squashfs offset - Use the
squashfs-toolslibrary orunsquashfsto list/extract specific files without full mount - Extract only:
.desktop, icon,*.appdata.xml,AppRun- no need to extract the full payload
6.3 Library View (Main Window)
The primary UI. Shows all managed AppImages in a grid or list.
Per-AppImage card/row displays:
- App icon (extracted from the AppImage)
- App name (from
.desktopName=field) - Version (from AppStream,
.desktop, or filename heuristic) - File size
- Location on disk
- Integration status: integrated / not integrated / orphaned entry
- Update status: up to date / update available / unknown / checking
- Wayland status badge: Native Wayland / XWayland / Unknown
- FUSE status badge: OK / needs libfuse2 / using extract-and-run
Sorting/filtering:
- By name, size, date added, date modified, update status
- Filter by: integrated/not, has updates, Wayland status, category
Bulk operations:
- Select multiple → integrate all / remove all / update all
- "Integrate all discovered" one-click action
6.4 Detail View
Opened by clicking an AppImage in the library. Full information panel:
Sections:
-
Identity
- Name, version, description (from AppStream)
- Icon (large)
- Categories, keywords
- Developer/maintainer info (from AppStream)
-
File Info
- Full path
- File size
- SHA256 hash (computed, with copy button)
- AppImage type (1 or 2)
- Created/modified timestamps
- Permissions (executable bit)
-
Desktop Integration
- Status: integrated / not integrated
.desktopfile path (if integrated)- Icon installation path
- "Integrate" / "Remove integration" button
- Preview of the generated
.desktopfile
-
Runtime Compatibility
- FUSE status with explanation and fix button
- Wayland status with detailed explanation
- Libraries bundled vs system (if detectable)
- Architecture (x86_64, aarch64)
-
Updates
- Current version
- Update information type (zsync, GitHub releases, OCS, none)
- Update URL
- Last checked timestamp
- "Check now" / "Update" buttons
- Update history
-
Security & Library Audit
- Signature status (if AppImage is signed)
- GPG key info
- Sandbox status: not sandboxed / firejail / bubblewrap
- Permissions audit (what the AppImage can access)
- "Run sandboxed" toggle
- Bundled library CVE summary (critical/high/medium counts)
- Expandable list of flagged libraries with CVE details
- "Scan now" button to refresh CVE check
- Comparison: bundled library version vs system version
-
Usage & Disk Footprint
- Last launched date/time
- Launch count (total, last 30 days)
- AppImage file size
- Config data size (in
~/.config/) - Application data size (in
~/.local/share/) - Cache size (in
~/.cache/) - Total footprint (file + config + data + cache)
- "This AppImage hasn't been used in N months" warning if stale
-
Duplicates
- "2 other versions of this app found" warning if applicable
- Links to the other copies
- "Keep this, remove others" quick action
6.5 Settings View
General:
- Scan directories (add/remove paths)
- Default AppImage storage location (default:
~/Applications) - Move AppImages to storage location on integration (toggle)
- Language
- Theme (follow system / light / dark)
Integration:
- Auto-integrate newly discovered AppImages (toggle, default: off - ask first)
- Desktop entry template customization
- Custom icon theme integration
- Auto-cleanup orphaned entries (toggle, default: on)
Updates:
- Check frequency (never / daily / weekly / on launch)
- Auto-update (toggle, default: off - notify only)
- Keep old versions on update (toggle)
- Number of old versions to keep
FUSE:
- Preferred FUSE strategy: native mount / extract-and-run / auto-detect
- Show "install libfuse2" prompts (toggle)
Sandboxing:
- Default sandbox backend: none / firejail / bubblewrap
- Default permission profile: permissive / standard / strict
- Per-app override capability
Security Scanner:
- CVE database update frequency (weekly / daily / manual)
- Notification threshold (critical only / high+ / medium+ / all)
- Show system vs bundled version comparisons (toggle)
Usage & Cleanup:
- Enable launch tracking (toggle, default: on)
- Stale threshold (1 month / 3 months / 6 months / 1 year)
- Show disk usage in library view (toggle)
- Track config/data paths via fanotify (toggle, default: on)
- Backup config before cleanup (toggle, default: on)
Duplicates:
- Auto-detect duplicates on scan (toggle, default: on)
- Auto-clean old versions on update (toggle, default: off - prompt instead)
- Number of old versions to keep when auto-cleaning
Daemon:
- Enable background daemon (toggle)
- Start on login (toggle)
- Notification preferences
7. UI/UX Design
7.1 Design Language
Follow GNOME Human Interface Guidelines (HIG) strictly. Target libadwaita 1.7+ (GNOME 48) with an eye toward 1.8 (GNOME 50):
- Use
AdwApplicationWindowwithAdwHeaderBar - Use
AdwNavigationViewfor main → detail navigation - Use
AdwPreferencesWindowfor settings - Use
AdwStatusPagefor empty states - Use
AdwToastOverlayfor transient notifications - Use
AdwBannerfor persistent warnings (e.g., "FUSE2 not installed") - Use
AdwDialogfor modal interactions (libadwaita 1.5+) - Respect the new Adwaita Sans / Adwaita Mono default fonts (GNOME 48+, replacing Cantarell / Source Code Pro)
7.2 Key Screens
Empty State (First Launch)
┌─────────────────────────────────────────┐
│ ◀ Driftwood ☰ ⚙ │
├─────────────────────────────────────────┤
│ │
│ [Driftwood icon] │
│ │
│ No AppImages Found │
│ │
│ Driftwood will look for AppImages in │
│ ~/Applications and ~/Downloads. │
│ │
│ Drop an AppImage here or configure │
│ scan locations in Settings. │
│ │
│ [ Scan Now ] [ Settings ] │
│ │
└─────────────────────────────────────────┘
Library View (Populated)
┌─────────────────────────────────────────┐
│ ◀ Driftwood (12 apps) 🔍 ☰ ⚙ │
├─────────────────────────────────────────┤
│ ⚠ 3 updates available [Update All│
├─────────────────────────────────────────┤
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ [icon] │ │ [icon] │ │ [icon] │ │
│ │ Firefox │ │ Kdenlive │ │ Inkscape │ │
│ │ v124.0 │ │ v24.02 │ │ v1.3.2 │ │
│ │ 🟢 Wayl. │ │ 🟡 XWay. │ │ 🟢 Wayl. │ │
│ │ ⬆ Update │ │ ✓ Latest │ │ ⬆ Update │ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ [icon] │ │ [icon] │ │ [icon] │ │
│ │ Krita │ │ Blender │ │ MuseScore│ │
│ │ v5.2.2 │ │ v4.1 │ │ v4.2 │ │
│ │ 🟡 XWay. │ │ 🟢 Wayl. │ │ 🔴 X11 │ │
│ │ ✓ Latest │ │ ⬆ Update │ │ ✓ Latest │ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ │
└─────────────────────────────────────────┘
Detail View
┌─────────────────────────────────────────┐
│ ◁ Back Kdenlive ⋮ │
├─────────────────────────────────────────┤
│ │
│ [large icon] Kdenlive │
│ Video Editor │
│ Version 24.02.1 │
│ KDE Community │
│ │
│ [ Launch ] [ Launch Sandboxed ] │
│ │
├─────────────────────────────────────────┤
│ Desktop Integration [ ✓ ] │
│ ~/.local/share/.../kdenlive.desktop │
│ │
│ Wayland Status │
│ 🟡 Running via XWayland │
│ Bundled Qt 5.15 - no native Wayland │
│ protocol support detected. │
│ │
│ FUSE Status │
│ 🟢 Using system libfuse2 │
│ │
│ Updates │
│ ℹ zsync update info embedded │
│ Last checked: 2 hours ago │
│ [ Check for Updates ] │
│ │
│ File Details │
│ Path: ~/Applications/Kdenlive.App... │
│ Size: 287 MB │
│ SHA256: a3f2e1... [Copy] │
│ Type: AppImage Type 2 (squashfs) │
│ │
└─────────────────────────────────────────┘
7.3 Notification & Warning Design
AdwBanner (persistent, top of library):
- "libfuse2 is not installed. Some AppImages may not launch. [Install] [Use extract-and-run]"
- "3 orphaned desktop entries found. [Clean up]"
- "Background daemon is not running. [Enable]"
AdwToast (transient, bottom):
- "Firefox updated to v125.0"
- "Desktop entry created for Inkscape"
- "Removed 3 orphaned entries"
System notifications (via daemon):
- "2 AppImage updates available" (clicking opens Driftwood)
- "New AppImage discovered in ~/Downloads"
7.4 Drag and Drop
The main window accepts AppImage files via drag and drop:
- User drags
.AppImagefile onto window - Drop zone highlights
- On drop: inspect → show detail view → offer integration
- Optionally move to
~/Applicationson integration
8. Technical Stack & Dependencies
Build System
meson.build # Primary build system
├── src/
│ ├── meson.build
│ ├── main.rs
│ ├── application.rs
│ ├── window.rs
│ ├── core/
│ │ ├── discovery.rs
│ │ ├── inspector.rs
│ │ ├── integrator.rs
│ │ ├── updater.rs
│ │ ├── fuse.rs
│ │ ├── wayland.rs
│ │ ├── sandbox.rs
│ │ ├── security_scanner.rs
│ │ ├── usage_tracker.rs
│ │ ├── config_tracker.rs
│ │ ├── duplicate_detector.rs
│ │ ├── cve_database.rs
│ │ └── database.rs
│ ├── ui/
│ │ ├── library_view.rs
│ │ ├── detail_view.rs
│ │ ├── settings_view.rs
│ │ ├── app_card.rs
│ │ └── widgets/
│ ├── daemon/
│ │ ├── main.rs
│ │ ├── watcher.rs
│ │ ├── update_checker.rs
│ │ └── dbus_interface.rs
│ └── cli/
│ └── main.rs
├── data/
│ ├── icons/
│ ├── driftwood.desktop
│ ├── driftwood.metainfo.xml
│ ├── driftwood.gschema.xml
│ └── resources.gresource.xml
├── po/ # Translations
└── tests/
Dependencies
Build-time:
- Rust toolchain (stable, latest)
- Meson >= 0.64
- gtk4 >= 4.16 (GNOME 48+)
- libadwaita >= 1.7 (GNOME 48+; target 1.8 for GNOME 50 features)
- SQLite3
- gettext (i18n)
Runtime (required):
- GTK4, libadwaita (linked)
- SQLite3
unsquashfsorsquashfs-tools(for AppImage inspection without FUSE)- D-Bus (for daemon communication)
Runtime (optional, for full functionality):
libfuse2orlibfuse3(for AppImage mounting)zsync2orlibzsync(for delta updates)firejailorbubblewrap(for sandboxing)libnotify/ notification daemon (for system notifications)gpg/gpgme(for signature verification)
GSettings Schema
<schemalist>
<schema id="io.github.driftwood" path="/io/github/driftwood/">
<key name="scan-directories" type="as">
<default>['~/Applications', '~/Downloads']</default>
</key>
<key name="storage-directory" type="s">
<default>'~/Applications'</default>
</key>
<key name="move-on-integrate" type="b">
<default>true</default>
</key>
<key name="auto-integrate" type="b">
<default>false</default>
</key>
<key name="update-check-interval" type="s">
<default>'weekly'</default>
</key>
<key name="auto-update" type="b">
<default>false</default>
</key>
<key name="keep-old-versions" type="b">
<default>false</default>
</key>
<key name="fuse-strategy" type="s">
<default>'auto'</default>
</key>
<key name="sandbox-backend" type="s">
<default>'none'</default>
</key>
<key name="daemon-enabled" type="b">
<default>true</default>
</key>
<key name="daemon-autostart" type="b">
<default>true</default>
</key>
<key name="cve-scan-interval" type="s">
<default>'weekly'</default>
</key>
<key name="cve-notify-threshold" type="s">
<default>'high'</default>
</key>
<key name="launch-tracking-enabled" type="b">
<default>true</default>
</key>
<key name="stale-threshold-days" type="i">
<default>90</default>
</key>
<key name="config-tracking-enabled" type="b">
<default>true</default>
</key>
<key name="backup-config-on-cleanup" type="b">
<default>true</default>
</key>
<key name="auto-detect-duplicates" type="b">
<default>true</default>
</key>
<key name="auto-clean-old-versions" type="b">
<default>false</default>
</key>
<key name="old-versions-to-keep" type="i">
<default>1</default>
</key>
</schema>
</schemalist>
9. AppImage Internals & Integration Points
9.1 AppImage Type 2 Structure (Current Standard)
┌─────────────────────────────────────┐
│ ELF Runtime Binary │ ← Executable header
│ - Contains AppImage magic AI\x02 │
│ - Contains update info in ELF │
│ section .upd_info │
│ - Handles FUSE mounting │
│ - Offset to squashfs payload │
├─────────────────────────────────────┤
│ SquashFS Filesystem │ ← Compressed payload
│ ├── AppRun │ ← Entry point script/binary
│ ├── *.desktop │ ← Desktop entry (exactly one)
│ ├── *.png / *.svg │ ← Application icon
│ ├── usr/ │ ← App files
│ │ ├── bin/
│ │ ├── lib/
│ │ └── share/
│ │ ├── applications/
│ │ ├── icons/
│ │ └── metainfo/ │ ← AppStream metadata
│ │ └── *.appdata.xml
│ └── .DirIcon │ ← Fallback icon
└─────────────────────────────────────┘
9.2 Desktop Entry Generation
When integrating an AppImage, Driftwood generates a .desktop file:
Location: ~/.local/share/applications/driftwood-{app_id}.desktop
Template:
[Desktop Entry]
Type=Application
Name={app_name}
Exec={appimage_path} %U
Icon={icon_path}
Categories={categories}
Comment={comment}
Terminal=false
X-AppImage-Path={appimage_path}
X-AppImage-Version={version}
X-AppImage-Managed-By=Driftwood
X-AppImage-Integrated-Date={iso_date}
The X-AppImage-* fields are crucial - they let Driftwood:
- Identify entries it created vs user-created ones
- Detect orphans when the AppImage file is deleted
- Track version for update comparison
Icon installation:
- Extract icon from AppImage
- Install to
~/.local/share/icons/hicolor/{size}x{size}/apps/driftwood-{app_id}.png - Support SVG icons in
scalable/apps/when available - Run
gtk-update-icon-cacheorxdg-icon-resourceas needed
9.3 Orphan Detection
An orphaned desktop entry is one where:
- The
.desktopfile containsX-AppImage-Managed-By=Driftwood - The
X-AppImage-Pathpoints to a file that no longer exists
Cleanup actions:
- Remove the
.desktopfile - Remove the installed icon files
- Run
update-desktop-database ~/.local/share/applications/ - Log the cleanup action
Scan trigger:
- On every Driftwood launch
- Periodically via daemon
- On user request
10. FUSE Compatibility Layer
10.1 The Problem
AppImage Type 2 uses FUSE to mount the embedded squashfs at runtime. This requires:
libfuse2on the system (for most existing AppImages compiled against fuse2)- The
fusermountbinary - The
/dev/fusedevice node (usually present)
Ubuntu 22.04+ removed libfuse2 from the default install. Ubuntu 24.04 renamed the package to libfuse2t64 (64-bit time_t transition). libfuse3 is available but is not ABI-compatible - AppImages compiled against fuse2 cannot use fuse3. The two can coexist, but users don't know this.
The new type2-runtime (updated January 2026) attempts to support both fuse2 and fuse3, preferring fuse3 when available. However, it has known compatibility issues with stable versions of AppImageLauncher - creating a fragmented situation where upgrading one tool can break another.
10.2 Detection Logic
function detect_fuse_status():
has_fuse2 = check_library("libfuse.so.2")
has_fuse3 = check_library("libfuse3.so.3")
has_fusermount = which("fusermount") or which("fusermount3")
has_dev_fuse = exists("/dev/fuse")
if has_fuse2 and has_fusermount and has_dev_fuse:
return FuseStatus::FullyFunctional
elif has_fuse3 and not has_fuse2:
return FuseStatus::Fuse3Only // Most AppImages won't work
elif not has_fusermount:
return FuseStatus::NoFusermount
elif not has_dev_fuse:
return FuseStatus::NoDevFuse // Container/WSL issue
else:
return FuseStatus::MissingLibfuse2
10.3 Resolution Strategies
Strategy 1: Install libfuse2 (preferred)
- Detect distro via
/etc/os-release - Show the correct install command:
- Ubuntu 24.04+/Debian testing+:
sudo apt install libfuse2t64(renamed package) - Ubuntu 22.04/Debian 12:
sudo apt install libfuse2 - Fedora:
sudo dnf install fuse-libs(usually present) - Arch:
sudo pacman -S fuse2 - openSUSE:
sudo zypper install libfuse2
- Ubuntu 24.04+/Debian testing+:
- Offer to run the command via pkexec (polkit elevation)
Strategy 2: Extract-and-run (transparent fallback)
- When FUSE is unavailable, launch AppImages with
--appimage-extract-and-run - This extracts to a temp directory and runs from there
- Slower startup, uses more temp disk space
- Driftwood handles this transparently - user sees "Running in compatibility mode"
Strategy 3: New static runtime (already shipping)
- The new type2-runtime (updated January 2026) statically links libfuse and can use either fuse2 or fuse3, preferring fuse3 when available
- AppImages built with recent
appimagetooluse this runtime by default - Driftwood should detect which runtime an AppImage uses and report it
- Driftwood could offer to re-pack older AppImages with the new runtime (advanced feature, Phase 3+)
- Caveat: The new runtime has known compatibility issues with AppImageLauncher stable (v2.2.0) - Driftwood should warn about this if AppImageLauncher is detected on the system
10.4 Per-AppImage FUSE Status
Each AppImage in the library shows one of:
- 🟢 Native FUSE mount - libfuse2/fuse3 available, working normally
- 🟢 Static runtime - uses new type2-runtime with embedded FUSE, works with fuse2 or fuse3
- 🟡 Extract-and-run mode - working but with slower startup
- 🔴 Cannot launch - FUSE unavailable and extract-and-run failed
- ⚠️ AppImageLauncher conflict - new static runtime detected but AppImageLauncher v2.2.0 is installed (known compatibility issue)
11. Wayland Awareness Engine
11.1 Why This Matters - The Wayland-Only Era (2026)
The Linux desktop has crossed the Wayland tipping point. As of early 2026:
- GNOME 49 (September 2025): X11 session disabled by default
- GNOME 50 (March 18, 2026): X11 backend code completely removed from Mutter and GNOME Shell. Wayland-only.
- Ubuntu 25.10: Dropped GNOME-on-Xorg entirely
- Ubuntu 26.04 LTS (April 2026): Will ship GNOME 50 - Wayland-only, with XWayland for legacy apps
- KDE Plasma 6.8 (October 2026): Dropping the X11 session, going Wayland-only. X11 session supported until early 2027 via Plasma 6.7.
- 79% of KDE Plasma 6 users are already on Wayland
- Over 60% of all Linux desktops use Wayland by default
This means XWayland is no longer a "fallback" - it's the only way X11-toolkit apps can run on mainstream desktops. Applications running under XWayland experience:
- Blurry rendering on HiDPI/fractional scaling (GNOME 50 includes initial stable fractional scaling and VRR support, making the gap more visible)
- Broken or inconsistent clipboard behavior
- No smooth touchpad gestures
- Potential security issues (X11 allows keylogging between XWayland apps)
- Higher memory/CPU usage
- Missing Wayland features (per-monitor scaling, direct scanout, HDR)
Users currently have no way to know which of their AppImages run native Wayland vs XWayland without manually checking. With X11 sessions disappearing entirely, this is no longer a nice-to-have - it's critical information. Driftwood provides it.
11.2 Detection Methods
Method 1: Static analysis (pre-launch, preferred)
Inspect the bundled libraries inside the AppImage:
Libraries that indicate Wayland support:
libwayland-client.so.* → Direct Wayland protocol
libGdk-4.0.so.* → GTK4 (Wayland-native by default)
libgtk-4.so.* → GTK4
libQt6WaylandClient.so.* → Qt6 Wayland platform plugin
libQt5WaylandClient.so.* → Qt5 with Wayland plugin
Libraries that indicate X11-only:
libGdk-3.0.so.* WITHOUT libwayland → GTK3 without Wayland
libgtk-x11-*.so.* → GTK2 (X11 only)
libQt5X11Extras.so.* → Qt5 X11-specific
Presence of Wayland platform plugins:
usr/lib/*/qt5/plugins/platforms/libqwayland*.so
usr/lib/*/qt6/plugins/platforms/libqwayland*.so
Electron-based apps (version matters significantly):
Electron >= 38 (Sept 2025+): --ozone-platform=auto is DEFAULT, native Wayland
Electron 28-37: Look for --ozone-platform-hint=auto in wrapper scripts
Electron < 28: X11 only or buggy Wayland
Method 2: Runtime detection (post-launch)
After launching an AppImage, check its Wayland status:
# Check the process's environment
cat /proc/{pid}/environ | tr '\0' '\n' | grep -E '^(WAYLAND_DISPLAY|GDK_BACKEND|QT_QPA_PLATFORM|XDG_SESSION_TYPE)'
# Check if the process has an X11 connection
ls -la /proc/{pid}/fd/ | grep -c '.X11-unix'
# Use xdotool/xprop to check if window appears in X11
xdotool search --pid {pid} # If found, it's using X11/XWayland
# Check via compositor protocol
# GNOME: org.gnome.Shell.Introspect D-Bus interface
# KDE: org.kde.KWin.Windows D-Bus interface
Method 3: Toolkit version heuristic
| Toolkit | Wayland Status |
|---|---|
| GTK4 | Native Wayland (default) |
| GTK3 >= 3.22 | Native Wayland (if GDK_BACKEND not forced to x11) |
| GTK2 | X11 only (forever) |
| Qt6 + QtWayland | Native Wayland |
| Qt5 + QtWayland | Native Wayland (if platform plugin present) |
| Qt5 without QtWayland | XWayland fallback |
| Electron >= 38 | Native Wayland by default (--ozone-platform=auto is now the default as of Electron 38.0, September 2025) |
| Electron 28-37 | Native Wayland with --ozone-platform-hint=auto flag |
| Electron 12-27 | Wayland possible with flags but buggy |
| Electron < 12 | X11 only |
| Java/Swing/JavaFX | X11 only (as of early 2026 - Wakefield project for Wayland support is still experimental) |
| wxWidgets (GTK3) | Depends on GTK3 backend |
| Flutter (Linux) | Native Wayland (GTK backend) |
11.3 Wayland Status Display
Each AppImage gets a Wayland badge:
- 🟢 Native Wayland - detected Wayland-capable toolkit and platform plugins
- 🟡 XWayland - will run but via X11 compatibility layer
- 🟠 Wayland possible - toolkit supports it but plugins may be missing or env vars needed
- 🔴 X11 only - toolkit has no Wayland path
- ⚪ Unknown - couldn't determine (uncommon toolkit, static binary)
For 🟡 and 🟠 apps, Driftwood can suggest or apply fixes:
- Set
GDK_BACKEND=waylandfor GTK3 apps - Set
QT_QPA_PLATFORM=waylandfor Qt5 apps with QtWayland present - Add
--ozone-platform-hint=autofor Electron apps - Show a warning that these are per-app overrides and may cause issues
11.4 Session Detection
Driftwood itself needs to know the session type:
fn detect_session_type() -> SessionType {
if env::var("WAYLAND_DISPLAY").is_ok() {
if env::var("DISPLAY").is_ok() {
SessionType::WaylandWithXWayland
} else {
SessionType::PureWayland
}
} else if env::var("DISPLAY").is_ok() {
SessionType::X11
} else {
SessionType::Unknown
}
}
On the few remaining X11 sessions (Linux Mint Cinnamon, some legacy setups), the Wayland audit is still useful - with GNOME 50 and KDE Plasma 6.8 going Wayland-only in 2026, users will be forced to switch soon. Badges should note "You're currently on X11 - Wayland status will matter when your desktop upgrades to Wayland-only."
12. Update System
12.1 Update Information Spec
AppImages can embed update information in the ELF binary's .upd_info section. Driftwood reads this to determine how to check for and download updates.
Supported update info formats:
| Type | Format | Example |
|---|---|---|
| zsync | zsync|{url} |
zsync|https://example.com/app-latest-x86_64.AppImage.zsync |
| GitHub Releases | gh-releases-zsync|{owner}|{repo}|{release_tag}|{filename_pattern} |
gh-releases-zsync|user|repo|latest|*x86_64.AppImage.zsync |
| GitLab Releases | gl-releases-zsync|{host}|{owner}|{repo}|{release_tag}|{filename_pattern} |
Similar to GitHub |
| OCS | ocs-v1-appimagehub-direct|{url} |
For AppImageHub-listed apps |
| bintray | bintray-zsync|... |
Deprecated (Bintray shut down) |
12.2 Update Check Flow
1. Read update info from ELF section
2. Based on type:
a. zsync: HEAD request to zsync URL, compare SHA headers
b. GitHub: Query GitHub Releases API for latest release
- GET https://api.github.com/repos/{owner}/{repo}/releases/latest
- Compare tag/version against current
c. GitLab: Similar via GitLab API
d. OCS: Query OCS endpoint
3. If update available:
a. Record new version, download URL, file size
b. Show notification / badge in library
c. Compute delta size if zsync available
4. Store result in database with timestamp
12.3 Delta Update (zsync2)
The zsync protocol enables downloading only the changed blocks of an AppImage:
- Download the
.zsynccontrol file (small, contains block checksums) - Compare checksums against the local AppImage's blocks
- Download only the changed blocks
- Reconstruct the new AppImage from local blocks + downloaded deltas
- Verify full-file checksum
- Replace old AppImage with new one
Typical savings: 60-90% bandwidth reduction for minor version updates.
Implementation options:
- Shell out to
AppImageUpdatebinary (simplest, if installed) - Shell out to
zsync2binary - Use
libappimageupdateas a library (C API) - Implement zsync protocol in Rust (most work, most control)
Recommended: Shell out to AppImageUpdate (latest release October 2025) with fallback to full download. zsync2 v2.0.0-alpha (September 2025) is stabilizing but still alpha quality - the API is usable but debugging is ongoing. Shelling out provides isolation from upstream instability.
12.4 Full Download Fallback
When zsync is not available or the AppImage has no update info:
- For GitHub-hosted AppImages: use GitHub Releases API to find the latest
.AppImageasset - Download to a temporary location
- Verify (checksum if available, at minimum check it's a valid AppImage)
- Replace old file (atomic rename)
- Re-extract desktop entry and icon (version may have changed)
- Update database
12.5 Update UI
In library view:
- Badge on app cards: "⬆ Update available"
- Banner: "N updates available [Update All]"
Update dialog:
┌────────────────────────────────────────┐
│ Update Available │
├────────────────────────────────────────┤
│ │
│ Firefox 124.0 → 125.0 │
│ │
│ Update size: ~45 MB (delta) │
│ Full size: 287 MB │
│ Method: zsync (GitHub Releases) │
│ │
│ ☐ Keep old version │
│ │
│ [ Cancel ] [ Update ] │
│ │
└────────────────────────────────────────┘
During update:
- Progress bar with speed and ETA
- Cancel button
- "Updated successfully" toast on completion
13. Sandboxing & Permissions
13.1 Rationale
AppImages are unsigned, unreviewed binaries from the internet. Unlike Flatpak (which has a permissions model) or Snap (which has AppArmor confinement), AppImages run with the user's full permissions by default. Driftwood offers optional sandboxing to mitigate this.
13.2 Backend Options
Firejail:
- Mature, widely packaged (v0.9.78 released January 2026, with emergency fix for GTK library compatibility changes)
- Pre-built profiles for many applications
- Simple wrapper:
firejail --appimage /path/to/app.AppImage - The
--appimageflag handles the FUSE mount correctly inside the sandbox - Good default choice
Bubblewrap (bwrap):
- Lower-level, more flexible
- Used internally by Flatpak
- Requires more configuration per app
- Better for custom permission profiles
Driftwood approach:
- Default: no sandbox (matches current AppImage behavior)
- One-click "Run sandboxed" using firejail (if installed) with default profile
- Custom profiles per app (advanced, stored in
~/.config/driftwood/sandbox/) - Show what a sandbox would restrict (permission audit view)
13.3 Permission Audit
Even without sandboxing, Driftwood can show what an AppImage could access:
Potential Access:
✓ Full home directory read/write
✓ Network access (unrestricted)
✓ X11 display (if running via XWayland - enables keylogging)
✓ D-Bus session bus
✓ All mounted filesystems
✓ GPU acceleration
⚠ No Wayland isolation (X11 clients can see each other)
This is informational - educating users about the security model they're operating under.
13.4 Firejail Profile Generation
For apps without a pre-existing firejail profile, Driftwood can generate a basic one:
# Auto-generated by Driftwood for {app_name}
include disable-common.inc
include disable-devel.inc
include disable-exec.inc
include disable-interpreters.inc
include disable-programs.inc
whitelist ${HOME}/Documents
whitelist ${HOME}/Downloads
whitelist ${HOME}/Pictures
caps.drop all
netfilter
no3d
nodvd
nogroups
nonewprivs
noroot
nosound
notv
nou2f
novideo
protocol unix,inet,inet6
seccomp
Users can then customize from this starting point.
14. Bundled Library Security Scanner
14.1 The Problem
AppImages are self-contained - they bundle their own copies of libraries like OpenSSL, libcurl, zlib, libwebp, GnuTLS, SQLite, libpng, libjpeg, and dozens more. When a CVE is discovered in one of these libraries, your system package manager patches the system copy within hours or days. But the copy inside the AppImage never gets patched unless the AppImage developer rebuilds and re-releases.
This means users can be running applications with known-vulnerable OpenSSL, known-exploitable libwebp (remember CVE-2023-4863, the zero-click WebP exploit that hit Chrome/Firefox/Signal?), or ancient zlib with buffer overflows - and have absolutely no visibility into it.
No existing AppImage tool inspects bundled libraries for known vulnerabilities. Driftwood does.
14.2 How It Works
Phase 1: Library inventory extraction
When Driftwood inspects an AppImage (during discovery or on demand), it inventories the bundled shared libraries:
1. Extract or mount the AppImage's squashfs/dwarfs payload
2. Walk the filesystem looking for:
- usr/lib/**/*.so*
- usr/lib64/**/*.so*
- lib/**/*.so*
- Any .so files in the top-level directory
3. For each shared library found:
a. Read the ELF SONAME from the dynamic section
b. Extract version strings using heuristics:
- SONAME version suffix (libssl.so.1.1 → OpenSSL 1.1.x)
- .rodata section string scanning for version patterns
(e.g., "OpenSSL 1.1.1k 25 Mar 2021")
- Symbol version tables (OPENSSL_1_1_1 version tag)
- pkg-config .pc files if bundled (usr/lib/pkgconfig/)
c. Record: library name, SONAME, detected version, file path, file size
Output per AppImage:
struct BundledLibrary {
soname: String, // e.g., "libssl.so.1.1"
detected_name: String, // e.g., "OpenSSL"
detected_version: String,// e.g., "1.1.1k"
file_path: String, // e.g., "usr/lib/x86_64-linux-gnu/libssl.so.1.1"
file_size: u64,
cve_matches: Vec<CveMatch>,
}
Phase 2: CVE matching
Driftwood maintains a local vulnerability database, updated periodically:
1. Data sources (fetched by daemon or on-demand):
a. NVD (National Vulnerability Database) JSON feeds
- https://nvd.nist.gov/feeds/json/cve/1.1/
- Filtered to Linux-relevant libraries only
b. OSV (Open Source Vulnerabilities) database
- https://osv.dev/ - structured, easy to query
- Has a simple REST API and downloadable dumps
c. Debian Security Tracker data
- Maps CVEs to specific library versions
- Already has the "which versions are affected" mappings done
2. Local database structure:
- Table of known libraries (name, CPE identifier, version ranges)
- Table of CVEs with affected version ranges and severity (CVSS)
- Updated weekly (or daily if user opts in)
- Stored in SQLite alongside the main Driftwood database
- Total size estimate: 5-15 MB compressed
3. Matching algorithm:
For each bundled library:
a. Normalize the library name to a CPE product name
(libssl.so → openssl, libcurl.so → curl, libz.so → zlib)
b. Look up all CVEs affecting that product
c. Check if the detected version falls within the affected range
d. Record matching CVEs with severity and description
Phase 3: Reporting
Per-AppImage security report in the detail view:
Security Audit - Kdenlive 24.02.1
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🔴 2 Critical 🟠 5 High 🟡 3 Medium
Libraries scanned: 47
Libraries with known CVEs: 4
🔴 OpenSSL 1.1.1k (bundled)
System has: 3.0.13
CVE-2024-0727 (Critical, CVSS 9.8) - PKCS12 decoding crash
CVE-2023-5678 (High, CVSS 7.5) - Excessive time in DH check
+ 3 more
🟠 libcurl 7.81.0 (bundled)
System has: 8.5.0
CVE-2023-38545 (High, CVSS 7.5) - SOCKS5 heap overflow
+ 2 more
🟢 zlib 1.2.13 (bundled) - No known CVEs
🟢 libpng 1.6.39 (bundled) - No known CVEs
Recommendation: Update this AppImage to the latest version.
The developer may have rebuilt with patched libraries.
14.3 Key Libraries to Track
These are the highest-value targets for CVE scanning - libraries that are frequently bundled in AppImages and have regular security-critical updates:
| Library | Why it matters |
|---|---|
| OpenSSL / LibreSSL / GnuTLS | TLS/crypto - CVEs can be catastrophic |
| libcurl | HTTP client - SOCKS5, redirect, and auth vulns |
| zlib / zlib-ng | Compression - buffer overflows |
| libwebp | Image decoding - CVE-2023-4863 was a zero-click RCE |
| libpng / libjpeg-turbo | Image codecs - parsing vulnerabilities |
| SQLite | Database - query injection, overflow |
| libxml2 / libxslt | XML parsing - XXE, buffer overflows |
| FFmpeg / libav* | Media codecs - extremely frequent CVEs |
| GLib / GIO | GNOME foundation - occasional vulns |
| Qt5 / Qt6 core libs | Widget toolkit - rendering, input handling vulns |
| Electron / Chromium (libcef) | If bundled: inherits all Chromium CVEs |
| Python / Node.js | If bundled as runtime: interpreter-level CVEs |
14.4 Comparison with System Libraries
A particularly useful feature: show the user what version their system has vs what the AppImage bundles:
Library AppImage System Status
OpenSSL 1.1.1k 3.0.13 ⚠ AppImage is 3 major versions behind
libcurl 7.81.0 8.5.0 ⚠ AppImage is behind
zlib 1.2.13 1.3.1 ℹ Minor version difference
libpng 1.6.39 1.6.43 ℹ Patch version difference
This immediately tells the user: "your system is patched, but the AppImage is running its own old, unpatched copy."
14.5 Severity Thresholds and Notifications
- Critical (CVSS >= 9.0): Red badge in library view. Desktop notification via daemon. Prominent banner in detail view.
- High (CVSS >= 7.0): Orange badge. Included in periodic notifications.
- Medium (CVSS >= 4.0): Yellow indicator in detail view only.
- Low (CVSS < 4.0): Listed in full report, no badges.
Users can configure notification thresholds in settings.
14.6 Limitations and Honest Framing
This feature must be honest about what it can and cannot do:
- It cannot guarantee completeness. Version detection from binary inspection is heuristic. Some libraries don't embed version strings.
- It cannot tell you if a CVE is actually exploitable in the context of how the AppImage uses the library. A CVE in OpenSSL's PKCS12 parsing doesn't matter if the app never touches PKCS12.
- It's informational, not blocking. Driftwood never prevents you from launching an AppImage. It informs you of the risk.
- The fix is upstream. Driftwood can't patch the libraries inside an AppImage. It can only tell you to update the AppImage (which may or may not include patched libraries) or contact the developer.
This is framed as: "Here's what you should know about what's inside this binary you downloaded from the internet."
15. Usage Tracking & Disk Space Reclamation
15.1 The Problem
AppImages are big. A typical Electron app is 150-300 MB. Media apps like Kdenlive or Blender can be 300-500 MB. Users accumulate these over time, forget about them, and end up with gigabytes of AppImages they never launch sitting in ~/Applications or ~/Downloads.
Unlike Flatpak (which has flatpak uninstall --unused) or system packages (which have apt autoremove), there's no tooling to identify stale, unused AppImages.
15.2 How It Works
Launch tracking:
Driftwood wraps AppImage launches (when launched via its desktop entries or directly through Driftwood) and records usage:
1. When Driftwood creates a .desktop entry, the Exec= line goes through Driftwood:
Exec=driftwood launch /path/to/App.AppImage %U
The "driftwood launch" subcommand:
a. Records a launch event (timestamp, AppImage path) in the database
b. Immediately exec()s the actual AppImage (zero overhead after the DB write)
c. The AppImage runs as a direct child - no wrapper process stays resident
2. For AppImages launched outside Driftwood (e.g., from terminal):
- The background daemon can optionally monitor /proc for running AppImage
processes and record "seen running" events
- This is lightweight: poll /proc every 60 seconds, check for processes
whose exe symlink points to a known AppImage path
- Less precise than direct tracking but catches manual launches
3. Launch events are stored in SQLite:
CREATE TABLE launch_events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
appimage_id INTEGER REFERENCES appimages(id),
launched_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
source TEXT -- 'desktop_entry', 'cli', 'detected'
);
Disk space analysis:
$ driftwood usage
AppImage Disk Usage Summary
━━━━━━━━━━━━━━━━━━━━━━━━━━
Total: 14 AppImages using 4.7 GB
By last used:
Not launched in 6+ months (1.8 GB):
⚠ Audacity 3.4.2 287 MB Last used: 8 months ago
⚠ Godot 4.2 398 MB Last used: 11 months ago
⚠ LibreOffice 7.6 1.1 GB Last used: 7 months ago
Not launched in 1-6 months (920 MB):
ℹ Blender 4.1 412 MB Last used: 3 months ago
ℹ Krita 5.2.2 508 MB Last used: 2 months ago
Active - used in last 30 days (2.0 GB):
✓ Firefox 125.0 287 MB Last used: today
✓ Kdenlive 24.02.1 312 MB Last used: 2 days ago
✓ Inkscape 1.3.2 198 MB Last used: 5 days ago
... 6 more
Never launched (450 MB):
❓ SomeApp-x86_64 150 MB Discovered: 4 months ago
❓ AnotherTool 300 MB Discovered: 2 weeks ago
Potential savings: 2.25 GB from unused AppImages
15.3 UI Integration
Library view enhancements:
- Optional "Last used" column in list view
- Sort by "last used" to surface stale AppImages
- Filter: "Not used in 3+ months" / "Never launched"
- Color coding: recently used (normal) → stale (dimmed) → never used (dimmed + indicator)
Dashboard / summary widget:
- Disk usage pie chart or bar showing space by usage category
- "You could reclaim X GB" call-to-action
- "Clean up" button that shows a checklist of stale AppImages to remove
Cleanup flow:
┌─────────────────────────────────────────┐
│ Reclaim Disk Space │
├─────────────────────────────────────────┤
│ │
│ These AppImages haven't been used │
│ recently. Remove them to free space. │
│ │
│ ☑ Audacity 3.4.2 287 MB │
│ Last used 8 months ago │
│ │
│ ☑ Godot 4.2 398 MB │
│ Last used 11 months ago │
│ │
│ ☐ LibreOffice 7.6 1.1 GB │
│ Last used 7 months ago │
│ │
│ Selected: 685 MB to reclaim │
│ │
│ [ Cancel ] [ Remove Selected ] │
│ │
└─────────────────────────────────────────┘
15.4 Privacy Considerations
- All usage data is local-only (SQLite database, never transmitted)
- Users can disable launch tracking entirely in settings
- "Clear usage history" button in settings
- Usage data is deleted when an AppImage is removed from Driftwood
- No tracking of what the user does inside the AppImage - only launch/exit events
16. Orphaned Config & Data Detection
16.1 The Problem
When you delete an AppImage, the application's configuration and data files remain scattered across your home directory. Unlike Flatpak (which confines app data to ~/.var/app/{app-id}/) or Snap (which uses ~/snap/{app}/), AppImages write to the same locations as natively-installed apps - and those locations are unpredictable:
~/.config/{app-name}/- settings~/.local/share/{app-name}/- application data~/.cache/{app-name}/- cached data (can be huge for media apps)~/.{app-name}or~/.{app-name}rc- legacy dotfiles~/Documents/{app-name}/- user-created content (should NOT auto-delete)- Various other XDG and non-XDG paths
After removing an AppImage, users have no idea what got left behind or how to clean it up. Over time, this accumulates into gigabytes of orphaned config, cache, and data scattered across hundreds of hidden directories.
16.2 How It Works
Phase 1: Data path discovery (learning phase)
Driftwood learns where each AppImage stores its data by monitoring filesystem activity during launches:
Method A: Filesystem monitoring via fanotify (preferred, Linux 5.1+)
1. Before launching an AppImage, set up fanotify watches on key directories:
- ~/.config/
- ~/.local/share/
- ~/.local/state/
- ~/.cache/
- ~/
2. Launch the AppImage
3. Monitor fanotify events for FAN_CREATE, FAN_MODIFY, FAN_OPEN_PERM
filtered to the AppImage's PID and child PIDs
4. Record all paths written to or created by the AppImage process tree
5. After the AppImage exits, store the discovered paths in the database
6. Over multiple launches, build a comprehensive map of where this
AppImage reads and writes
Method B: /proc scanning fallback (less precise, no special permissions)
1. While an AppImage is running, periodically (every 30s) scan:
- /proc/{pid}/fd/ - open file descriptors
- /proc/{pid}/maps - memory-mapped files
- /proc/{pid}/io - I/O stats (to detect active writing)
2. Record paths that are open for writing within the home directory
3. Less comprehensive than fanotify (misses files that were
opened and closed between scans) but requires no special
permissions
Method C: Heuristic matching (no runtime monitoring needed)
1. Extract the app name from the AppImage metadata
(e.g., "kdenlive" from the .desktop file)
2. Search for matching directories in standard XDG locations:
~/.config/kdenlive/
~/.local/share/kdenlive/
~/.cache/kdenlive/
~/.config/kdenliverc (KDE-style config)
3. Also check common variations:
- Case variations (Kdenlive, KDENLIVE, kdenlive)
- With org prefix (org.kde.kdenlive)
- Flatpak-style reverse DNS (org.kde.kdenlive)
- Electron-style (~/.config/{app-name}/ containing
Preferences, Local Storage, etc.)
4. Score matches by confidence:
- Exact name match in standard XDG dir: high confidence
- Case-insensitive match: medium confidence
- Partial name match: low confidence (flag for user review)
Phase 2: Data path storage
struct AppDataPath {
appimage_id: i64,
path: PathBuf,
path_type: DataPathType, // Config, Data, Cache, State, Other
discovery_method: Method, // Fanotify, ProcScan, Heuristic
confidence: Confidence, // High, Medium, Low
first_seen: DateTime,
last_accessed: DateTime,
size_bytes: u64,
contains_user_content: bool, // True for ~/Documents paths - don't auto-delete
}
enum DataPathType {
Config, // ~/.config/{app}/
Data, // ~/.local/share/{app}/
Cache, // ~/.cache/{app}/
State, // ~/.local/state/{app}/
DotFile, // ~/.{app}rc, ~/.{app}/
UserData, // ~/Documents/{app}/ - flagged, never auto-deleted
Other,
}
Phase 3: Orphan detection and cleanup
When an AppImage is removed (either deleted from disk or removed via Driftwood):
1. Look up all known data paths for this AppImage
2. Check which paths still exist on disk
3. Calculate total size of orphaned data
4. Present to user:
Kdenlive was removed. The following data remains:
Config:
~/.config/kdenliverc 4 KB
~/.config/kdenlive/ 12 KB
Cache (safe to delete):
~/.cache/kdenlive/ 340 MB
Application data:
~/.local/share/kdenlive/ 28 MB
⚠ User content (review before deleting):
~/Videos/Kdenlive Projects/ 1.2 GB
Total reclaimable: 380 MB (+ 1.2 GB user content)
[ Keep All ] [ Delete Cache Only ] [ Delete All Config/Cache ]
16.3 Safety Rules
This feature handles user data, so safety is paramount:
- Never auto-delete anything. Always present a list and let the user choose.
- Separate cache from config from user content. Cache is always safe to delete. Config is usually safe. User-created content (documents, projects, saves) should be prominently warned about.
- Low-confidence matches require explicit confirmation. If a path was found heuristically with low confidence, show it separately: "We think this might belong to {app} but aren't sure."
- Dry-run by default. The cleanup dialog shows what would be deleted. The user clicks confirm.
- Backup option. Offer to tar the config before deleting: "Save a backup of Kdenlive's config to ~/driftwood-backups/ before removing?"
- Never touch paths outside $HOME without explicit user action and polkit elevation.
16.4 Proactive Data Mapping
Even before an AppImage is removed, Driftwood can show the data footprint in the detail view:
Data Footprint - Kdenlive 24.02.1
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
AppImage file: 312 MB
Configuration: 16 KB (~/.config/kdenlive/)
Application data: 28 MB (~/.local/share/kdenlive/)
Cache: 340 MB (~/.cache/kdenlive/)
Total footprint: 680 MB
Detection method: fanotify (high confidence)
Last scanned: 2 hours ago
This gives users visibility into the true cost of each AppImage - not just the file itself but everything it sprawls across the filesystem.
17. Duplicate & Multi-Version Detection
17.1 The Problem
Users commonly end up with duplicate or multiple versions of the same AppImage without realizing it:
- Downloaded Firefox in January →
~/Downloads/Firefox-124.0-x86_64.AppImage - Downloaded Firefox update in March →
~/Downloads/Firefox-125.0-x86_64.AppImage - Moved an older copy to Applications →
~/Applications/Firefox.AppImage - Now there are 3 Firefox AppImages consuming 860 MB total
This happens because:
- Browsers save new downloads without overwriting old ones
- AppImage filenames often include version numbers, so each version is a separate file
- No tool correlates "these are all the same application at different versions"
- Users forget what they've already downloaded
17.2 How It Works
Detection algorithm:
1. Group AppImages by identity (not filename):
For each discovered AppImage, extract identity fingerprints:
a. Desktop entry Name= field (strongest signal)
"Name=Firefox Web Browser" → identity: "firefox"
b. Desktop entry WM_CLASS / StartupWMClass
c. AppStream <id> element (e.g., "org.mozilla.firefox")
d. Executable name from AppRun or Exec= line
e. Filename heuristic: strip version numbers, architecture,
and extension from filename
"Firefox-125.0-x86_64.AppImage" → "firefox"
"Krita-5.2.2-x86_64.appimage" → "krita"
2. Normalize and cluster:
a. Lowercase all identity strings
b. Strip common suffixes (-bin, -latest, -stable, -nightly)
c. Group AppImages sharing any identity fingerprint
d. Within each group, sort by version (extracted from metadata)
3. Flag groups with more than one member:
- If versions differ: "Multiple versions detected"
- If versions are identical: "Exact duplicate detected"
- If one is in ~/Downloads and another in ~/Applications:
"Likely an unmoved copy"
Version comparison:
struct DuplicateGroup {
canonical_name: String, // e.g., "Firefox"
appimages: Vec<DuplicateEntry>, // sorted by version, newest first
total_size: u64,
potential_savings: u64, // size of all but the newest
duplicate_type: DuplicateType,
}
enum DuplicateType {
ExactDuplicate, // Same app, same version, different locations
MultipleVersions, // Same app, different versions
NightlyAndStable, // Same app, different release channels
}
struct DuplicateEntry {
appimage_id: i64,
path: PathBuf,
version: Option<SemanticVersion>,
size: u64,
last_modified: DateTime,
last_launched: Option<DateTime>, // from usage tracking
is_integrated: bool, // has a .desktop entry
}
17.3 UI Integration
Library view:
- Duplicate groups get a subtle badge: "2 versions" or "duplicate"
- Optional filter: "Show duplicates only"
Duplicate resolution dialog:
┌──────────────────────────────────────────────┐
│ Duplicate Detected: Firefox │
├──────────────────────────────────────────────┤
│ │
│ 3 copies of Firefox found (860 MB total): │
│ │
│ ★ Firefox 125.0 287 MB │
│ ~/Applications/Firefox-125.0.AppImage │
│ Last used: today │
│ Integrated: ✓ │
│ │
│ Firefox 124.0 287 MB │
│ ~/Downloads/Firefox-124.0.AppImage │
│ Last used: 2 months ago │
│ Integrated: ✗ │
│ │
│ Firefox 124.0 287 MB │
│ ~/Applications/Firefox.AppImage │
│ Last used: 3 months ago │
│ Integrated: ✗ (orphaned .desktop exists) │
│ │
│ ★ = Recommended to keep (newest, integrated) │
│ │
│ Removing the other 2 would save 574 MB │
│ │
│ [ Keep All ] [ Remove Old Versions ] │
│ [ Remove Duplicates ] │
│ │
└──────────────────────────────────────────────┘
17.4 Smart Recommendations
When duplicates are detected, Driftwood recommends which to keep based on:
- Version: Newest version preferred (unless user explicitly kept an older one)
- Integration status: The one with an active
.desktopentry preferred - Location:
~/Applicationspreferred over~/Downloads - Usage: Most recently launched preferred
- Nightly vs stable: If both channels are present, assume intentional - don't recommend removal, just inform
17.5 Automatic Cleanup on Update
When Driftwood updates an AppImage (via zsync or full download):
1. New version downloaded to temp location
2. Verify new AppImage is valid
3. Replace old AppImage (atomic rename)
4. Old version optionally kept:
a. If "keep old versions" is enabled:
Move to ~/Applications/.driftwood-old/Firefox-124.0.AppImage
Maintain a configurable limit (default: 1 old version)
Auto-prune beyond the limit
b. If disabled: old version is deleted after successful replacement
5. Update .desktop entry with new version info
6. If any OTHER copies of this AppImage exist in other directories,
notify the user: "You also have an older Firefox in ~/Downloads.
Remove it?"
17.6 CLI Integration
$ driftwood duplicates
Duplicate AppImages Found
━━━━━━━━━━━━━━━━━━━━━━━━
Firefox (3 copies, 860 MB):
★ v125.0 ~/Applications/Firefox-125.0.AppImage 287 MB
v124.0 ~/Downloads/Firefox-124.0.AppImage 287 MB
v124.0 ~/Applications/Firefox.AppImage 287 MB
Krita (2 copies, 1016 MB):
★ v5.2.2 ~/Applications/Krita-5.2.2.AppImage 508 MB
v5.1.5 ~/Downloads/Krita-5.1.5.AppImage 508 MB
Total potential savings: 1.4 GB
$ driftwood duplicates --clean
Remove 3 old/duplicate AppImages to save 1.4 GB? [y/N]
18. Desktop Integration Manager
18.1 Integration Flow
User clicks "Integrate" on an AppImage
│
▼
┌─ Move AppImage to ~/Applications? ─┐
│ (if not already there and │
│ move-on-integrate is enabled) │
└──────────┬──────────────────────────┘
▼
┌─ Extract metadata ─┐
│ - .desktop file │
│ - icon(s) │
│ - AppStream XML │
└──────────┬──────────┘
▼
┌─ Generate .desktop file ─┐
│ - Use extracted Name, │
│ Icon, Categories │
│ - Set Exec= to AppImage │
│ path (with FUSE │
│ wrapper if needed) │
│ - Add X-AppImage-* keys │
└──────────┬───────────────┘
▼
┌─ Install icon ─┐
│ - Copy to │
│ hicolor │
│ theme dirs │
└──────────┬─────┘
▼
┌─ Update databases ─┐
│ - update-desktop- │
│ database │
│ - gtk-update-icon- │
│ cache │
└──────────┬──────────┘
▼
┌─ Update Driftwood DB ─┐
│ - Mark as integrated │
│ - Store paths │
│ - Record timestamp │
└───────────────────────┘
18.2 Exec Line Generation
The Exec= line needs to handle several scenarios:
Normal (FUSE works):
Exec=/home/user/Applications/Firefox.AppImage %U
FUSE fallback (extract-and-run):
Exec=env APPIMAGE_EXTRACT_AND_RUN=1 /home/user/Applications/Firefox.AppImage %U
With Wayland hints:
Exec=env GDK_BACKEND=wayland /home/user/Applications/SomeGtk3App.AppImage %U
With sandboxing:
Exec=firejail --appimage /home/user/Applications/UntrustedApp.AppImage %U
Combined:
Exec=env APPIMAGE_EXTRACT_AND_RUN=1 GDK_BACKEND=wayland firejail --appimage /home/user/Applications/App.AppImage %U
18.3 Multi-Desktop Support
GNOME: Primary target. .desktop files in ~/.local/share/applications/ with standard freedesktop.org format. GNOME Shell picks them up automatically after update-desktop-database.
KDE Plasma: Same .desktop files work. KDE uses kbuildsycoca6 to rebuild its cache. Driftwood runs this when detected on KDE. KDE-specific X-KDE-* keys can be added.
Other (XFCE, Cinnamon, MATE, etc.): All follow freedesktop.org standards. Should work with the GNOME path. May need xfce4-panel --restart type commands for menu refresh.
19. Background Daemon
19.1 Purpose
driftwoodd runs in the background to:
- Watch configured directories for new/removed AppImages (via
inotify) - Periodically check for updates
- Detect and flag orphaned desktop entries
- Send desktop notifications
- Provide a D-Bus API for the GUI and CLI
19.2 D-Bus Interface
<node>
<interface name="io.github.Driftwood.Manager">
<!-- Methods -->
<method name="Scan">
<annotation name="org.freedesktop.DBus.Description"
value="Trigger a full rescan of all configured directories"/>
</method>
<method name="GetAppImages">
<arg direction="out" type="a(ssssbbb)" name="appimages"/>
<!-- Returns array of (path, name, version, status, integrated, has_update, wayland_native) -->
</method>
<method name="Integrate">
<arg direction="in" type="s" name="path"/>
<arg direction="out" type="b" name="success"/>
</method>
<method name="RemoveIntegration">
<arg direction="in" type="s" name="path"/>
<arg direction="out" type="b" name="success"/>
</method>
<method name="CheckUpdates">
<annotation name="org.freedesktop.DBus.Description"
value="Check all AppImages for available updates"/>
</method>
<method name="UpdateAppImage">
<arg direction="in" type="s" name="path"/>
<arg direction="out" type="b" name="success"/>
</method>
<method name="CleanOrphans">
<arg direction="out" type="u" name="removed_count"/>
</method>
<!-- Signals -->
<signal name="AppImageDiscovered">
<arg type="s" name="path"/>
<arg type="s" name="name"/>
</signal>
<signal name="AppImageRemoved">
<arg type="s" name="path"/>
</signal>
<signal name="UpdateAvailable">
<arg type="s" name="path"/>
<arg type="s" name="current_version"/>
<arg type="s" name="new_version"/>
</signal>
<signal name="UpdateProgress">
<arg type="s" name="path"/>
<arg type="d" name="progress"/> <!-- 0.0 to 1.0 -->
</signal>
</interface>
</node>
19.3 Autostart
When enabled, Driftwood installs an autostart entry:
# ~/.config/autostart/driftwoodd.desktop
[Desktop Entry]
Type=Application
Name=Driftwood Daemon
Exec=driftwood --daemon
NoDisplay=true
X-GNOME-Autostart-enabled=true
X-KDE-autostart-phase=1
19.4 Resource Usage
The daemon must be extremely lightweight:
- No GUI, no GTK dependency when running as daemon
- Idle memory target: < 15 MB RSS
- inotify watches: one per configured directory (not recursive - avoids exhausting inotify limits)
- Update checks: batched, rate-limited, with exponential backoff on failures
- Wakes up only on events (inotify) or timer (update check interval)
20. CLI Interface
driftwood # Launch GUI
driftwood --daemon # Start background daemon
driftwood list # List all known AppImages
driftwood list --format=json # Machine-readable output
driftwood scan # Trigger rescan
driftwood integrate <path> # Integrate an AppImage
driftwood integrate --all # Integrate all discovered
driftwood remove <path> # Remove integration
driftwood update <path> # Update specific AppImage
driftwood update --all # Update all with available updates
driftwood check # Check for updates
driftwood inspect <path> # Show metadata for an AppImage
driftwood audit # Show Wayland + FUSE status for all
driftwood clean # Remove orphaned desktop entries
driftwood security [path] # CVE scan for one or all AppImages
driftwood usage # Show disk usage and last-used report
driftwood duplicates # Show duplicate/multi-version AppImages
driftwood duplicates --clean # Interactive removal of old duplicates
driftwood footprint <path> # Show config/data/cache footprint of an AppImage
driftwood launch <path> [args] # Launch an AppImage with usage tracking
driftwood version # Show Driftwood version
Example output:
$ driftwood list
Name Version Wayland FUSE Updates Integrated
Firefox 124.0 🟢 Native 🟢 OK ⬆ 125.0 ✓
Kdenlive 24.02.1 🟡 XWayl 🟢 OK ✓ Latest ✓
Inkscape 1.3.2 🟢 Native 🟡 E&R ⬆ 1.4.0 ✓
MuseScore 4.2.0 🔴 X11 🟢 OK ✓ Latest ✗
MyApp unknown ⚪ N/A 🟢 OK - None ✗
5 AppImages found, 3 integrated, 2 updates available
$ driftwood audit
FUSE Status:
libfuse2: NOT INSTALLED
libfuse3: installed (3.14.0)
fusermount: /usr/bin/fusermount3
/dev/fuse: present
⚠ Most AppImages require libfuse2. Install with: sudo apt install libfuse2
Driftwood is using extract-and-run mode as fallback.
Wayland Status:
Session: Wayland (GNOME 46 on Mutter)
XWayland: available
Per-app Wayland audit:
🟢 Firefox 124.0 - GTK3 with Wayland backend
🟡 Kdenlive 24.02.1 - Qt5 without QtWayland plugin
🟢 Inkscape 1.3.2 - GTK4 (native Wayland)
🔴 MuseScore 4.2.0 - Qt5 without Wayland, X11 only
21. Distribution & Self-Updating
21.1 AppImage Packaging
Driftwood itself is distributed as an AppImage. This is intentional dogfooding - it proves the format works and gives users a zero-dependency installation experience.
Build process:
- Compile the Rust binary with static linking where possible
- Bundle GTK4, libadwaita, and all Rust dependencies
- Use
linuxdeploywith thegtkplugin to create the AppImage - Embed update information pointing to GitHub Releases
- Sign the AppImage with GPG
AppImage contents:
Driftwood.AppImage
├── AppRun
├── driftwood.desktop
├── driftwood.svg
├── usr/
│ ├── bin/
│ │ └── driftwood # Main binary (GUI + daemon + CLI)
│ ├── lib/
│ │ ├── libgtk-4.so.*
│ │ ├── libadwaita-1.so.*
│ │ ├── libsqlite3.so.*
│ │ └── ... (other deps)
│ └── share/
│ ├── applications/
│ │ └── driftwood.desktop
│ ├── icons/
│ ├── glib-2.0/schemas/
│ │ └── io.github.driftwood.gschema.xml
│ └── metainfo/
│ └── driftwood.metainfo.xml
└── .upd_info # zsync update info → GitHub Releases
21.2 Self-Updating
Driftwood uses its own update system on itself:
- On launch, check for updates (respecting the user's update check interval)
- If update available, show banner: "A new version of Driftwood is available [Update]"
- Download the new AppImage to a temp location
- Verify checksum / signature
- Replace the current AppImage file (atomic rename)
- Prompt user to restart: "Driftwood has been updated. Restart to use the new version."
21.3 Alternative Distribution
For users who prefer it:
- Flatpak (Flathub) - for those who want sandboxed Driftwood (like Gear Lever's approach)
- AUR (Arch) -
driftwood-bin(AppImage) anddriftwood-git(build from source) - Copr (Fedora) - RPM packaging
- PPA (Ubuntu) - Debian packaging, targeting Ubuntu 24.04 LTS and 26.04 LTS
- Nix - nixpkg for NixOS users
The AppImage is the primary distribution channel. Others are community-maintained. Ubuntu 26.04 LTS (April 2026) with its Wayland-only GNOME 50 and unified App Center is the ideal first target for mainstream adoption.
22. KDE/Plasma Support Strategy
While Driftwood targets GTK4/libadwaita primarily, KDE Plasma is a first-class desktop that deserves good support - especially as KDE Plasma 6.8 (October 2026) goes Wayland-only, meaning KDE users will face the same XWayland visibility issues that Driftwood addresses.
22.1 Approach: Single GTK4 App with KDE Adaptations
Rather than maintaining two toolkit versions, Driftwood ships as a GTK4 app but adapts to KDE:
Visual integration:
- GTK4 apps on KDE Plasma 6 look good when
xdg-desktop-portal-kdeandkde-gtk-configare installed - Colors and fonts follow KDE's Breeze theme via the portal
- libadwaita's adaptive colors work reasonably well with Breeze
Functional adaptations:
- Detect
$XDG_CURRENT_DESKTOP=KDEand adjust:- Use
kbuildsycoca6instead of / in addition toupdate-desktop-database - Support KDE's service menus if needed
- Use KDE notification service (
org.freedesktop.Notifications- same D-Bus interface) - File dialogs: use
xdg-desktop-portal-kdefor native KDE file picker - Wayland audit: use
org.kde.KWinD-Bus interface for window inspection instead of GNOME's introspection API
- Use
22.2 Future: Qt6 Version
If demand warrants it, a Qt6/KDE Frameworks version (driftwood-qt) could be developed sharing the same core library (Rust). The architecture separates core logic from UI for this reason. Given that 79% of Plasma 6 users are already on Wayland, demand for Driftwood's Wayland audit on KDE may drive this.
23. Accessibility
Following GNOME accessibility standards:
- All interactive elements have accessible names and descriptions
- Keyboard navigation: full Tab/Shift+Tab cycling, Enter to activate, Escape to go back
- Screen reader support:
ATKvia GTK4's built-in accessibility - High contrast mode: works via GTK4/libadwaita theme support
- Reduced motion: respect
prefers-reduced-motion(no gratuitous animations) - Status badges use text labels, not just color (color blind safe)
- "🟢 Native Wayland" not just a green dot
- All status indicators have tooltip text and aria labels
24. Internationalization
- Use
gettextfor all user-facing strings - Ship with English (default)
- Set up Weblate or similar for community translations
- All date/time formatting via locale-aware formatters
- File sizes in locale-appropriate units
- RTL layout support via GTK4's built-in bidi handling
25. Testing Strategy
Unit Tests
- Discovery engine: mock filesystem with various AppImage types
- Inspector: test against sample AppImage binaries (Type 1, Type 2, broken, unsigned)
- Desktop entry generation: verify output format
- FUSE detection: mock
/proc, library checks - Wayland detection: mock process inspection
- Update info parsing: all supported formats
Integration Tests
- Full flow: discover → inspect → integrate → verify desktop entry → remove
- Update flow: embed test zsync info, verify delta download
- Orphan detection: create entry, delete AppImage, verify cleanup
- FUSE fallback: test with and without libfuse2
UI Tests
- Screenshot tests against reference images (GTK4 broadway backend)
- Accessibility audit with
accerciser
Manual Testing Matrix
| Distro | Desktop | FUSE | Session | Priority |
|---|---|---|---|---|
| Ubuntu 26.04 LTS | GNOME 50 | fuse3 (libfuse2t64 in universe) | Wayland-only | P0 |
| Ubuntu 24.04 LTS | GNOME 46 | fuse3 (libfuse2t64 in universe) | Wayland default, X11 available | P0 |
| Fedora 42+ | GNOME 48/49 | fuse2 + fuse3 | Wayland default | P0 |
| Arch | GNOME 50 / latest | fuse2 + fuse3 | Wayland default | P0 |
| Kubuntu 26.04 | KDE Plasma 6.7+ | fuse3 (libfuse2t64 in universe) | Wayland default | P1 |
| Fedora KDE | KDE Plasma 6.7+ | fuse2 + fuse3 | Wayland default | P1 |
| openSUSE TW | GNOME or KDE | fuse2 + fuse3 | Wayland default | P2 |
| Debian 13 "Trixie" | GNOME 48 | fuse2 + fuse3 | Wayland default | P2 |
| Linux Mint 22/23 | Cinnamon | fuse3 (libfuse2t64 avail) | X11 default (Cinnamon Wayland in progress) | P2 |
26. Development Phases & Roadmap
Phase 1 - Foundation (Months 1-3)
Goal: A working app that discovers, inspects, and integrates AppImages.
- Project scaffolding (Meson + Rust + gtk4-rs)
- AppImage discovery engine (file scanning, magic byte detection)
- AppImage inspector (metadata extraction via unsquashfs)
- SQLite database for tracking state
- Library view (grid/list of discovered AppImages)
- Detail view (metadata display)
- Desktop integration (generate .desktop, extract icons)
- Orphan detection and cleanup
- Basic settings view
- CLI:
list,integrate,remove,scan,clean - AppImage packaging of Driftwood itself
Milestone: v0.1.0 - "It works. You can find, view, and integrate your AppImages."
Phase 2 - Intelligence (Months 4-6)
Goal: FUSE management, Wayland audit, update system, and usage tracking.
- FUSE detection and compatibility management
- Extract-and-run transparent fallback
- Wayland awareness engine (static analysis)
- Wayland status badges in library and detail views
- Update info parsing (zsync, GitHub, GitLab, OCS)
- Update checking (periodic + manual)
- Delta updates via AppImageUpdate/zsync2
- Full download fallback updates
- Update UI (progress, notifications)
- Driftwood self-update mechanism
- Background daemon (file watching + update checking)
- D-Bus interface
- Desktop notifications
- Usage tracking via
driftwood launchwrapper - Duplicate and multi-version detection engine
- Duplicate resolution UI
- CLI:
update,check,audit,usage,duplicates,launch
Milestone: v0.5.0 - "It's smart. It knows your FUSE status, Wayland status, available updates, and which AppImages you actually use."
Phase 3 - Security & Deep Analysis (Months 7-9)
Goal: Security scanning, data tracking, sandboxing, and polish.
- Bundled library inventory extraction (ELF SONAME, version string scanning)
- CVE database integration (NVD/OSV/Debian Security Tracker)
- CVE matching engine and per-AppImage security reports
- System vs bundled library version comparison
- Security badges and notifications for critical CVEs
- Config/data path discovery via fanotify (with proc-scan fallback)
- Heuristic config/data path matching
- Orphaned config detection on AppImage removal
- Config cleanup UI with safety categories (cache vs config vs user content)
- Firejail integration (one-click sandboxed launch)
- Bubblewrap integration (alternative backend)
- Permission audit view
- Sandbox profile generation
- GPG signature verification for AppImages
- SHA256 verification for updates
- Drag-and-drop support
- CLI:
security,footprint
Milestone: v0.8.0 - "It's secure. It knows what's inside your AppImages, where they store data, and can sandbox them."
Phase 4 - Polish & Release (Months 10-12)
Goal: Production-quality release.
- Keyboard navigation audit
- Screen reader testing
- i18n infrastructure + initial translations
- Comprehensive test suite
- Performance optimization (startup time, scan speed)
- Disk space reclamation UI (usage dashboard, cleanup wizard)
- Auto-cleanup of old duplicate versions on update
- AppStream metainfo for Driftwood itself
- Flathub submission
- AUR package
- Documentation (user guide, contributor guide)
Milestone: v1.0.0 - "It's ready. A complete, polished AppImage management experience."
Phase 5 - Ecosystem (Months 13+)
Goal: Become the standard AppImage management tool.
- AppImage catalog integration (optional download from curated sources)
- Wayland runtime detection (post-launch analysis)
- Batch re-packaging (FUSE runtime updates)
- Multi-user / system-wide mode
- GNOME Software / KDE Discover plugin
- AppImage developer tools (validate your AppImage)
- Qt6 frontend (for KDE purists)
- ARM64/aarch64 support
- Community sandbox profile sharing
- CVE database push notifications (critical CVE in widely-bundled library)
- Exportable security reports (for organizations managing AppImage deployments)
- Config backup/restore across AppImage versions
27. Project Naming & Branding
Name: Driftwood
Driftwood conveys:
- Something gathered and collected (like AppImages scattered across your filesystem)
- Natural, organic - not corporate or clinical
- Resilience - driftwood survives journeys, like AppImages surviving across distros
- A warm, approachable tool, not an intimidating "manager"
Icon concept:
- A stylized piece of driftwood with app-like elements (windows, gears) emerging from it
- Or: a flowing, organic shape containing a grid pattern - nature meets organization
- Colors: follow GNOME's app icon style (bold, flat, distinctive) - warm wood tones with accent color
App ID: io.github.driftwood (assuming GitHub hosting)
28. Risk Analysis
| Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|
| AppImage format itself loses traction | Low | High | Driftwood is useful as long as any AppImages exist. The format is deeply embedded in many projects' release workflows. The new type2-runtime (updated Jan 2026) shows continued investment. |
| libfuse2 completely removed from all distros | Medium | Medium | Extract-and-run fallback exists. New type2-runtime supports fuse3. Ubuntu keeps libfuse2t64 in universe repos. Static runtime eliminates dependency entirely for new AppImages. |
| GTK4/libadwaita breaks backward compat | Low | Medium | Pin to stable versions. Target libadwaita 1.7+ (GNOME 48). GNOME has strong ABI stability commitments. |
unsquashfs not available on some systems |
Low | Low | Bundle it in the AppImage. It's a small static binary. |
| GitHub rate limits on update checks | Medium | Low | Cache aggressively. Use conditional requests (ETags). Batch checks. Respect rate limit headers. |
| Upstream AppImageUpdate library changes | Low | Medium | Shell out to the binary for isolation. zsync2 v2.0.0-alpha (Sept 2025) is stabilizing but still alpha. Maintain a thin interface layer. |
| Single maintainer burnout | High | High | Design for contributor-friendliness. Rust attracts contributors. Good documentation. Modular architecture allows focused PRs. |
| AppManager catches up first | Medium | Medium | AppManager (Vala/GTK4) v3.0.0 is the closest competitor. Driftwood differentiates with Wayland audit, FUSE management, background daemon, orphan cleanup, and system-health perspective. Collaborate where possible (shared AppImage inspection libraries). |
| GNOME 50 Wayland-only transition breaks assumptions | Low | Low | Actually benefits Driftwood - makes Wayland audit MORE valuable since there's no X11 session to fall back to. |
| type2-runtime compatibility fragmentation | Medium | Medium | Driftwood should handle both old (fuse2-only) and new (fuse2/fuse3 static) runtimes gracefully. Test against both AppImageLauncher v2.2 and v3.0 interactions. |
29. Community & Contribution Model
Governance
- Open source (GPL-3.0 or LGPL-3.0 - TBD, should match GNOME ecosystem norms)
- Hosted on GitHub (for ecosystem familiarity; could be GNOME GitLab later)
- BDFL model initially, transitioning to maintainer team as community grows
Contribution Areas
- Rust developers: core logic, daemon, CLI
- GTK4/UI designers: views, widgets, UX refinement
- Packagers: Flatpak, AUR, RPM, DEB, Nix
- Translators: via Weblate
- Documentation writers: user guides, developer docs
- Testers: distro testing matrix, edge cases
Communication
- GitHub Issues for bugs and feature requests
- GitHub Discussions for design conversations
- Matrix/IRC channel for real-time chat
- Blog posts for major releases
30. Appendix: Technical Reference
A. AppImage Magic Bytes
Type 2: bytes at offset 8 in ELF: 0x41 0x49 0x02 (AI\x02)
Type 1: ISO 9660 magic: 0x43 0x44 0x30 0x30 0x31 (CD001) at offset 32769
B. Key Environment Variables
| Variable | Purpose | Values |
|---|---|---|
APPIMAGE |
Set by runtime; points to AppImage path | /path/to/app.AppImage |
APPDIR |
Set by runtime; points to mounted/extracted dir | /tmp/.mount_appXXX/ |
APPIMAGE_EXTRACT_AND_RUN |
Force extract-and-run mode | 1 |
WAYLAND_DISPLAY |
Wayland compositor socket | wayland-0 |
DISPLAY |
X11 display | :0 |
GDK_BACKEND |
Force GTK backend | wayland, x11 |
QT_QPA_PLATFORM |
Force Qt platform | wayland, xcb |
XDG_SESSION_TYPE |
Session type | wayland, x11 |
XDG_CURRENT_DESKTOP |
Desktop environment | GNOME, KDE, etc. |
C. Key File Paths
| Path | Purpose |
|---|---|
~/.local/share/applications/ |
User desktop entries |
~/.local/share/icons/hicolor/ |
User icon theme |
~/.local/share/driftwood/driftwood.db |
Driftwood SQLite database |
~/.config/driftwood/ |
Driftwood configuration |
~/.config/autostart/driftwoodd.desktop |
Daemon autostart entry |
/usr/lib/x86_64-linux-gnu/libfuse.so.2 |
libfuse2 library (Debian) |
/usr/lib/libfuse.so.2 |
libfuse2 library (Arch/Fedora) |
D. D-Bus Services
| Service | Purpose |
|---|---|
io.github.Driftwood.Manager |
Driftwood daemon interface |
org.freedesktop.Notifications |
Desktop notifications |
org.gnome.Shell.Introspect |
GNOME window inspection (Wayland audit) |
org.kde.KWin |
KDE window inspection (Wayland audit) |
E. Database Schema (SQLite)
CREATE TABLE appimages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
path TEXT NOT NULL UNIQUE,
filename TEXT NOT NULL,
app_name TEXT,
app_version TEXT,
appimage_type INTEGER, -- 1 or 2
size_bytes INTEGER,
sha256 TEXT,
icon_path TEXT,
desktop_file TEXT, -- path to generated .desktop
integrated BOOLEAN DEFAULT FALSE,
integrated_at TIMESTAMP,
fuse_status TEXT,
wayland_status TEXT,
update_info TEXT, -- raw update info string
update_type TEXT, -- zsync, github, gitlab, ocs, none
latest_version TEXT,
update_checked TIMESTAMP,
update_url TEXT,
sandbox_profile TEXT,
first_seen TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_scanned TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
file_modified TIMESTAMP,
notes TEXT -- user notes
);
CREATE TABLE orphaned_entries (
id INTEGER PRIMARY KEY AUTOINCREMENT,
desktop_file TEXT NOT NULL,
original_path TEXT,
detected_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
cleaned BOOLEAN DEFAULT FALSE,
cleaned_at TIMESTAMP
);
CREATE TABLE update_history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
appimage_id INTEGER REFERENCES appimages(id),
from_version TEXT,
to_version TEXT,
update_method TEXT, -- zsync, full_download
download_size INTEGER,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
success BOOLEAN
);
CREATE TABLE scan_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
scan_type TEXT, -- full, incremental, inotify
directories TEXT, -- JSON array of scanned dirs
found_count INTEGER,
new_count INTEGER,
removed_count INTEGER,
duration_ms INTEGER,
scanned_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Bundled library security scanner
CREATE TABLE bundled_libraries (
id INTEGER PRIMARY KEY AUTOINCREMENT,
appimage_id INTEGER REFERENCES appimages(id),
soname TEXT NOT NULL,
library_name TEXT, -- normalized: "openssl", "curl", etc.
detected_version TEXT,
file_path TEXT, -- path within the squashfs
file_size INTEGER,
scanned_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE cve_database (
id INTEGER PRIMARY KEY AUTOINCREMENT,
cve_id TEXT NOT NULL UNIQUE, -- e.g., "CVE-2024-0727"
library_name TEXT NOT NULL,
affected_versions TEXT, -- JSON version range
fixed_version TEXT,
cvss_score REAL,
severity TEXT, -- critical, high, medium, low
description TEXT,
source TEXT, -- nvd, osv, debian
published_at TIMESTAMP,
fetched_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE cve_matches (
id INTEGER PRIMARY KEY AUTOINCREMENT,
appimage_id INTEGER REFERENCES appimages(id),
library_id INTEGER REFERENCES bundled_libraries(id),
cve_id TEXT REFERENCES cve_database(cve_id),
matched_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Usage tracking
CREATE TABLE launch_events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
appimage_id INTEGER REFERENCES appimages(id),
launched_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
source TEXT -- desktop_entry, cli, detected
);
-- Orphaned config/data tracking
CREATE TABLE app_data_paths (
id INTEGER PRIMARY KEY AUTOINCREMENT,
appimage_id INTEGER REFERENCES appimages(id),
path TEXT NOT NULL,
path_type TEXT, -- config, data, cache, state, dotfile, user_data
discovery_method TEXT, -- fanotify, proc_scan, heuristic
confidence TEXT, -- high, medium, low
size_bytes INTEGER,
first_seen TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_accessed TIMESTAMP
);
-- Duplicate detection
CREATE TABLE duplicate_groups (
id INTEGER PRIMARY KEY AUTOINCREMENT,
canonical_name TEXT NOT NULL,
duplicate_type TEXT, -- exact, multi_version, channel_split
detected_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE duplicate_members (
id INTEGER PRIMARY KEY AUTOINCREMENT,
group_id INTEGER REFERENCES duplicate_groups(id),
appimage_id INTEGER REFERENCES appimages(id),
is_recommended BOOLEAN DEFAULT FALSE -- the one to keep
);
Summary
Driftwood fills the critical gap between "I downloaded an AppImage" and "this app is a first-class citizen on my desktop." It addresses every pain point in the current AppImage experience - discovery, integration, updates, FUSE compatibility, Wayland awareness, orphan cleanup, bundled library CVE scanning, usage tracking, disk space reclamation, config/data lifecycle management, and duplicate detection - all through a modern, native GTK4/libadwaita interface that respects GNOME HIG and works well on KDE.
Built in Rust for safety and performance, distributed as an AppImage that can update itself, and designed from the ground up for the Wayland-only era that arrived in 2026.
The timing is right: GNOME 50 ships Wayland-only in March 2026. Ubuntu 26.04 LTS follows in April. KDE Plasma 6.8 drops X11 in October. The new AppImage type2-runtime is stabilizing with fuse2/fuse3 support. Existing tools (Gear Lever v4.4.6, AppManager v3.0.0, AppImageLauncher v3.0.0) each solve pieces of the puzzle but none provide the complete, system-aware, Wayland-auditing, FUSE-managing experience that Driftwood delivers.
This is the app that makes AppImage actually work the way it was always supposed to.