Add icon preview to drag-and-drop dialog
Fast icon extraction pulls .DirIcon from the squashfs without full analysis. Single-file drops show a 64px preview in the dialog while the user chooses an import option.
This commit is contained in:
@@ -603,6 +603,50 @@ fn make_app_id(name: &str) -> String {
|
|||||||
.to_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<PathBuf> {
|
||||||
|
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.
|
/// Inspect an AppImage and extract its metadata.
|
||||||
pub fn inspect_appimage(
|
pub fn inspect_appimage(
|
||||||
path: &Path,
|
path: &Path,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use std::rc::Rc;
|
|||||||
use crate::core::analysis;
|
use crate::core::analysis;
|
||||||
use crate::core::database::Database;
|
use crate::core::database::Database;
|
||||||
use crate::core::discovery;
|
use crate::core::discovery;
|
||||||
|
use crate::core::inspector;
|
||||||
use crate::i18n::{i18n, ni18n_f};
|
use crate::i18n::{i18n, ni18n_f};
|
||||||
|
|
||||||
/// Registered file info returned by the fast registration phase.
|
/// Registered file info returned by the fast registration phase.
|
||||||
@@ -63,6 +64,28 @@ pub fn show_drop_dialog(
|
|||||||
.body(&body)
|
.body(&body)
|
||||||
.build();
|
.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("cancel", &i18n("Cancel"));
|
||||||
dialog.add_response("keep-in-place", &i18n("Keep in place"));
|
dialog.add_response("keep-in-place", &i18n("Keep in place"));
|
||||||
dialog.add_response("copy-only", &i18n("Copy to Applications"));
|
dialog.add_response("copy-only", &i18n("Copy to Applications"));
|
||||||
|
|||||||
Reference in New Issue
Block a user