Add file type association manager with MIME type support
Generated .desktop files now include MimeType and StartupWMClass when available. Detail view system tab shows MIME types with per-type "Set Default" buttons that use xdg-mime and track previous defaults in system_modifications for reversal.
This commit is contained in:
@@ -104,7 +104,10 @@ pub fn integrate(record: &AppImageRecord) -> Result<IntegrationResult, Integrati
|
|||||||
|
|
||||||
let icon_id = format!("driftwood-{}", app_id);
|
let icon_id = format!("driftwood-{}", app_id);
|
||||||
|
|
||||||
let desktop_content = format!("\
|
let mime_types = record.mime_types.as_deref().unwrap_or("");
|
||||||
|
let wm_class = record.startup_wm_class.as_deref().unwrap_or("");
|
||||||
|
|
||||||
|
let mut desktop_content = format!("\
|
||||||
[Desktop Entry]
|
[Desktop Entry]
|
||||||
Type=Application
|
Type=Application
|
||||||
Name={name}
|
Name={name}
|
||||||
@@ -128,6 +131,13 @@ X-AppImage-Integrated-Date={date}
|
|||||||
date = now,
|
date = now,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if !mime_types.is_empty() {
|
||||||
|
desktop_content.push_str(&format!("MimeType={}\n", mime_types));
|
||||||
|
}
|
||||||
|
if !wm_class.is_empty() {
|
||||||
|
desktop_content.push_str(&format!("StartupWMClass={}\n", wm_class));
|
||||||
|
}
|
||||||
|
|
||||||
fs::write(&desktop_path, &desktop_content)?;
|
fs::write(&desktop_path, &desktop_content)?;
|
||||||
|
|
||||||
// Install icon if we have a cached one
|
// Install icon if we have a cached one
|
||||||
@@ -340,6 +350,51 @@ pub fn undo_all_modifications(db: &Database, appimage_id: i64) -> Result<(), Str
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set an AppImage as the default handler for a MIME type.
|
||||||
|
/// Stores the previous default in system_modifications for reversal.
|
||||||
|
pub fn set_mime_default(
|
||||||
|
db: &Database,
|
||||||
|
appimage_id: i64,
|
||||||
|
app_id: &str,
|
||||||
|
mime_type: &str,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let desktop_filename = format!("driftwood-{}.desktop", app_id);
|
||||||
|
|
||||||
|
// Query current default
|
||||||
|
let prev = Command::new("xdg-mime")
|
||||||
|
.args(["query", "default", mime_type])
|
||||||
|
.output()
|
||||||
|
.ok()
|
||||||
|
.and_then(|o| {
|
||||||
|
if o.status.success() {
|
||||||
|
let s = String::from_utf8_lossy(&o.stdout).trim().to_string();
|
||||||
|
if s.is_empty() { None } else { Some(s) }
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set new default
|
||||||
|
let status = Command::new("xdg-mime")
|
||||||
|
.args(["default", &desktop_filename, mime_type])
|
||||||
|
.status()
|
||||||
|
.map_err(|e| format!("xdg-mime failed: {}", e))?;
|
||||||
|
|
||||||
|
if !status.success() {
|
||||||
|
return Err("xdg-mime returned non-zero".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track the modification
|
||||||
|
db.register_modification(
|
||||||
|
appimage_id,
|
||||||
|
"mime_default",
|
||||||
|
mime_type,
|
||||||
|
prev.as_deref(),
|
||||||
|
).ok();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn update_desktop_database() {
|
fn update_desktop_database() {
|
||||||
let apps_dir = applications_dir();
|
let apps_dir = applications_dir();
|
||||||
Command::new("update-desktop-database")
|
Command::new("update-desktop-database")
|
||||||
|
|||||||
@@ -1085,6 +1085,60 @@ fn build_system_tab(record: &AppImageRecord, db: &Rc<Database>, toast_overlay: &
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// File type associations group
|
||||||
|
if let Some(ref mime_str) = record.mime_types {
|
||||||
|
let types: Vec<&str> = mime_str.split(';').filter(|s| !s.is_empty()).collect();
|
||||||
|
if !types.is_empty() {
|
||||||
|
let mime_group = adw::PreferencesGroup::builder()
|
||||||
|
.title("File Type Associations")
|
||||||
|
.description("MIME types this app can handle. Set as default to open these files with this app.")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let app_id = integrator::make_app_id(
|
||||||
|
record.app_name.as_deref().unwrap_or(&record.filename),
|
||||||
|
);
|
||||||
|
|
||||||
|
for mime_type in &types {
|
||||||
|
let row = adw::ActionRow::builder()
|
||||||
|
.title(*mime_type)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let set_btn = gtk::Button::builder()
|
||||||
|
.label("Set Default")
|
||||||
|
.valign(gtk::Align::Center)
|
||||||
|
.build();
|
||||||
|
set_btn.add_css_class("flat");
|
||||||
|
|
||||||
|
let db_mime = db.clone();
|
||||||
|
let record_id = record.id;
|
||||||
|
let app_id_clone = app_id.clone();
|
||||||
|
let mime = mime_type.to_string();
|
||||||
|
let toast_mime = toast_overlay.clone();
|
||||||
|
set_btn.connect_clicked(move |btn| {
|
||||||
|
match integrator::set_mime_default(
|
||||||
|
&db_mime, record_id, &app_id_clone, &mime,
|
||||||
|
) {
|
||||||
|
Ok(()) => {
|
||||||
|
toast_mime.add_toast(adw::Toast::new(
|
||||||
|
&format!("Set as default for {}", mime),
|
||||||
|
));
|
||||||
|
btn.set_sensitive(false);
|
||||||
|
btn.set_label("Default");
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Failed to set MIME default: {}", e);
|
||||||
|
toast_mime.add_toast(adw::Toast::new("Failed to set default"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
row.add_suffix(&set_btn);
|
||||||
|
mime_group.add(&row);
|
||||||
|
}
|
||||||
|
inner.append(&mime_group);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Runtime Compatibility group
|
// Runtime Compatibility group
|
||||||
let compat_group = adw::PreferencesGroup::builder()
|
let compat_group = adw::PreferencesGroup::builder()
|
||||||
.title("Compatibility")
|
.title("Compatibility")
|
||||||
|
|||||||
Reference in New Issue
Block a user