# 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 1. [Problem Statement](#1-problem-statement) 2. [Landscape Analysis](#2-landscape-analysis) 3. [Project Vision & Goals](#3-project-vision--goals) 4. [Target Users](#4-target-users) 5. [Architecture Overview](#5-architecture-overview) 6. [Core Features - Detailed Design](#6-core-features--detailed-design) 7. [UI/UX Design](#7-uiux-design) 8. [Technical Stack & Dependencies](#8-technical-stack--dependencies) 9. [AppImage Internals & Integration Points](#9-appimage-internals--integration-points) 10. [FUSE Compatibility Layer](#10-fuse-compatibility-layer) 11. [Wayland Awareness Engine](#11-wayland-awareness-engine) 12. [Update System](#12-update-system) 13. [Sandboxing & Permissions](#13-sandboxing--permissions) 14. [Bundled Library Security Scanner](#14-bundled-library-security-scanner) 15. [Usage Tracking & Disk Space Reclamation](#15-usage-tracking--disk-space-reclamation) 16. [Orphaned Config & Data Detection](#16-orphaned-config--data-detection) 17. [Duplicate & Multi-Version Detection](#17-duplicate--multi-version-detection) 18. [Desktop Integration Manager](#18-desktop-integration-manager) 19. [Background Daemon](#19-background-daemon) 20. [CLI Interface](#20-cli-interface) 21. [Distribution & Self-Updating](#21-distribution--self-updating) 22. [KDE/Plasma Support Strategy](#22-kdeplasma-support-strategy) 23. [Accessibility](#23-accessibility) 24. [Internationalization](#24-internationalization) 25. [Testing Strategy](#25-testing-strategy) 26. [Development Phases & Roadmap](#26-development-phases--roadmap) 27. [Project Naming & Branding](#27-project-naming--branding) 28. [Risk Analysis](#28-risk-analysis) 29. [Community & Contribution Model](#29-community--contribution-model) 30. [Appendix: Technical Reference](#30-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 `~/Downloads` with 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 `.desktop` files and extract icons, or rely on `appimaged` (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 `libfuse2` from default installs (renamed to `libfuse2t64` on 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 `.desktop` entry 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](https://pedroinnecco.com/2025/09/linux-deserves-better-the-future-of-appimage-integration/) (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](https://github.com/AppImage/type2-runtime/issues/121) 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 1. **Zero-friction onboarding.** Launch Driftwood, it finds all your AppImages, shows their status, and offers to integrate them - in one click. 2. **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. 3. **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. 4. **Clean system, always.** No orphaned `.desktop` files, no ghost icons, no stale entries. Driftwood owns the lifecycle. 5. **FUSE just works.** Detect the FUSE situation, offer to install `libfuse2`, or transparently use `--appimage-extract-and-run` as needed. 6. **Security-conscious.** Optional sandboxing via bubblewrap/firejail, permission auditing, hash verification. 7. **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 `.desktop` file 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-rs` and `libadwaita-rs` are 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):** 1. **Magic bytes check.** AppImages (Type 2) contain the magic bytes `AI\x02` at ELF offset. Type 1 uses ISO 9660 magic. Check the ELF header for the AppImage magic at the section header offset field. 2. **File extension.** `.AppImage`, `.appimage`, `.app` - but don't rely on this alone; many AppImages have no extension. 3. **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 `hsqs` at the offset indicated by the AppImage runtime). 4. **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 `tokio` or `rayon` (Rust) / `concurrent.futures` (Python) - Subsequent updates: `inotify` watches 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):** ```rust struct DiscoveredAppImage { path: PathBuf, filename: String, appimage_type: AppImageType, // Type1 | Type2 | Unknown size_bytes: u64, modified_time: SystemTime, is_executable: bool, sha256: Option, // computed on demand // Extracted from embedded .desktop / AppStream: app_name: Option, app_version: Option, app_icon: Option, categories: Vec, desktop_entry: Option, update_info: Option, // Runtime analysis: fuse_requirement: FuseRequirement, // Fuse2 | Fuse3 | ExtractAndRun wayland_status: Option, 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): 1. Read the ELF header to find the AppImage runtime offset 2. Read the `.appimage_update_info` section from the ELF (update URL) 3. Mount (via FUSE) or extract (via `unsquashfs` / `squashfuse`) the embedded filesystem 4. Parse `.desktop` file from the squashfs root 5. Extract icon (`.DirIcon`, or icon referenced in `.desktop`) 6. Parse `AppStream` metadata XML if present (`usr/share/metainfo/*.appdata.xml`) 7. Check for `AppRun` script to determine runtime behavior 8. Detect runtime type: legacy fuse2-only runtime vs new static runtime (fuse2/fuse3 capable) For **Type 1** AppImages (legacy, ISO 9660-based): 1. Mount as ISO or extract with `bsdtar` 2. Same metadata extraction from the contained filesystem For **DwarFS-based AppImages** (emerging format, used by some projects): 1. Detect DwarFS magic bytes instead of squashfs 2. Use `dwarfsextract` or `dwarfs` FUSE mount for inspection 3. Same metadata extraction from the contained filesystem 4. 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-offset` to find the squashfs offset - Use the `squashfs-tools` library or `unsquashfs` to 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 `.desktop` `Name=` 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:** 1. **Identity** - Name, version, description (from AppStream) - Icon (large) - Categories, keywords - Developer/maintainer info (from AppStream) 2. **File Info** - Full path - File size - SHA256 hash (computed, with copy button) - AppImage type (1 or 2) - Created/modified timestamps - Permissions (executable bit) 3. **Desktop Integration** - Status: integrated / not integrated - `.desktop` file path (if integrated) - Icon installation path - "Integrate" / "Remove integration" button - Preview of the generated `.desktop` file 4. **Runtime Compatibility** - FUSE status with explanation and fix button - Wayland status with detailed explanation - Libraries bundled vs system (if detectable) - Architecture (x86_64, aarch64) 5. **Updates** - Current version - Update information type (zsync, GitHub releases, OCS, none) - Update URL - Last checked timestamp - "Check now" / "Update" buttons - Update history 6. **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 7. **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 8. **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 `AdwApplicationWindow` with `AdwHeaderBar` - Use `AdwNavigationView` for main → detail navigation - Use `AdwPreferencesWindow` for settings - Use `AdwStatusPage` for empty states - Use `AdwToastOverlay` for transient notifications - Use `AdwBanner` for persistent warnings (e.g., "FUSE2 not installed") - Use `AdwDialog` for 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: 1. User drags `.AppImage` file onto window 2. Drop zone highlights 3. On drop: inspect → show detail view → offer integration 4. Optionally move to `~/Applications` on 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 - `unsquashfs` or `squashfs-tools` (for AppImage inspection without FUSE) - D-Bus (for daemon communication) **Runtime (optional, for full functionality):** - `libfuse2` or `libfuse3` (for AppImage mounting) - `zsync2` or `libzsync` (for delta updates) - `firejail` or `bubblewrap` (for sandboxing) - `libnotify` / notification daemon (for system notifications) - `gpg` / `gpgme` (for signature verification) ### GSettings Schema ```xml ['~/Applications', '~/Downloads'] '~/Applications' true false 'weekly' false false 'auto' 'none' true true 'weekly' 'high' true 90 true true true false 1 ``` --- ## 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:** ```ini [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-cache` or `xdg-icon-resource` as needed ### 9.3 Orphan Detection An orphaned desktop entry is one where: 1. The `.desktop` file contains `X-AppImage-Managed-By=Driftwood` 2. The `X-AppImage-Path` points to a file that no longer exists **Cleanup actions:** - Remove the `.desktop` file - 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: - `libfuse2` on the system (for most existing AppImages compiled against fuse2) - The `fusermount` binary - The `/dev/fuse` device 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](https://github.com/AppImage/type2-runtime) (updated January 2026) attempts to support both fuse2 and fuse3, preferring fuse3 when available. However, it has [known compatibility issues](https://github.com/AppImage/type2-runtime/issues/121) 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` - 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](https://github.com/AppImage/type2-runtime) (updated January 2026) statically links libfuse and can use either fuse2 or fuse3, preferring fuse3 when available - AppImages built with recent `appimagetool` use 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](https://github.com/AppImage/type2-runtime/issues/121) 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: ```bash # 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=wayland` for GTK3 apps - Set `QT_QPA_PLATFORM=wayland` for Qt5 apps with QtWayland present - Add `--ozone-platform-hint=auto` for 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: ```rust 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: 1. Download the `.zsync` control file (small, contains block checksums) 2. Compare checksums against the local AppImage's blocks 3. Download only the changed blocks 4. Reconstruct the new AppImage from local blocks + downloaded deltas 5. Verify full-file checksum 6. Replace old AppImage with new one **Typical savings:** 60-90% bandwidth reduction for minor version updates. **Implementation options:** - Shell out to `AppImageUpdate` binary (simplest, if installed) - Shell out to `zsync2` binary - Use `libappimageupdate` as 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: 1. For GitHub-hosted AppImages: use GitHub Releases API to find the latest `.AppImage` asset 2. Download to a temporary location 3. Verify (checksum if available, at minimum check it's a valid AppImage) 4. Replace old file (atomic rename) 5. Re-extract desktop entry and icon (version may have changed) 6. 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 `--appimage` flag 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:** 1. Default: no sandbox (matches current AppImage behavior) 2. One-click "Run sandboxed" using firejail (if installed) with default profile 3. Custom profiles per app (advanced, stored in `~/.config/driftwood/sandbox/`) 4. 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: ```ini # 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:** ```rust 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, } ``` **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** ```rust 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: 1. **Never auto-delete anything.** Always present a list and let the user choose. 2. **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. 3. **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." 4. **Dry-run by default.** The cleanup dialog shows what *would* be deleted. The user clicks confirm. 5. **Backup option.** Offer to tar the config before deleting: "Save a backup of Kdenlive's config to ~/driftwood-backups/ before removing?" 6. **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 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:** ```rust struct DuplicateGroup { canonical_name: String, // e.g., "Firefox" appimages: Vec, // 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, size: u64, last_modified: DateTime, last_launched: Option, // 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: 1. **Version:** Newest version preferred (unless user explicitly kept an older one) 2. **Integration status:** The one with an active `.desktop` entry preferred 3. **Location:** `~/Applications` preferred over `~/Downloads` 4. **Usage:** Most recently launched preferred 5. **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: 1. Watch configured directories for new/removed AppImages (via `inotify`) 2. Periodically check for updates 3. Detect and flag orphaned desktop entries 4. Send desktop notifications 5. Provide a D-Bus API for the GUI and CLI ### 19.2 D-Bus Interface ```xml ``` ### 19.3 Autostart When enabled, Driftwood installs an autostart entry: ```ini # ~/.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 # Integrate an AppImage driftwood integrate --all # Integrate all discovered driftwood remove # Remove integration driftwood update # Update specific AppImage driftwood update --all # Update all with available updates driftwood check # Check for updates driftwood inspect # 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 # Show config/data/cache footprint of an AppImage driftwood launch [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:** 1. Compile the Rust binary with static linking where possible 2. Bundle GTK4, libadwaita, and all Rust dependencies 3. Use `linuxdeploy` with the `gtk` plugin to create the AppImage 4. Embed update information pointing to GitHub Releases 5. 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: 1. On launch, check for updates (respecting the user's update check interval) 2. If update available, show banner: "A new version of Driftwood is available [Update]" 3. Download the new AppImage to a temp location 4. Verify checksum / signature 5. Replace the current AppImage file (atomic rename) 6. 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) and `driftwood-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-kde` and `kde-gtk-config` are 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=KDE` and adjust: - Use `kbuildsycoca6` instead of / in addition to `update-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-kde` for native KDE file picker - Wayland audit: use `org.kde.KWin` D-Bus interface for window inspection instead of GNOME's introspection API ### 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: `ATK` via 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 `gettext` for 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 launch` wrapper - [ ] 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) ```sql 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.