Add feature roadmap design for 25 new features
Comprehensive design doc covering discovery-to-uninstall lifecycle: - System modification tracking for clean reversible uninstalls - Sorted by complexity from quick wins to major features - Covers FUSE wizard, autostart, MIME types, catalog browser, portable mode, batch operations, and more
This commit is contained in:
679
docs/plans/2026-02-27-feature-roadmap-design.md
Normal file
679
docs/plans/2026-02-27-feature-roadmap-design.md
Normal file
@@ -0,0 +1,679 @@
|
|||||||
|
# Driftwood Feature Roadmap - Design Document
|
||||||
|
|
||||||
|
**Goal:** Add 26 features to make Driftwood the definitive AppImage manager for Linux newcomers coming from Windows, covering the full lifecycle from discovery to clean uninstall.
|
||||||
|
|
||||||
|
**Architecture:** All features build on the existing GTK4/libadwaita/Rust stack. Every system modification (desktop files, icons, MIME associations, autostart entries) is tracked in a central `system_modifications` table and fully reversed on app removal. Features are ordered from simplest to most complex.
|
||||||
|
|
||||||
|
**Tech Stack:** Rust, gtk4-rs, libadwaita-rs, rusqlite, gio, notify crate, pkexec/polkit for privileged operations, XDG specs (Desktop Entry, Autostart, MIME Applications), AppImage feed.json catalog.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Core Architecture: System Modification Tracking
|
||||||
|
|
||||||
|
Every feature that touches system files MUST use this tracking system. No exceptions.
|
||||||
|
|
||||||
|
### New Database Table
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE IF NOT EXISTS system_modifications (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
appimage_id INTEGER REFERENCES appimages(id) ON DELETE CASCADE,
|
||||||
|
mod_type TEXT NOT NULL,
|
||||||
|
file_path TEXT NOT NULL,
|
||||||
|
previous_value TEXT,
|
||||||
|
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||||
|
);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_system_mods_appimage
|
||||||
|
ON system_modifications(appimage_id);
|
||||||
|
```
|
||||||
|
|
||||||
|
**mod_type values:** `desktop_file`, `icon`, `autostart`, `mime_default`, `mime_entry`, `system_desktop`, `system_icon`, `system_binary`
|
||||||
|
|
||||||
|
### Core Functions (src/core/integrator.rs)
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn register_modification(db: &Database, appimage_id: i64, mod_type: &str, file_path: &str, previous_value: Option<&str>) -> Result<()>
|
||||||
|
pub fn undo_modification(db: &Database, mod_id: i64) -> Result<()>
|
||||||
|
pub fn undo_all_modifications(db: &Database, appimage_id: i64) -> Result<()>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Uninstall Flow
|
||||||
|
|
||||||
|
1. Query `system_modifications` for the appimage_id
|
||||||
|
2. For each modification (in reverse order):
|
||||||
|
- `desktop_file` / `autostart` / `icon`: delete the file
|
||||||
|
- `mime_default`: restore previous_value via `xdg-mime default {previous_value} {mime_type}`
|
||||||
|
- `system_*`: delete via `pkexec rm`
|
||||||
|
3. Run `update-desktop-database ~/.local/share/applications/`
|
||||||
|
4. Run `gtk-update-icon-cache ~/.local/share/icons/hicolor/` (if icons were removed)
|
||||||
|
5. Optionally delete app data paths (with user confirmation)
|
||||||
|
6. Delete the AppImage file
|
||||||
|
7. Delete DB record (CASCADE handles system_modifications rows)
|
||||||
|
|
||||||
|
### CLI Purge Command
|
||||||
|
|
||||||
|
`driftwood purge` - removes ALL system modifications for ALL managed apps, for when Driftwood itself is being removed from the system.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Feature Specifications (Sorted: Easiest to Most Complex)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### F1. Auto-Set Executable Permission on Existing Files
|
||||||
|
|
||||||
|
**Complexity:** Trivial (15 min)
|
||||||
|
**Problem:** Files already in scan directories skip chmod during drag-and-drop. Non-executable AppImages found during scan aren't fixed.
|
||||||
|
**Files:** `src/ui/drop_dialog.rs`, `src/core/discovery.rs`
|
||||||
|
|
||||||
|
**Changes:**
|
||||||
|
- `drop_dialog.rs`: In the `in_scan_dir` branch of `register_dropped_files()`, add permission check and fix:
|
||||||
|
```rust
|
||||||
|
if !std::os::unix::fs::PermissionsExt::mode(&metadata.permissions()) & 0o111 != 0 {
|
||||||
|
std::fs::set_permissions(&final_path, std::fs::Permissions::from_mode(0o755))?;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- `discovery.rs`: After scan finds a non-executable AppImage, auto-fix permissions and log it
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### F2. Move-or-Copy Option for Drag-and-Drop (+ Keep in Place)
|
||||||
|
|
||||||
|
**Complexity:** Easy (30 min)
|
||||||
|
**Problem:** Always copies file, wasting disk. No option to keep in place.
|
||||||
|
**Files:** `src/ui/drop_dialog.rs`
|
||||||
|
|
||||||
|
**Changes:**
|
||||||
|
- Replace 3-button dialog with 4 response options:
|
||||||
|
- `"cancel"` - Cancel
|
||||||
|
- `"keep-in-place"` - Keep in place (register at current location, set executable, no copy)
|
||||||
|
- `"copy-only"` - Copy to Applications (current "Just add" behavior)
|
||||||
|
- `"copy-and-integrate"` - Copy & add to menu (current "Add to app menu", suggested/default)
|
||||||
|
- `register_dropped_files()` receives a new `copy_mode` enum: `KeepInPlace`, `CopyOnly`, `CopyAndIntegrate`
|
||||||
|
- `KeepInPlace`: set executable on original path, register in DB at original location, optionally integrate
|
||||||
|
- Dialog text updated: "Where should this AppImage live?"
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### F3. Version Rollback
|
||||||
|
|
||||||
|
**Complexity:** Easy (1 hr)
|
||||||
|
**Problem:** No way to go back if an update breaks things.
|
||||||
|
**Files:** `src/core/updater.rs`, `src/ui/detail_view.rs`, `src/core/database.rs`
|
||||||
|
|
||||||
|
**Changes:**
|
||||||
|
- New DB column: `previous_version_path TEXT` on appimages
|
||||||
|
- In `updater.rs` update flow: before replacing, rename old to `{path}.prev` and store path in `previous_version_path`
|
||||||
|
- Detail view system tab: "Rollback to previous version" button (visible only when `previous_version_path` is set)
|
||||||
|
- Rollback: swap current and .prev files, update DB fields, re-run analysis
|
||||||
|
- Cleanup: delete .prev file when user explicitly confirms or after configurable retention
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### F4. Source Tracking
|
||||||
|
|
||||||
|
**Complexity:** Easy (1 hr)
|
||||||
|
**Problem:** Users can't tell where an AppImage was downloaded from.
|
||||||
|
**Files:** `src/ui/detail_view.rs`, `src/core/database.rs`, `src/ui/drop_dialog.rs`
|
||||||
|
|
||||||
|
**Changes:**
|
||||||
|
- New DB column: `source_url TEXT` on appimages
|
||||||
|
- Auto-detect from `update_info` field (already stored): parse GitHub/GitLab URLs
|
||||||
|
- Display "Source: github.com/obsidianmd/obsidian" on detail view overview tab
|
||||||
|
- Drop dialog: optional "Where did you download this?" text field (pre-filled if detected)
|
||||||
|
- Catalog installs (F26): automatically set source_url from catalog entry
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### F5. Launch Statistics Dashboard
|
||||||
|
|
||||||
|
**Complexity:** Easy (1-2 hrs)
|
||||||
|
**Problem:** Launch data is tracked but never shown.
|
||||||
|
**Files:** `src/ui/dashboard.rs`, `src/core/database.rs`
|
||||||
|
|
||||||
|
**Changes:**
|
||||||
|
- New DB queries:
|
||||||
|
- `get_top_launched(limit: i32) -> Vec<(String, u64)>` - most launched apps
|
||||||
|
- `get_launch_count_since(since: &str) -> u64` - total launches since date
|
||||||
|
- `get_recent_launches(limit: i32) -> Vec<(String, String)>` - recent launch events with timestamps
|
||||||
|
- Dashboard: new "Activity" section showing:
|
||||||
|
- Top 5 most-launched apps with launch counts
|
||||||
|
- "X launches this week" summary stat
|
||||||
|
- "Last launched: AppName, 2 hours ago"
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### F6. Batch Operations
|
||||||
|
|
||||||
|
**Complexity:** Medium (2-3 hrs)
|
||||||
|
**Problem:** Can't select multiple AppImages for bulk actions.
|
||||||
|
**Files:** `src/ui/library_view.rs`, `src/ui/app_card.rs`, `src/window.rs`
|
||||||
|
|
||||||
|
**Changes:**
|
||||||
|
- Library view header: "Select" toggle button
|
||||||
|
- When active: app cards show checkboxes, bottom action bar slides up
|
||||||
|
- Action bar buttons: "Integrate" / "Remove Integration" / "Delete" / "Export"
|
||||||
|
- Each action confirms with count: "Integrate 5 AppImages?"
|
||||||
|
- Delete uses the full uninstall flow (F14/system_modifications cleanup)
|
||||||
|
- Selection state stored in a `HashSet<i64>` of record IDs on the LibraryView
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### F7. Automatic Desktop Integration on Scan
|
||||||
|
|
||||||
|
**Complexity:** Easy (30 min)
|
||||||
|
**Problem:** Users forget to integrate after scanning.
|
||||||
|
**Files:** `src/window.rs`, `src/ui/preferences.rs`
|
||||||
|
|
||||||
|
**Changes:**
|
||||||
|
- GSettings key `auto-integrate` already exists (default false)
|
||||||
|
- Wire it up: after scan completes in `window.rs`, if setting is true, iterate newly discovered apps and call `integrator::integrate()` for each
|
||||||
|
- Register all created files via `register_modification()`
|
||||||
|
- Preferences: add toggle in Behavior page (already may be there, verify)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### F8. Autostart Manager
|
||||||
|
|
||||||
|
**Complexity:** Medium (2 hrs)
|
||||||
|
**Problem:** No way to set AppImages to start at login.
|
||||||
|
**Spec:** XDG Autostart - `.desktop` file in `~/.config/autostart/`
|
||||||
|
**Files:** `src/core/integrator.rs`, `src/ui/detail_view.rs`, `src/core/database.rs`
|
||||||
|
|
||||||
|
**Changes:**
|
||||||
|
- New DB column: `autostart INTEGER NOT NULL DEFAULT 0`
|
||||||
|
- New functions in `integrator.rs`:
|
||||||
|
```rust
|
||||||
|
pub fn enable_autostart(db: &Database, record: &AppImageRecord) -> Result<PathBuf>
|
||||||
|
pub fn disable_autostart(db: &Database, record_id: i64) -> Result<()>
|
||||||
|
```
|
||||||
|
- `enable_autostart`: creates `~/.config/autostart/driftwood-{id}.desktop` with:
|
||||||
|
```ini
|
||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Name={app_name}
|
||||||
|
Exec={appimage_path}
|
||||||
|
Icon={icon_path}
|
||||||
|
X-GNOME-Autostart-enabled=true
|
||||||
|
X-Driftwood-AppImage-ID={id}
|
||||||
|
```
|
||||||
|
- Registers modification with mod_type `autostart`
|
||||||
|
- Detail view system tab: "Start at login" switch row
|
||||||
|
- `disable_autostart`: deletes the file, removes from system_modifications
|
||||||
|
- Uninstall flow: handled by `undo_all_modifications()`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### F9. System Notification Integration
|
||||||
|
|
||||||
|
**Complexity:** Medium (2 hrs)
|
||||||
|
**Problem:** Toasts vanish, important events get missed.
|
||||||
|
**Files:** `src/core/notification.rs`, `src/window.rs`
|
||||||
|
|
||||||
|
**Changes:**
|
||||||
|
- Use `gio::Application::send_notification(id, notification)` for:
|
||||||
|
- App crash on launch (high priority)
|
||||||
|
- Updates available after background check (normal priority)
|
||||||
|
- Security vulnerabilities found (high priority if critical/high severity)
|
||||||
|
- Keep toasts for minor confirmations (copied, integrated, etc.)
|
||||||
|
- Notification click opens Driftwood and navigates to relevant view
|
||||||
|
- `notification.rs` already has the logic for CVE notifications - extend to use gio::Notification instead of/in addition to libnotify
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### F10. Storage Dashboard per App
|
||||||
|
|
||||||
|
**Complexity:** Medium (2 hrs)
|
||||||
|
**Problem:** Users don't know total disk usage per app.
|
||||||
|
**Files:** `src/ui/detail_view.rs`, `src/core/footprint.rs`, `src/core/database.rs`
|
||||||
|
|
||||||
|
**Changes:**
|
||||||
|
- `footprint.rs`: new function `get_total_footprint(db: &Database, record_id: i64) -> FootprintSummary`
|
||||||
|
```rust
|
||||||
|
pub struct FootprintSummary {
|
||||||
|
pub binary_size: u64,
|
||||||
|
pub config_size: u64,
|
||||||
|
pub cache_size: u64,
|
||||||
|
pub data_size: u64,
|
||||||
|
pub state_size: u64,
|
||||||
|
pub total_size: u64,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- Detail view storage tab: visual breakdown with labeled size bars
|
||||||
|
- Each category shows path and size: "Config (~/.config/MyApp) - 12 MB"
|
||||||
|
- "Clean cache" button per category (deletes cache paths only)
|
||||||
|
- Library list view: optional "Total size" column
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### F11. Background Update Checks
|
||||||
|
|
||||||
|
**Complexity:** Medium (2-3 hrs)
|
||||||
|
**Problem:** No automatic update awareness.
|
||||||
|
**Files:** `src/window.rs`, `src/ui/dashboard.rs`, `src/ui/library_view.rs`, GSettings schema
|
||||||
|
|
||||||
|
**Changes:**
|
||||||
|
- New GSettings key: `update-check-interval-hours` type i, default 24, range 1-168
|
||||||
|
- New GSettings key: `last-update-check` type s, default ''
|
||||||
|
- On startup: if `auto-check-updates` is true and enough time has passed, spawn background check
|
||||||
|
- Background check: iterate all apps with update_info, call `check_appimage_for_update()` per app
|
||||||
|
- Results: update `update_available` column in DB, send gio::Notification if updates found
|
||||||
|
- Dashboard: show "X updates available" with timestamp "Last checked: 2h ago"
|
||||||
|
- Library view: badge on apps with updates
|
||||||
|
- Preferences: toggle + interval dropdown
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### F12. One-Click Update All
|
||||||
|
|
||||||
|
**Complexity:** Medium (3-4 hrs)
|
||||||
|
**Problem:** Can only update one app at a time.
|
||||||
|
**Files:** `src/ui/dashboard.rs`, new `src/ui/batch_update_dialog.rs`, `src/core/updater.rs`
|
||||||
|
|
||||||
|
**Changes:**
|
||||||
|
- Dashboard: "Update All (N)" button when updates are available
|
||||||
|
- Opens batch update dialog showing list of apps to update with checkboxes
|
||||||
|
- Progress: per-app progress bar, overall progress bar
|
||||||
|
- Each update: download new version, save old as .prev (F3 rollback), replace, re-analyze
|
||||||
|
- On completion: summary toast "Updated 5 apps successfully, 1 failed"
|
||||||
|
- Failed updates: show error per app, keep old version
|
||||||
|
- Cancel: stops remaining updates, already-updated apps stay updated
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### F13. Full Uninstall with Data Cleanup
|
||||||
|
|
||||||
|
**Complexity:** Medium (3 hrs)
|
||||||
|
**Problem:** Deleting AppImage leaves config/cache/data behind.
|
||||||
|
**Files:** `src/ui/detail_view.rs`, new confirmation dialog logic, `src/core/footprint.rs`
|
||||||
|
|
||||||
|
**Changes:**
|
||||||
|
- Delete button in detail view triggers new uninstall flow:
|
||||||
|
1. Show dialog with FootprintSummary (from F10):
|
||||||
|
- "Delete MyApp?" with breakdown:
|
||||||
|
- [x] AppImage file (245 MB)
|
||||||
|
- [x] Configuration (~/.config/MyApp) - 12 MB
|
||||||
|
- [x] Cache (~/.cache/MyApp) - 89 MB
|
||||||
|
- [x] Data (~/.local/share/MyApp) - 1.2 GB
|
||||||
|
- Total: 1.5 GB will be freed
|
||||||
|
2. All checked by default, user can uncheck to keep data
|
||||||
|
3. On confirm:
|
||||||
|
- Call `undo_all_modifications()` (removes .desktop, icons, autostart, MIME defaults)
|
||||||
|
- Delete selected data paths
|
||||||
|
- Delete the AppImage file
|
||||||
|
- Remove DB record
|
||||||
|
- Batch delete (F6) uses same flow with aggregated summary
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### F14. Theme/Icon Preview in Drop Dialog
|
||||||
|
|
||||||
|
**Complexity:** Medium (2 hrs)
|
||||||
|
**Problem:** Users don't see what the app looks like before integrating.
|
||||||
|
**Files:** `src/ui/drop_dialog.rs`, `src/core/inspector.rs`
|
||||||
|
|
||||||
|
**Changes:**
|
||||||
|
- New function in inspector: `extract_icon_fast(path: &Path) -> Option<PathBuf>`
|
||||||
|
- Runs `unsquashfs -l` to find icon file, then extracts just that one file
|
||||||
|
- Much faster than full inspection
|
||||||
|
- Drop dialog: after registration, show preview card:
|
||||||
|
- App icon (from fast extraction)
|
||||||
|
- App name (from filename initially, updated after full analysis)
|
||||||
|
- "This is how it will appear in your app menu"
|
||||||
|
- If icon extraction fails, show default AppImage icon
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### F15. FUSE Fix Wizard
|
||||||
|
|
||||||
|
**Complexity:** Significant (4-5 hrs)
|
||||||
|
**Problem:** #1 support issue - FUSE not installed, AppImages won't run.
|
||||||
|
**Files:** `src/core/fuse.rs`, new `src/ui/fuse_wizard.rs`, `src/ui/dashboard.rs`, new `data/app.driftwood.Driftwood.policy`
|
||||||
|
|
||||||
|
**Changes:**
|
||||||
|
- New distro detection in `fuse.rs`:
|
||||||
|
```rust
|
||||||
|
pub struct DistroInfo {
|
||||||
|
pub id: String, // "ubuntu", "fedora", "arch", etc.
|
||||||
|
pub id_like: Vec<String>,
|
||||||
|
pub version_id: String,
|
||||||
|
}
|
||||||
|
pub fn detect_distro() -> Option<DistroInfo> // parses /etc/os-release
|
||||||
|
pub fn get_fuse_install_command(distro: &DistroInfo) -> Option<String>
|
||||||
|
```
|
||||||
|
- Install commands by distro family:
|
||||||
|
- Debian/Ubuntu: `apt install -y libfuse2t64` (24.04+) or `apt install -y libfuse2` (older)
|
||||||
|
- Fedora/RHEL: `dnf install -y fuse-libs`
|
||||||
|
- Arch/Manjaro: `pacman -S --noconfirm fuse2`
|
||||||
|
- openSUSE: `zypper install -y libfuse2`
|
||||||
|
- Polkit policy file (`data/app.driftwood.Driftwood.policy`):
|
||||||
|
```xml
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE policyconfig PUBLIC
|
||||||
|
"-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
|
||||||
|
"https://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd">
|
||||||
|
<policyconfig>
|
||||||
|
<action id="app.driftwood.Driftwood.install-fuse">
|
||||||
|
<description>Install FUSE library for AppImage support</description>
|
||||||
|
<message>Authentication is required to install the FUSE library</message>
|
||||||
|
<defaults>
|
||||||
|
<allow_any>auth_admin</allow_any>
|
||||||
|
<allow_inactive>auth_admin</allow_inactive>
|
||||||
|
<allow_active>auth_admin</allow_active>
|
||||||
|
</defaults>
|
||||||
|
</action>
|
||||||
|
</policyconfig>
|
||||||
|
```
|
||||||
|
- Wizard dialog (NavigationPage-style multi-step):
|
||||||
|
1. "FUSE is not installed" - explanation of what FUSE is and why it's needed
|
||||||
|
2. "We detected {distro}. Install with: `{command}`" - shows exact command
|
||||||
|
3. "Install now" button runs `pkexec sh -c "{command}"` and shows output
|
||||||
|
4. Re-check: calls `detect_system_fuse()` and shows success/failure
|
||||||
|
- Dashboard: yellow banner when FUSE is missing with "Fix now" button
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### F16. File Type Association Manager
|
||||||
|
|
||||||
|
**Complexity:** Significant (4 hrs)
|
||||||
|
**Problem:** AppImages don't register MIME types, so files don't open with them.
|
||||||
|
**Spec:** XDG MIME Applications - `~/.local/share/applications/mimeapps.list`
|
||||||
|
**Files:** `src/core/integrator.rs`, `src/ui/detail_view.rs`
|
||||||
|
|
||||||
|
**Changes:**
|
||||||
|
- `inspector.rs` already extracts desktop entry content including MimeType= field
|
||||||
|
- New function: `parse_mime_types(desktop_entry: &str) -> Vec<String>` - extracts MimeType= values
|
||||||
|
- When integrating: include `MimeType=` in generated .desktop file
|
||||||
|
- Detail view overview tab: "Supported file types" section listing MIME types
|
||||||
|
- Per-type toggle: "Set as default for .png files"
|
||||||
|
- Setting default:
|
||||||
|
1. Query current default: `xdg-mime query default {mime_type}`
|
||||||
|
2. Store in `system_modifications` with `previous_value` = current default
|
||||||
|
3. Set new: `xdg-mime default driftwood-{id}.desktop {mime_type}`
|
||||||
|
- Removing: restore previous default from `previous_value`
|
||||||
|
- Uninstall: handled by `undo_all_modifications()`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### F17. Taskbar/Panel Icon Fix (StartupWMClass)
|
||||||
|
|
||||||
|
**Complexity:** Significant (3 hrs)
|
||||||
|
**Problem:** Running AppImages show wrong/generic icon in taskbar.
|
||||||
|
**Files:** `src/core/integrator.rs`, `src/core/wayland.rs`, `src/ui/detail_view.rs`
|
||||||
|
|
||||||
|
**Changes:**
|
||||||
|
- During inspection: extract `StartupWMClass=` from embedded .desktop entry
|
||||||
|
- Store in new DB column: `startup_wm_class TEXT`
|
||||||
|
- When generating .desktop file for integration, include StartupWMClass if available
|
||||||
|
- After launch + Wayland analysis: read `/proc/{pid}/environ` for `GDK_BACKEND`, check window list via `xdotool` or `xprop` for WM_CLASS
|
||||||
|
- If WM_CLASS doesn't match StartupWMClass: log warning, offer to patch .desktop file
|
||||||
|
- Detail view system tab: show detected WM_CLASS, allow manual override
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### F18. Download Verification Helper
|
||||||
|
|
||||||
|
**Complexity:** Significant (4 hrs)
|
||||||
|
**Problem:** No way to verify AppImage authenticity.
|
||||||
|
**Files:** New `src/core/verification.rs`, `src/ui/detail_view.rs`, `src/ui/drop_dialog.rs`
|
||||||
|
|
||||||
|
**Changes:**
|
||||||
|
- AppImage signature spec: GPG signature embedded at offset stored in ELF section
|
||||||
|
- New module `verification.rs`:
|
||||||
|
```rust
|
||||||
|
pub enum VerificationStatus {
|
||||||
|
SignedValid { signer: String },
|
||||||
|
SignedInvalid { reason: String },
|
||||||
|
Unsigned,
|
||||||
|
ChecksumMatch,
|
||||||
|
ChecksumMismatch,
|
||||||
|
NotChecked,
|
||||||
|
}
|
||||||
|
pub fn check_embedded_signature(path: &Path) -> VerificationStatus
|
||||||
|
pub fn verify_sha256(path: &Path, expected: &str) -> VerificationStatus
|
||||||
|
```
|
||||||
|
- `check_embedded_signature`: runs `unsquashfs -l` to check for `.sha256` or signature files, or uses the AppImage --appimage-signature flag
|
||||||
|
- Drop dialog: after adding, show verification badge
|
||||||
|
- Detail view: "Verification: Signed by ..." or "Unsigned - verify manually"
|
||||||
|
- Manual verification: paste SHA256 hash, compare with computed hash
|
||||||
|
- New DB column: `verification_status TEXT` on appimages
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### F19. First-Run Permission Summary
|
||||||
|
|
||||||
|
**Complexity:** Medium (3 hrs)
|
||||||
|
**Problem:** Users don't know what an AppImage can access before running it.
|
||||||
|
**Files:** New `src/ui/permission_dialog.rs`, `src/ui/detail_view.rs`
|
||||||
|
|
||||||
|
**Changes:**
|
||||||
|
- Before first launch of any AppImage (check `launch_events` count = 0):
|
||||||
|
1. Show permission dialog:
|
||||||
|
- "MyApp will run with full access to your files and system"
|
||||||
|
- Icon + app name
|
||||||
|
- If `has_firejail()`: offer sandbox options
|
||||||
|
- "Run without restrictions" (default for now)
|
||||||
|
- "Run in Firejail sandbox (recommended)"
|
||||||
|
- "Don't show this again" checkbox (stored per-app in DB)
|
||||||
|
2. If user chooses Firejail: set `sandbox_mode = 'firejail'` in DB
|
||||||
|
- New DB column: `first_run_prompted INTEGER NOT NULL DEFAULT 0`
|
||||||
|
- Skip dialog if `first_run_prompted = 1`
|
||||||
|
- Preferences: global toggle to disable first-run prompts
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### F20. Default App Selector
|
||||||
|
|
||||||
|
**Complexity:** Medium (3 hrs)
|
||||||
|
**Problem:** Can't set AppImage as default browser, mail client, etc.
|
||||||
|
**Files:** `src/ui/detail_view.rs`, `src/core/integrator.rs`
|
||||||
|
|
||||||
|
**Changes:**
|
||||||
|
- Detect app capabilities from categories:
|
||||||
|
- `WebBrowser` -> can be default web browser
|
||||||
|
- `Email` -> can be default email client
|
||||||
|
- `FileManager` -> can be default file manager
|
||||||
|
- `TerminalEmulator` -> can be default terminal
|
||||||
|
- Detail view overview tab: "Set as default" section (only shown for applicable categories)
|
||||||
|
- Setting defaults:
|
||||||
|
- Browser: `xdg-settings set default-web-browser driftwood-{id}.desktop`
|
||||||
|
- Email: `xdg-mime default driftwood-{id}.desktop x-scheme-handler/mailto`
|
||||||
|
- File manager: `xdg-mime default driftwood-{id}.desktop inode/directory`
|
||||||
|
- Before setting: query current default, store in `system_modifications` with `previous_value`
|
||||||
|
- Uninstall: restore previous defaults via `undo_all_modifications()`
|
||||||
|
- Requirements: app must be integrated first (needs .desktop file)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### F21. Multi-User / System-Wide Install
|
||||||
|
|
||||||
|
**Complexity:** Significant (4 hrs)
|
||||||
|
**Problem:** Can't install for all users on a shared computer.
|
||||||
|
**Files:** `src/core/integrator.rs`, `src/ui/detail_view.rs`, polkit policy
|
||||||
|
|
||||||
|
**Changes:**
|
||||||
|
- Reuses polkit policy from F15 (add new action `app.driftwood.Driftwood.system-install`)
|
||||||
|
- "Install system-wide" option in detail view (requires app to be integrated first)
|
||||||
|
- Flow:
|
||||||
|
1. `pkexec cp {appimage} /opt/driftwood-apps/{filename}`
|
||||||
|
2. `pkexec chmod 755 /opt/driftwood-apps/{filename}`
|
||||||
|
3. Generate system .desktop in `/usr/share/applications/driftwood-{id}.desktop`
|
||||||
|
4. `pkexec cp {desktop_file} /usr/share/applications/`
|
||||||
|
5. Copy icon to `/usr/share/icons/hicolor/` via pkexec
|
||||||
|
6. `pkexec update-desktop-database /usr/share/applications/`
|
||||||
|
- Register all paths as `system_desktop`, `system_icon`, `system_binary` in system_modifications
|
||||||
|
- Uninstall system-wide: `pkexec rm` for each tracked path
|
||||||
|
- DB flag: `system_wide INTEGER NOT NULL DEFAULT 0`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### F22. CLI Enhancements
|
||||||
|
|
||||||
|
**Complexity:** Medium (3 hrs)
|
||||||
|
**Problem:** Missing install-from-URL and update-all commands.
|
||||||
|
**Files:** `src/cli.rs`
|
||||||
|
|
||||||
|
**New commands:**
|
||||||
|
- `driftwood install <url>` - Download from URL, validate, move to ~/Applications, set executable, register, optionally integrate
|
||||||
|
- `driftwood update --all` - Check all apps, download and apply available updates
|
||||||
|
- `driftwood autostart <path> --enable/--disable` - Toggle autostart for an AppImage
|
||||||
|
- `driftwood purge` - Remove ALL system modifications for all managed apps (for Driftwood removal)
|
||||||
|
- `driftwood verify <path>` - Check embedded signature or compare SHA256
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### F23. Portable Mode / USB Drive Support
|
||||||
|
|
||||||
|
**Complexity:** Major (6-8 hrs)
|
||||||
|
**Problem:** Can't manage AppImages on removable media.
|
||||||
|
**Files:** New `src/core/portable.rs`, `src/ui/library_view.rs`, `src/window.rs`, `src/core/database.rs`
|
||||||
|
|
||||||
|
**Changes:**
|
||||||
|
- New GSettings key: `watch-removable-media` type b, default false
|
||||||
|
- `portable.rs`:
|
||||||
|
```rust
|
||||||
|
pub fn detect_removable_mounts() -> Vec<MountInfo>
|
||||||
|
pub fn scan_mount_for_appimages(mount: &MountInfo) -> Vec<DiscoveredAppImage>
|
||||||
|
pub fn is_path_on_removable(path: &Path) -> bool
|
||||||
|
```
|
||||||
|
- Detection: parse `/proc/mounts` for removable media (type vfat, exfat, ntfs on /media/ or /run/media/)
|
||||||
|
- Alternative: use `gio::VolumeMonitor` to watch for mount/unmount events
|
||||||
|
- DB changes:
|
||||||
|
- New column: `is_portable INTEGER DEFAULT 0`
|
||||||
|
- New column: `mount_point TEXT`
|
||||||
|
- Library view: "Portable" filter/section showing apps on removable media
|
||||||
|
- When drive unmounts: grey out those apps, mark as unavailable
|
||||||
|
- When drive mounts: re-scan and refresh
|
||||||
|
- Portable apps skip the "copy to ~/Applications" step - they stay on the drive
|
||||||
|
- Integration: .desktop files use the full path (may break when unmounted - show warning)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### F24. "Similar to..." Recommendations
|
||||||
|
|
||||||
|
**Complexity:** Medium (3 hrs, depends on F26)
|
||||||
|
**Problem:** No app discovery within the tool.
|
||||||
|
**Files:** `src/ui/detail_view.rs`, `src/core/database.rs`
|
||||||
|
|
||||||
|
**Changes:**
|
||||||
|
- Requires catalog data from F26 (catalog_apps table populated)
|
||||||
|
- New DB query: `find_similar_apps(categories: &[String], exclude_id: i64, limit: i32) -> Vec<CatalogApp>`
|
||||||
|
- Matches on shared categories, weighted by specificity
|
||||||
|
- Detail view overview tab: "You might also like" section at the bottom
|
||||||
|
- Shows up to 5 catalog apps with icon, name, one-line description
|
||||||
|
- Click opens catalog detail page (F26)
|
||||||
|
- Falls back to nothing if catalog is empty
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### F25. AppImageHub In-App Catalog Browser
|
||||||
|
|
||||||
|
**Complexity:** Major (8-12 hrs)
|
||||||
|
**Problem:** No way to discover and install new AppImages from within the app.
|
||||||
|
**Data source:** `https://appimage.github.io/feed.json` (~1500 apps)
|
||||||
|
**Files:** New `src/ui/catalog_view.rs`, new `src/ui/catalog_detail.rs`, `src/core/database.rs`, `src/window.rs`
|
||||||
|
|
||||||
|
**Architecture:**
|
||||||
|
|
||||||
|
#### Data Layer
|
||||||
|
- Fetch `feed.json` and parse into `catalog_apps` table (already exists in DB schema)
|
||||||
|
- Store in `catalog_sources` as source record
|
||||||
|
- Fields mapped from feed.json:
|
||||||
|
- `name` -> `name`
|
||||||
|
- `description` -> `description`
|
||||||
|
- `categories` -> `categories` (joined with ;)
|
||||||
|
- `authors[0].name` -> `author`
|
||||||
|
- `license` -> `license`
|
||||||
|
- `links` (type=GitHub) -> `repository_url`
|
||||||
|
- `links` (type=Download) -> `download_url`
|
||||||
|
- `icons[0]` -> `icon_url` (prefix with `https://appimage.github.io/database/`)
|
||||||
|
- `screenshots` -> `screenshots` (JSON array)
|
||||||
|
- Refresh: on first open, then daily if enabled
|
||||||
|
- New GSettings key: `catalog-last-refreshed` type s, default ''
|
||||||
|
|
||||||
|
#### Catalog Browse View (NavigationPage)
|
||||||
|
- Header: search bar + category filter chips
|
||||||
|
- Categories from feed: AudioVideo, Development, Education, Game, Graphics, Network, Office, Science, System, Utility
|
||||||
|
- Grid of catalog app cards (reuse app_card pattern):
|
||||||
|
- Icon (fetched from URL, cached locally)
|
||||||
|
- App name
|
||||||
|
- Short description (first line)
|
||||||
|
- Category badge
|
||||||
|
- Pagination: load 50 at a time, "Load more" button
|
||||||
|
- Search: filters by name and description (client-side, data is local)
|
||||||
|
|
||||||
|
#### Catalog App Detail (NavigationPage pushed on click)
|
||||||
|
- App icon (large)
|
||||||
|
- Name, author, license
|
||||||
|
- Full description
|
||||||
|
- Screenshots carousel (if available)
|
||||||
|
- "Install" button (suggested style)
|
||||||
|
- Source link (opens GitHub/website in browser)
|
||||||
|
|
||||||
|
#### Install Flow
|
||||||
|
1. User clicks "Install"
|
||||||
|
2. Resolve download URL:
|
||||||
|
- If `download_url` points to GitHub releases page: fetch latest release via GitHub API (`https://api.github.com/repos/{owner}/{repo}/releases/latest`), find .AppImage asset
|
||||||
|
- If direct link: use as-is
|
||||||
|
3. Download with progress bar (reqwest or gio file download)
|
||||||
|
4. Validate: check AppImage magic bytes
|
||||||
|
5. Move to ~/Applications, set executable
|
||||||
|
6. Register in DB with `source_url` set
|
||||||
|
7. Run full analysis pipeline
|
||||||
|
8. Optionally integrate (based on `auto-integrate` setting)
|
||||||
|
9. Navigate to the app's detail view in library
|
||||||
|
|
||||||
|
#### Navigation
|
||||||
|
- Main sidebar/navigation: add "Catalog" entry alongside Dashboard and Library
|
||||||
|
- Or: floating action button on library view "Browse catalog"
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## New GSettings Keys Summary
|
||||||
|
|
||||||
|
| Key | Type | Default | Range/Choices | Feature |
|
||||||
|
|-----|------|---------|---------------|---------|
|
||||||
|
| `update-check-interval-hours` | i | 24 | 1-168 | F11 |
|
||||||
|
| `last-update-check` | s | '' | - | F11 |
|
||||||
|
| `catalog-last-refreshed` | s | '' | - | F25 |
|
||||||
|
| `watch-removable-media` | b | false | - | F23 |
|
||||||
|
| `show-first-run-prompt` | b | true | - | F19 |
|
||||||
|
|
||||||
|
## New Database Columns Summary
|
||||||
|
|
||||||
|
| Table | Column | Type | Default | Feature |
|
||||||
|
|-------|--------|------|---------|---------|
|
||||||
|
| appimages | previous_version_path | TEXT | NULL | F3 |
|
||||||
|
| appimages | source_url | TEXT | NULL | F4 |
|
||||||
|
| appimages | autostart | INTEGER | 0 | F8 |
|
||||||
|
| appimages | startup_wm_class | TEXT | NULL | F17 |
|
||||||
|
| appimages | verification_status | TEXT | NULL | F18 |
|
||||||
|
| appimages | first_run_prompted | INTEGER | 0 | F19 |
|
||||||
|
| appimages | system_wide | INTEGER | 0 | F21 |
|
||||||
|
| appimages | is_portable | INTEGER | 0 | F23 |
|
||||||
|
| appimages | mount_point | TEXT | NULL | F23 |
|
||||||
|
|
||||||
|
## New Files Summary
|
||||||
|
|
||||||
|
| File | Feature | Purpose |
|
||||||
|
|------|---------|---------|
|
||||||
|
| `src/ui/fuse_wizard.rs` | F15 | FUSE installation wizard dialog |
|
||||||
|
| `src/ui/batch_update_dialog.rs` | F12 | Batch update progress dialog |
|
||||||
|
| `src/ui/permission_dialog.rs` | F19 | First-run permission summary |
|
||||||
|
| `src/ui/catalog_view.rs` | F25 | Catalog browse/search page |
|
||||||
|
| `src/ui/catalog_detail.rs` | F25 | Catalog app detail page |
|
||||||
|
| `src/core/verification.rs` | F18 | Signature and checksum verification |
|
||||||
|
| `src/core/portable.rs` | F23 | Removable media detection and management |
|
||||||
|
| `data/app.driftwood.Driftwood.policy` | F15, F21 | Polkit policy for privileged operations |
|
||||||
|
|
||||||
|
## Implementation Order
|
||||||
|
|
||||||
|
The features are numbered F1-F25 in order of complexity. Implement sequentially - some later features depend on earlier ones:
|
||||||
|
|
||||||
|
- F25 (Catalog) depends on nothing but is largest
|
||||||
|
- F24 (Similar to) depends on F25
|
||||||
|
- F12 (Update All) depends on F11 (Background checks)
|
||||||
|
- F13 (Full Uninstall) depends on system_modifications table (core architecture)
|
||||||
|
- F20 (Default App) depends on F16 (MIME associations)
|
||||||
|
|
||||||
|
**Critical path:** Core architecture (system_modifications) -> F1-F7 quick wins -> F8-F14 medium features -> F15-F22 significant features -> F23-F25 major features
|
||||||
Reference in New Issue
Block a user