diff --git a/src/core/inspector.rs b/src/core/inspector.rs index cc12fb6..5d8eece 100644 --- a/src/core/inspector.rs +++ b/src/core/inspector.rs @@ -603,6 +603,50 @@ fn make_app_id(name: &str) -> String { .to_string() } +/// Quickly extract just the icon from an AppImage (for preview). +/// Only extracts .DirIcon and root-level .png/.svg files. +/// Returns the path to the cached icon if successful. +pub fn extract_icon_fast(appimage_path: &Path) -> Option { + if !has_unsquashfs() { + return None; + } + + let offset = find_squashfs_offset(appimage_path).ok()?; + let tmp = tempfile::tempdir().ok()?; + let dest = tmp.path().join("icon_extract"); + + let status = Command::new("unsquashfs") + .arg("-offset") + .arg(offset.to_string()) + .arg("-no-progress") + .arg("-force") + .arg("-dest") + .arg(&dest) + .arg(appimage_path) + .arg(".DirIcon") + .arg("*.png") + .arg("*.svg") + .stdout(std::process::Stdio::null()) + .stderr(std::process::Stdio::null()) + .status() + .ok()?; + + if !status.success() { + return None; + } + + let icon_path = find_icon(&dest, None)?; + + // Generate app_id from filename + let stem = appimage_path + .file_stem() + .and_then(|s| s.to_str()) + .unwrap_or("unknown"); + let app_id = make_app_id(stem); + + cache_icon(&icon_path, &app_id) +} + /// Inspect an AppImage and extract its metadata. pub fn inspect_appimage( path: &Path, diff --git a/src/ui/drop_dialog.rs b/src/ui/drop_dialog.rs index 76cfcb4..c8a830d 100644 --- a/src/ui/drop_dialog.rs +++ b/src/ui/drop_dialog.rs @@ -6,6 +6,7 @@ use std::rc::Rc; use crate::core::analysis; use crate::core::database::Database; use crate::core::discovery; +use crate::core::inspector; use crate::i18n::{i18n, ni18n_f}; /// Registered file info returned by the fast registration phase. @@ -63,6 +64,28 @@ pub fn show_drop_dialog( .body(&body) .build(); + // For single file, try fast icon extraction for a preview + if count == 1 { + let file_path = files[0].clone(); + let dialog_ref = dialog.clone(); + glib::spawn_future_local(async move { + let result = gio::spawn_blocking(move || { + inspector::extract_icon_fast(&file_path) + }) + .await; + + if let Ok(Some(icon_path)) = result { + let image = gtk::Image::builder() + .file(icon_path.to_string_lossy().as_ref()) + .pixel_size(64) + .halign(gtk::Align::Center) + .margin_top(12) + .build(); + dialog_ref.set_extra_child(Some(&image)); + } + }); + } + dialog.add_response("cancel", &i18n("Cancel")); dialog.add_response("keep-in-place", &i18n("Keep in place")); dialog.add_response("copy-only", &i18n("Copy to Applications"));