Compare commits

..

85 Commits

Author SHA1 Message Date
lashman edaf98b10d Add app icons, screenshots, and complete AppStream metainfo 2026-03-01 14:46:41 +02:00
lashman 292b91d612 Rewrite README with better header and project framing 2026-03-01 13:12:45 +02:00
lashman c8683f3d72 Rewrite README with feature documentation 2026-03-01 12:54:55 +02:00
lashman 2cfb6fa0f0 Change license to CC0-1.0 2026-03-01 12:49:17 +02:00
lashman 622a6ecf79 Add WCAG 2.2 AAA compliance and automated AT-SPI audit tool 2026-03-01 12:44:21 +02:00
lashman 43db437ac7 Add tags, export/import, and changelog features 2026-03-01 01:01:43 +02:00
lashman 49b2a2f592 Add UX enhancements: carousel, filter chips, command palette, and more 2026-03-01 00:39:43 +02:00
lashman b73c457c34 Add AppImageHub.com OCS API as primary catalog source 2026-02-28 20:33:40 +02:00
lashman 4920211eec Add GitHub metadata enrichment for catalog apps 2026-02-28 16:49:13 +02:00
lashman d0f3984d32 Add Launch and Uninstall buttons to detail view header 2026-02-28 01:56:21 +02:00
lashman 5f3b7f892b Use relative time for update_checked in detail view 2026-02-28 01:54:51 +02:00
lashman 8f40262dff Use relative timestamps everywhere instead of raw date strings 2026-02-28 01:52:56 +02:00
lashman 5aff13e419 Use undo toast for integration removal instead of immediate action 2026-02-28 01:51:40 +02:00
lashman b120daa9c7 Consolidate preferences into General and Updates pages 2026-02-28 01:50:38 +02:00
lashman ffabc4c2ed Show updates-available banner in Installed view 2026-02-28 01:48:50 +02:00
lashman ad7c850ff0 Add Ctrl+1/2/3 keyboard shortcuts for view switching 2026-02-28 01:45:32 +02:00
lashman bb055a5503 Default to Catalog tab when library is empty on first launch 2026-02-28 01:44:41 +02:00
lashman fc1904be60 Replace technical jargon with user-friendly labels in detail view 2026-02-28 01:44:09 +02:00
lashman 0842f5ed61 Consolidate detail view from 4 tabs to 2 (About + Details) 2026-02-28 01:42:35 +02:00
lashman f2cf2b3466 Simplify app card badges, remove colored status borders 2026-02-28 01:41:28 +02:00
lashman e73ea22e23 Show update count badge on Updates tab in ViewSwitcherBar 2026-02-28 01:40:22 +02:00
lashman 7ad8903393 Slim down hamburger menu, move Preferences to bottom section 2026-02-28 01:38:40 +02:00
lashman dd8790c5d1 Improve catalog view with relative timestamps, auto-refresh, and installed badges 2026-02-28 01:38:01 +02:00
lashman a187316a1a Add scan button, sort dropdown, and improved empty state to library view 2026-02-28 01:36:47 +02:00
lashman f431cea768 Replace single NavigationView with ViewSwitcher (Installed, Catalog, Updates) 2026-02-28 01:33:43 +02:00
lashman 9fa48f1bac Add dedicated Updates view with check-now and update-all 2026-02-28 01:26:10 +02:00
lashman baa81243d3 Add relative_time() helper for human-readable timestamps 2026-02-28 01:24:08 +02:00
lashman 475a23f3dd Fix catalog refresh: handle null fields in AppImageHub feed JSON 2026-02-28 00:46:39 +02:00
lashman f06483194c Clean up unused catalog function and wire source info into catalog view 2026-02-28 00:25:05 +02:00
lashman 8c5ee314d8 Add AppImageHub in-app catalog browser with search, categories, and install 2026-02-28 00:23:36 +02:00
lashman db83dc3224 Add similar app recommendations from shared categories 2026-02-28 00:17:55 +02:00
lashman cdeab85cf6 Add portable mode with removable media detection and scanning 2026-02-28 00:16:52 +02:00
lashman 5b3c95ae26 Add update-all, autostart, purge, and verify CLI commands 2026-02-28 00:13:31 +02:00
lashman d05a0e39e0 Add system-wide installation via pkexec with polkit policy 2026-02-28 00:11:57 +02:00
lashman 0b6db27ac2 Add default application selector for browsers, email, and file managers 2026-02-28 00:10:39 +02:00
lashman 45e1c57842 Add first-run permission summary dialog before launch 2026-02-28 00:07:49 +02:00
lashman a515ee6b4f Add download verification with signature and SHA256 support 2026-02-28 00:05:43 +02:00
lashman e52397f64b Extract and apply StartupWMClass for proper taskbar icons 2026-02-28 00:02:44 +02:00
lashman 1abe783dfc Add file type association manager with MIME type support 2026-02-28 00:00:42 +02:00
lashman 8e95bbaabd Add FUSE fix wizard with pkexec installation 2026-02-27 23:59:26 +02:00
lashman 6208dab2b7 Add icon preview to drag-and-drop dialog 2026-02-27 23:58:09 +02:00
lashman 619b3cb447 Add full uninstall dialog with data cleanup options 2026-02-27 23:56:56 +02:00
lashman 84e33d06c8 Add one-click Update All with batch progress dialog 2026-02-27 23:55:04 +02:00
lashman 325cbdd87f Add background update checks with configurable interval 2026-02-27 23:52:28 +02:00
lashman e3433edb68 Add per-app storage breakdown to detail view 2026-02-27 23:48:21 +02:00
lashman 2aebccd914 Add system notification support for crashes and updates 2026-02-27 23:47:19 +02:00
lashman bee43b29c1 Add autostart manager with XDG autostart support 2026-02-27 23:45:58 +02:00
lashman fcd57a372d Add batch selection and bulk operations to library view 2026-02-27 23:45:01 +02:00
lashman ddde786c41 Wire auto-integrate setting to scan pipeline 2026-02-27 23:42:22 +02:00
lashman 8c57a06a79 Add launch statistics to dashboard 2026-02-27 23:41:22 +02:00
lashman 7ec32ed077 Add source URL tracking and display for AppImages 2026-02-27 23:40:18 +02:00
lashman c7bbc26822 Add version rollback support for AppImage updates 2026-02-27 23:38:36 +02:00
lashman 3ab12b0b8f Add keep-in-place option for drag-and-drop imports 2026-02-27 23:37:03 +02:00
lashman b717689ace Auto-fix executable permissions on discovered AppImages 2026-02-27 23:36:14 +02:00
lashman 6e97feddde Add system modification tracking for reversible installs 2026-02-27 23:35:27 +02:00
lashman 011c07820d Fix second audit findings and restore crash detection dialog 2026-02-27 22:48:43 +02:00
lashman 804ba35a70 Fix 29 audit findings across all severity tiers 2026-02-27 22:08:53 +02:00
lashman ce11431cdf Remove module-wide dead_code allows, annotate individual items instead 2026-02-27 21:05:56 +02:00
lashman cddbe5b2e2 Wire backup, notification, report export, and file watcher modules into UI 2026-02-27 21:03:19 +02:00
lashman 668206e5e5 Switch crash dialog from AlertDialog to Dialog for proper sizing 2026-02-27 20:31:03 +02:00
lashman b4f2d1bf0c Enlarge crash error dialog for better readability 2026-02-27 20:25:09 +02:00
lashman 11c754a8c1 Add launch crash detection with detailed error dialog, fix all warnings 2026-02-27 20:23:10 +02:00
lashman e20759d1cb Rewrite detail view copy for beginners, add tab transitions and lightbox fixes 2026-02-27 19:48:59 +02:00
lashman fb632c16c6 Stop executing AppImages during analysis, add screenshot lightbox and favicons 2026-02-27 18:58:12 +02:00
lashman a755e546d0 Fix performance, add screenshots, make banner scrollable 2026-02-27 18:44:50 +02:00
lashman 87f4e5d7bf Add AppImage metadata extraction, display, and bug fixes 2026-02-27 18:31:07 +02:00
lashman 4211ff5ce0 Fix icon extraction, drop overlay styling, and card padding 2026-02-27 17:49:04 +02:00
lashman a1c7f72d1d Add security, i18n, analysis, backup, notifications 2026-02-27 17:16:41 +02:00
lashman d6a6fb9eca Fix GTK CSS warnings and popover cleanup on widget destroy 2026-02-27 11:14:14 +02:00
lashman b0852f781c Implement UI/UX overhaul - cards, list, tabbed detail, context menu 2026-02-27 11:10:23 +02:00
lashman 9df79095f2 Suppress unused warning on announce() accessibility helper 2026-02-27 10:10:21 +02:00
lashman 0c9f2caa2c Update window title dynamically for WCAG 2.4.8 Location compliance 2026-02-27 10:09:31 +02:00
lashman bea039367d Add WCAG accessible labels and confirmation dialog to duplicate removal 2026-02-27 10:07:34 +02:00
lashman cd76d268e2 Add WCAG accessible labels and confirmation dialog to cleanup wizard 2026-02-27 10:07:17 +02:00
lashman ea6904040a Add WCAG accessible labels to preferences buttons and directory list 2026-02-27 10:06:37 +02:00
lashman ec413f4c6f Add WCAG accessible labels to integration dialog list boxes 2026-02-27 10:06:33 +02:00
lashman 1a177abb75 Rewrite update dialog text to plain language for WCAG readability 2026-02-27 10:06:30 +02:00
lashman 6aafaeaa09 Add WCAG tooltips and expanded abbreviations to security report 2026-02-27 10:06:21 +02:00
lashman 67e01c45be Add WCAG tooltips, plain language, busy states, and banner role to detail view 2026-02-27 10:04:46 +02:00
lashman d1628cf6d5 Add WCAG accessible labels to library view buttons, list box, and search bar 2026-02-27 10:03:28 +02:00
lashman 647fc39054 Add WCAG tooltips for technical terms on dashboard 2026-02-27 10:03:16 +02:00
lashman 8999fb99b3 Add accessible label to integration emblem overlay in app cards 2026-02-27 10:02:53 +02:00
lashman cc78b44d41 Add WCAG accessibility helpers: labeled badges, live announcements, copy button label 2026-02-27 10:01:37 +02:00
lashman 2f5e5cefc1 Add WCAG AAA focus indicators, high-contrast mode, and reduced-motion coverage 2026-02-27 09:59:44 +02:00
lashman c3620b487d Implement Driftwood AppImage manager 2026-02-26 23:04:27 +02:00
4 changed files with 30 additions and 35 deletions
+19 -24
View File
@@ -29,7 +29,6 @@ pub struct AppImageRecord {
pub first_seen: String, pub first_seen: String,
pub last_scanned: String, pub last_scanned: String,
pub file_modified: Option<String>, pub file_modified: Option<String>,
// Phase 2 fields
pub fuse_status: Option<String>, pub fuse_status: Option<String>,
pub wayland_status: Option<String>, pub wayland_status: Option<String>,
pub update_info: Option<String>, pub update_info: Option<String>,
@@ -38,20 +37,17 @@ pub struct AppImageRecord {
pub update_checked: Option<String>, pub update_checked: Option<String>,
pub update_url: Option<String>, pub update_url: Option<String>,
pub notes: Option<String>, pub notes: Option<String>,
// Phase 3 fields
pub sandbox_mode: Option<String>, pub sandbox_mode: Option<String>,
// Phase 5 fields
pub runtime_wayland_status: Option<String>, pub runtime_wayland_status: Option<String>,
pub runtime_wayland_checked: Option<String>, pub runtime_wayland_checked: Option<String>,
// Async analysis pipeline // Async analysis pipeline
pub analysis_status: Option<String>, pub analysis_status: Option<String>,
// Custom launch arguments // Custom launch arguments
pub launch_args: Option<String>, pub launch_args: Option<String>,
// Phase 6 fields
pub tags: Option<String>, pub tags: Option<String>,
pub pinned: bool, pub pinned: bool,
pub avg_startup_ms: Option<i64>, pub avg_startup_ms: Option<i64>,
// Phase 9 fields - extended metadata // extended metadata
pub appstream_id: Option<String>, pub appstream_id: Option<String>,
pub appstream_description: Option<String>, pub appstream_description: Option<String>,
pub generic_name: Option<String>, pub generic_name: Option<String>,
@@ -69,7 +65,7 @@ pub struct AppImageRecord {
pub desktop_actions: Option<String>, pub desktop_actions: Option<String>,
pub has_signature: bool, pub has_signature: bool,
pub screenshot_urls: Option<String>, pub screenshot_urls: Option<String>,
// Phase 11 fields - system modification tracking // system modification tracking
pub previous_version_path: Option<String>, pub previous_version_path: Option<String>,
pub source_url: Option<String>, pub source_url: Option<String>,
pub autostart: bool, pub autostart: bool,
@@ -346,7 +342,7 @@ impl Database {
} }
fn init_schema(&self) -> SqlResult<()> { fn init_schema(&self) -> SqlResult<()> {
// Phase 1 base tables // base tables
self.conn.execute_batch( self.conn.execute_batch(
"CREATE TABLE IF NOT EXISTS schema_version ( "CREATE TABLE IF NOT EXISTS schema_version (
version INTEGER NOT NULL version INTEGER NOT NULL
@@ -535,8 +531,8 @@ impl Database {
} }
fn migrate_to_v2(&self) -> SqlResult<()> { fn migrate_to_v2(&self) -> SqlResult<()> {
// Add Phase 2 columns to appimages table // Add columns to appimages table
let phase2_columns = [ let extra_columns = [
"fuse_status TEXT", "fuse_status TEXT",
"wayland_status TEXT", "wayland_status TEXT",
"update_info TEXT", "update_info TEXT",
@@ -546,12 +542,11 @@ impl Database {
"update_url TEXT", "update_url TEXT",
"notes TEXT", "notes TEXT",
]; ];
for col in &phase2_columns { for col in &extra_columns {
let sql = format!("ALTER TABLE appimages ADD COLUMN {}", col); let sql = format!("ALTER TABLE appimages ADD COLUMN {}", col);
self.conn.execute_batch(&sql).ok(); self.conn.execute_batch(&sql).ok();
} }
// Phase 2 tables
self.conn.execute_batch( self.conn.execute_batch(
"CREATE TABLE IF NOT EXISTS launch_events ( "CREATE TABLE IF NOT EXISTS launch_events (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -595,7 +590,7 @@ impl Database {
} }
fn migrate_to_v3(&self) -> SqlResult<()> { fn migrate_to_v3(&self) -> SqlResult<()> {
// Phase 3 tables: security scanning // security scanning
self.conn.execute_batch( self.conn.execute_batch(
"CREATE TABLE IF NOT EXISTS bundled_libraries ( "CREATE TABLE IF NOT EXISTS bundled_libraries (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -1712,7 +1707,7 @@ impl Database {
self.conn.query_row("SELECT COUNT(*) FROM appimages", [], |row| row.get(0)) self.conn.query_row("SELECT COUNT(*) FROM appimages", [], |row| row.get(0))
} }
// --- Phase 2: Status updates --- // --- Status updates ---
pub fn update_fuse_status(&self, id: i64, status: &str) -> SqlResult<()> { pub fn update_fuse_status(&self, id: i64, status: &str) -> SqlResult<()> {
self.conn.execute( self.conn.execute(
@@ -1811,7 +1806,7 @@ impl Database {
rows.collect() rows.collect()
} }
// --- Phase 2: Launch tracking --- // --- Launch tracking ---
pub fn record_launch(&self, appimage_id: i64, source: &str) -> SqlResult<()> { pub fn record_launch(&self, appimage_id: i64, source: &str) -> SqlResult<()> {
self.conn.execute( self.conn.execute(
@@ -1854,7 +1849,7 @@ impl Database {
rows.collect() rows.collect()
} }
// --- Phase 2: Update history --- // --- Update history ---
pub fn record_update( pub fn record_update(
&self, &self,
@@ -1874,7 +1869,7 @@ impl Database {
Ok(()) Ok(())
} }
// --- Phase 3: Security scanning --- // --- Security scanning ---
pub fn clear_bundled_libraries(&self, appimage_id: i64) -> SqlResult<()> { pub fn clear_bundled_libraries(&self, appimage_id: i64) -> SqlResult<()> {
self.conn.execute( self.conn.execute(
@@ -2026,7 +2021,7 @@ impl Database {
Ok(summary) Ok(summary)
} }
// --- Phase 3: App data paths --- // --- App data paths ---
pub fn insert_app_data_path( pub fn insert_app_data_path(
&self, &self,
@@ -2106,7 +2101,7 @@ impl Database {
Ok(()) Ok(())
} }
// --- Phase 5: Runtime Wayland --- // --- Runtime Wayland ---
pub fn update_runtime_wayland_status(&self, id: i64, status: &str) -> SqlResult<()> { pub fn update_runtime_wayland_status(&self, id: i64, status: &str) -> SqlResult<()> {
self.conn.execute( self.conn.execute(
@@ -2116,7 +2111,7 @@ impl Database {
Ok(()) Ok(())
} }
// --- Phase 5: Config Backups --- // --- Config Backups ---
pub fn insert_config_backup( pub fn insert_config_backup(
&self, &self,
@@ -2186,7 +2181,7 @@ impl Database {
Ok(()) Ok(())
} }
// --- Phase 5: CVE Notifications --- // --- CVE Notifications ---
pub fn has_cve_been_notified(&self, appimage_id: i64, cve_id: &str) -> SqlResult<bool> { pub fn has_cve_been_notified(&self, appimage_id: i64, cve_id: &str) -> SqlResult<bool> {
let count: i32 = self.conn.query_row( let count: i32 = self.conn.query_row(
@@ -2211,7 +2206,7 @@ impl Database {
Ok(()) Ok(())
} }
// --- Phase 5: Sandbox Profiles --- // --- Sandbox Profiles ---
pub fn insert_sandbox_profile( pub fn insert_sandbox_profile(
&self, &self,
@@ -2281,7 +2276,7 @@ impl Database {
rows.collect() rows.collect()
} }
// --- Phase 6: Tags, Pin, Startup Time --- // --- Tags, Pin, Startup Time ---
pub fn update_tags(&self, id: i64, tags: Option<&str>) -> SqlResult<()> { pub fn update_tags(&self, id: i64, tags: Option<&str>) -> SqlResult<()> {
self.conn.execute( self.conn.execute(
@@ -2355,7 +2350,7 @@ impl Database {
} }
} }
// --- Phase 5: Runtime Updates --- // --- Runtime Updates ---
pub fn record_runtime_update( pub fn record_runtime_update(
&self, &self,
@@ -3259,7 +3254,7 @@ mod tests {
} }
#[test] #[test]
fn test_phase2_status_updates() { fn test_status_updates() {
let db = Database::open_in_memory().unwrap(); let db = Database::open_in_memory().unwrap();
let id = db.upsert_appimage("/path/app.AppImage", "app.AppImage", Some(2), 1000, true, None).unwrap(); let id = db.upsert_appimage("/path/app.AppImage", "app.AppImage", Some(2), 1000, true, None).unwrap();
+2 -2
View File
@@ -87,7 +87,7 @@ pub fn detect_duplicates(db: &Database) -> Vec<DuplicateGroup> {
let mut groups = Vec::new(); let mut groups = Vec::new();
// Phase 1: Find exact duplicates by SHA256 hash // Find exact duplicates by SHA256 hash
let hash_groups = group_by_hash(&records); let hash_groups = group_by_hash(&records);
for (hash, members) in &hash_groups { for (hash, members) in &hash_groups {
if members.len() > 1 { if members.len() > 1 {
@@ -95,7 +95,7 @@ pub fn detect_duplicates(db: &Database) -> Vec<DuplicateGroup> {
} }
} }
// Phase 2: Find same app name groups (excluding already-found exact dupes) // Find same app name groups (excluding already-found exact dupes)
let exact_dupe_ids: std::collections::HashSet<i64> = groups let exact_dupe_ids: std::collections::HashSet<i64> = groups
.iter() .iter()
.flat_map(|g| g.members.iter().map(|m| m.record.id)) .flat_map(|g| g.members.iter().map(|m| m.record.id))
+2 -2
View File
@@ -117,7 +117,7 @@ pub fn show_drop_dialog(
let on_complete_ref = on_complete.clone(); let on_complete_ref = on_complete.clone();
glib::spawn_future_local(async move { glib::spawn_future_local(async move {
// Phase 1: Fast registration (copy + DB upsert only) // Fast registration (copy + DB upsert only)
let result = gio::spawn_blocking(move || { let result = gio::spawn_blocking(move || {
register_dropped_files(&files, copy) register_dropped_files(&files, copy)
}) })
@@ -143,7 +143,7 @@ pub fn show_drop_dialog(
toast_ref.add_toast(widgets::info_toast(&msg)); toast_ref.add_toast(widgets::info_toast(&msg));
} }
// Phase 2: Background analysis for each file // Background analysis for each file
let on_complete_bg = on_complete_ref.clone(); let on_complete_bg = on_complete_ref.clone();
for reg in registered { for reg in registered {
let on_complete_inner = on_complete_bg.clone(); let on_complete_inner = on_complete_bg.clone();
+7 -7
View File
@@ -1571,11 +1571,11 @@ impl DriftwoodWindow {
let toast_overlay = self.imp().toast_overlay.get().unwrap().clone(); let toast_overlay = self.imp().toast_overlay.get().unwrap().clone();
let window_weak = self.downgrade(); let window_weak = self.downgrade();
// Two-phase scan: // Two-pass scan:
// Phase 1 (fast): discover files, upsert into DB, mark pending analysis // Fast pass: discover files, upsert into DB, mark pending analysis
// Phase 2 (background): heavy analysis per file // Background pass: heavy analysis per file
glib::spawn_future_local(async move { glib::spawn_future_local(async move {
// Phase 1: Fast registration // Fast registration
let result = gio::spawn_blocking(move || { let result = gio::spawn_blocking(move || {
let bg_db = Database::open().expect("Failed to open database for scan"); let bg_db = Database::open().expect("Failed to open database for scan");
let start = Instant::now(); let start = Instant::now();
@@ -1646,7 +1646,7 @@ impl DriftwoodWindow {
} }
log::info!( log::info!(
"Scan phase 1: {} files, {} new, {} removed, {} skipped (unchanged), took {}ms", "Scan: {} files, {} new, {} removed, {} skipped (unchanged), took {}ms",
total, new_count, removed_count, skipped_count, start.elapsed().as_millis() total, new_count, removed_count, skipped_count, start.elapsed().as_millis()
); );
@@ -1665,7 +1665,7 @@ impl DriftwoodWindow {
.await; .await;
if let Ok((total, new_count, needs_analysis)) = result { if let Ok((total, new_count, needs_analysis)) = result {
// Dismiss the "Scanning..." toast now that Phase 1 is done // Dismiss the "Scanning..." toast now that fast scan is done
if let Some(ref toast) = scan_toast { if let Some(ref toast) = scan_toast {
toast.dismiss(); toast.dismiss();
} }
@@ -1689,7 +1689,7 @@ impl DriftwoodWindow {
}; };
toast_overlay.add_toast(widgets::info_toast(&msg)); toast_overlay.add_toast(widgets::info_toast(&msg));
// Phase 2: Background analysis per file with debounced UI refresh // Background analysis per file with debounced UI refresh
let running = analysis::running_count(); let running = analysis::running_count();
if running > 0 { if running > 0 {
log::info!("Analyzing {} AppImage(s) in background ({} already running)", needs_analysis.len(), running); log::info!("Analyzing {} AppImage(s) in background ({} already running)", needs_analysis.len(), running);