diff --git a/src/app.rs b/src/app.rs index e469bce..5f02aaa 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,7 +1,6 @@ // Copyright 2023 System76 // SPDX-License-Identifier: GPL-3.0-only -use cosmic::iced::mouse::Event::CursorMoved; #[cfg(feature = "wayland")] use cosmic::iced::{ event::wayland::{Event as WaylandEvent, OutputEvent, OverlapNotifyEvent}, @@ -57,6 +56,8 @@ use std::{ sync::{Arc, Mutex}, time::{self, Instant}, }; +use cosmic::iced::mouse::Event::CursorMoved; +use cosmic::widget::{Button, ListColumn}; use tokio::sync::mpsc; use trash::TrashItem; #[cfg(feature = "wayland")] @@ -75,6 +76,7 @@ use crate::{ spawn_detached::spawn_detached, tab::{self, HeadingOptions, ItemMetadata, Location, Tab, HOVER_DURATION}, }; +use crate::mime_app::MimeApp; #[derive(Clone, Debug)] pub enum Mode { @@ -560,6 +562,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 +630,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(()) => { @@ -2187,7 +2215,8 @@ impl Application for App { selected, .. } => { - if let Some(app) = self.mime_app_cache.get(&mime).get(selected) { + let direct_apps = self.mime_app_cache.get(&mime); + if let Some(app) = direct_apps.get(selected) { if let Some(mut command) = app.command(Some(path.clone().into())) { match spawn_detached(&mut command) { Ok(()) => { @@ -2215,6 +2244,46 @@ impl Application for App { ); } } + + let mut sub_class_index = selected - direct_apps.len(); + if let Some(sub_classes) = mime_icon::parent_mime_types(&mime) { + for sub_class in sub_classes { + let sub_class_apps = self.mime_app_cache.get(&sub_class); + if let Some(app) = sub_class_apps.get(sub_class_index) { + if let Some(mut command) = app.command(Some(path.clone().into())) { + match spawn_detached(&mut command) { + Ok(()) => { + let _ = recently_used_xbel::update_recently_used( + &path, + App::APP_ID.to_string(), + "cosmic-files".to_string(), + None, + ); + } + Err(err) => { + log::warn!( + "failed to open {:?} with {:?}: {}", + path, + app.id, + err + ) + } + } + } else { + log::warn!( + "failed to open {:?} with {:?}: failed to get command", + path, + app.id + ); + } + } + else { + sub_class_index -= sub_class_apps.len(); + } + + } + } + } DialogPage::RenameItem { from, parent, name, .. @@ -4229,6 +4298,7 @@ impl Application for App { }; let mut column = widget::list_column(); + let mut open_index = 0; for (i, app) in self.mime_app_cache.get(mime).iter().enumerate() { column = column.add( widget::button::custom( @@ -4260,8 +4330,48 @@ impl Application for App { .class(theme::Button::MenuItem) .on_press(Message::OpenWithSelection(i)), ); + open_index += 1; } + if let Some(sub_classes) = mime_icon::parent_mime_types(&mime) { + for sub_class in sub_classes { + for (i, app) in self.mime_app_cache.get(&sub_class).iter().enumerate() { + column = column.add( + widget::button::custom( + widget::row::with_children(vec![ + widget::icon(app.icon.clone()).size(32).into(), + if app.is_default { + widget::text::body(fl!( + "default-app", + name = Some(app.name.as_str()) + )) + .into() + } else { + widget::text::body(app.name.to_string()).into() + }, + widget::horizontal_space().into(), + if *selected == open_index { + widget::icon::from_name("checkbox-checked-symbolic") + .size(16) + .into() + } else { + widget::Space::with_width(Length::Fixed(16.0)).into() + }, + ]) + .spacing(space_s) + .height(Length::Fixed(32.0)) + .align_y(Alignment::Center), + ) + .width(Length::Fill) + .class(theme::Button::MenuItem) + .on_press(Message::OpenWithSelection(open_index)), + ); + open_index += 1; + } + } + } + + let mut dialog = widget::dialog() .title(fl!("open-with-title", name = name)) .primary_action( diff --git a/src/mime_icon.rs b/src/mime_icon.rs index a50ff8a..9be98b6 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) +} \ No newline at end of file