diff --git a/Cargo.lock b/Cargo.lock index b36c014..4938df5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7546,8 +7546,7 @@ dependencies = [ [[package]] name = "xdg-mime" version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58d325d0ca93fb1984a56eb926f019acfc67bd2ec559b0dbf09cafcc92e81ec9" +source = "git+https://github.com/ellieplayswow/xdg-mime-rs?branch=feature/get-same-as#4f8d07ceedabbe58368a8e7f5547232490860790" dependencies = [ "dirs-next", "glob", diff --git a/Cargo.toml b/Cargo.toml index 7392f56..d9765ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,7 +41,7 @@ url = "2.5" walkdir = "2.5.0" wayland-client = { version = "0.31.8", optional = true } xdg = { version = "2.5.2", optional = true } -xdg-mime = "0.4" +xdg-mime = "0.4.0" # Compression bzip2 = { version = "0.5", optional = true } #TODO: replace with pure Rust crate flate2 = "1.0" @@ -96,6 +96,8 @@ tokio = { version = "1", features = ["rt", "macros"] } [patch.crates-io] # https://github.com/alexcrichton/filetime/pull/104 filetime = { git = "https://github.com/jackpot51/filetime" } +# https://github.com/ebassi/xdg-mime-rs/pull/31 +xdg-mime = { git = "https://github.com/ellieplayswow/xdg-mime-rs", branch = "feature/get-same-as" } # [patch.'https://github.com/pop-os/cosmic-text'] # cosmic-text = { path = "../cosmic-text" } diff --git a/src/app.rs b/src/app.rs index e469bce..7fd1792 100644 --- a/src/app.rs +++ b/src/app.rs @@ -15,6 +15,7 @@ use cosmic::iced::{ }; #[cfg(feature = "wayland")] use cosmic::iced_winit::commands::overlap_notify::overlap_notify; +use cosmic::widget::{Button, ListColumn}; use cosmic::{ app::{self, context_drawer, message, Core, Task}, cosmic_config, cosmic_theme, executor, @@ -41,6 +42,7 @@ use cosmic::{ }, Application, ApplicationExt, Element, }; +use mime_guess::Mime; use notify_debouncer_full::{ new_debouncer, notify::{self, RecommendedWatcher, Watcher}, @@ -62,6 +64,7 @@ use trash::TrashItem; #[cfg(feature = "wayland")] use wayland_client::{protocol::wl_output::WlOutput, Proxy}; +use crate::mime_app::MimeApp; use crate::operation::{OperationError, OperationErrorType}; use crate::{ clipboard::{ClipboardCopy, ClipboardKind, ClipboardPaste}, @@ -560,6 +563,7 @@ pub struct App { impl App { fn open_file(&mut self, path: &PathBuf) { let mime = mime_icon::mime_for_path(path); + if mime == "application/x-desktop" { // Try opening desktop application match freedesktop_entry_parser::parse_entry(path) { @@ -627,6 +631,31 @@ impl App { } } + // loop through subclasses if available + if let Some(mime_sub_classes) = mime_icon::parent_mime_types(&mime) { + for sub_class in mime_sub_classes { + for app in self.mime_app_cache.get(&sub_class) { + let Some(mut command) = app.command(Some(path.clone().into())) else { + continue; + }; + match spawn_detached(&mut command) { + Ok(()) => { + let _ = recently_used_xbel::update_recently_used( + path, + App::APP_ID.to_string(), + "cosmic-files".to_string(), + None, + ); + return; + } + Err(err) => { + log::warn!("failed to open {:?} with {:?}: {}", path, app.id, err); + } + } + } + } + } + // Fall back to using open crate match open::that_detached(path) { Ok(()) => { @@ -1567,6 +1596,36 @@ impl App { .into() } + fn get_programs_for_mime(&self, mime_type: &Mime) -> Vec<&MimeApp> { + let mut results = Vec::new(); + + let mut dedupe = HashSet::new(); + + // start with exact matches + for mime_app in self.mime_app_cache.get(mime_type) { + let app_id = &mime_app.id; + if !dedupe.contains(app_id) { + results.push(mime_app); + dedupe.insert(app_id); + } + } + + // grab matches based off of subclass / parent mime type + if let Some(parent_types) = mime_icon::parent_mime_types(mime_type) { + for parent_type in parent_types { + for mime_app in self.mime_app_cache.get(&parent_type) { + let app_id = &mime_app.id; + if !dedupe.contains(app_id) { + results.push(mime_app); + dedupe.insert(app_id); + } + } + } + } + + results + } + // Update favorites based on renaming or moving dirs. fn update_favorites(&mut self, path_changes: &[(PathBuf, PathBuf)]) -> bool { let mut favorites_changed = false; @@ -2187,7 +2246,9 @@ impl Application for App { selected, .. } => { - if let Some(app) = self.mime_app_cache.get(&mime).get(selected) { + let all_apps = self.get_programs_for_mime(&mime); + + if let Some(app) = all_apps.get(selected) { if let Some(mut command) = app.command(Some(path.clone().into())) { match spawn_detached(&mut command) { Ok(()) => { @@ -4229,7 +4290,7 @@ impl Application for App { }; let mut column = widget::list_column(); - for (i, app) in self.mime_app_cache.get(mime).iter().enumerate() { + for (i, app) in self.get_programs_for_mime(&mime).iter().enumerate() { column = column.add( widget::button::custom( widget::row::with_children(vec![ diff --git a/src/mime_icon.rs b/src/mime_icon.rs index a50ff8a..c776dc8 100644 --- a/src/mime_icon.rs +++ b/src/mime_icon.rs @@ -73,3 +73,9 @@ pub fn mime_icon(mime: Mime, size: u16) -> icon::Handle { None => icon::from_name(FALLBACK_MIME_ICON).size(size).handle(), } } + +pub fn parent_mime_types(mime: &Mime) -> Option> { + let mut mime_icon_cache = MIME_ICON_CACHE.lock().unwrap(); + + mime_icon_cache.shared_mime_info.get_parents_aliased(mime) +}