diff --git a/src/exec/dbus.rs b/src/exec/dbus.rs index 0230a4d..5c633ff 100644 --- a/src/exec/dbus.rs +++ b/src/exec/dbus.rs @@ -23,7 +23,12 @@ trait Application { } impl DesktopEntry<'_> { - pub(crate) fn dbus_launch(&self, conn: &Connection, uris: &[&str]) -> Result<(), ExecError> { + pub(crate) fn dbus_launch( + &self, + conn: &Connection, + uris: &[&str], + action: Option, + ) -> Result<(), ExecError> { let dbus_path = self.appid.replace('.', "/"); let dbus_path = format!("/{dbus_path}"); let app_proxy = ApplicationProxyBlocking::builder(conn) @@ -41,10 +46,21 @@ impl DesktopEntry<'_> { } } - if !uris.is_empty() { - app_proxy.open(uris, platform_data)?; - } else { - app_proxy.activate(platform_data)?; + match action { + None => { + if !uris.is_empty() { + app_proxy.open(uris, platform_data)?; + } else { + app_proxy.activate(platform_data)?; + } + } + Some(action) => { + let parameters: Vec = uris + .iter() + .map(|uri| OwnedValue::from(Str::from(*uri))) + .collect(); + app_proxy.activate_action(&action, parameters.as_slice(), platform_data)? + } } Ok(()) diff --git a/src/exec/error.rs b/src/exec/error.rs index dcac984..ab5c98d 100644 --- a/src/exec/error.rs +++ b/src/exec/error.rs @@ -29,6 +29,18 @@ pub enum ExecError<'a> { #[error("Exec key not found in desktop entry '{0:?}'")] MissingExecKey(&'a Path), + #[error("Action '{action}' not found for desktop entry '{desktop_entry:?}'")] + ActionNotFound { + action: String, + desktop_entry: &'a Path, + }, + + #[error("Exec key not found for action :'{action}' in desktop entry '{desktop_entry:?}'")] + ActionExecKeyNotFound { + action: String, + desktop_entry: &'a Path, + }, + #[error("Failed to launch aplication via dbus: {0}")] DBusError(#[from] zbus::Error), } diff --git a/src/exec/mod.rs b/src/exec/mod.rs index f9aef2c..c5af65b 100644 --- a/src/exec/mod.rs +++ b/src/exec/mod.rs @@ -16,28 +16,69 @@ pub mod error; mod graphics; impl DesktopEntry<'_> { + /// Launch the given desktop entry action either via dbus or via its `Exec` key with the default gpu or + /// the alternative one if available. + pub fn launch_action(&self, action: &str, uris: &[&str]) -> Result<(), ExecError> { + let has_action = self + .actions() + .map(|actions| actions.split(';').any(|act| act == action)) + .unwrap_or(false); + + if !has_action { + return Err(ExecError::ActionNotFound { + action: action.to_string(), + desktop_entry: self.path, + }); + } + + match Connection::session() { + Ok(conn) => { + if self.is_bus_actionable(&conn) { + self.dbus_launch(&conn, uris, Some(action.to_string())) + } else { + self.shell_launch(uris, Some(action.to_string())) + } + } + Err(_) => self.shell_launch(uris, Some(action.to_string())), + } + } + /// Launch the given desktop entry either via dbus or via its `Exec` key with the default gpu or /// the alternative one if available. pub fn launch(&self, uris: &[&str]) -> Result<(), ExecError> { match Connection::session() { Ok(conn) => { if self.is_bus_actionable(&conn) { - self.dbus_launch(&conn, uris) + self.dbus_launch(&conn, uris, None) } else { - self.shell_launch(uris) + self.shell_launch(uris, None) } } - Err(_) => self.shell_launch(uris), + Err(_) => self.shell_launch(uris, None), } } - fn shell_launch(&self, uris: &[&str]) -> Result<(), ExecError> { - let exec = self.exec(); - if exec.is_none() { - return Err(ExecError::MissingExecKey(self.path)); - } + fn shell_launch(&self, uris: &[&str], action: Option) -> Result<(), ExecError> { + let exec = match action { + None => { + let exec = self.exec(); + if exec.is_none() { + return Err(ExecError::MissingExecKey(self.path)); + } + exec.unwrap() + } + Some(action) => { + let exec = self.action_exec(&action); + if exec.is_none() { + return Err(ExecError::ActionExecKeyNotFound { + action, + desktop_entry: self.path, + }); + } - let exec = exec.unwrap(); + exec.unwrap() + } + }; let mut exec_args = vec![]; @@ -281,12 +322,34 @@ mod test { let de = DesktopEntry::decode(path.as_path(), &input).unwrap(); let path = std::env::current_dir().unwrap(); let path = path.to_string_lossy(); - let path = format!("file:///{path}"); + let path = format!("file://{path}"); let result = de.launch(&[path.as_str()]); assert_that!(result).is_ok(); } + #[test] + #[ignore = "Needs a desktop environment with alacritty installed, run locally only"] + fn should_launch_action() { + let path = PathBuf::from("/usr/share/applications/Alacritty.desktop"); + let input = fs::read_to_string(&path).unwrap(); + let de = DesktopEntry::decode(path.as_path(), &input).unwrap(); + let result = de.launch_action("New", &[]); + + assert_that!(result).is_ok(); + } + + #[test] + #[ignore = "Needs a desktop environment with Nautilus installed, run locally only"] + fn should_launch_action_via_dbus() { + let path = PathBuf::from("/usr/share/applications/org.gnome.Nautilus.desktop"); + let input = fs::read_to_string(&path).unwrap(); + let de = DesktopEntry::decode(path.as_path(), &input).unwrap(); + let result = de.launch_action("new-window", &[]); + + assert_that!(result).is_ok(); + } + #[test] fn should_build_command_with_gpu() { let cmd = with_non_default_gpu(Command::new("glxgears")); diff --git a/tests/entries/empty-exec.desktop b/tests/entries/empty-exec.desktop index e6bfa79..fb28c29 100644 --- a/tests/entries/empty-exec.desktop +++ b/tests/entries/empty-exec.desktop @@ -2,4 +2,4 @@ Exec= Terminal=false Type=Application -Name=Alacritty +Name=NoExecKey diff --git a/tests/entries/non-terminal-cmd.desktop b/tests/entries/non-terminal-cmd.desktop index b8dc18b..3b84b75 100644 --- a/tests/entries/non-terminal-cmd.desktop +++ b/tests/entries/non-terminal-cmd.desktop @@ -2,4 +2,4 @@ Exec=alacritty -e glxgears -info Terminal=false Type=Application -Name=Alacritty \ No newline at end of file +Name=GlxGearNoTerminal \ No newline at end of file diff --git a/tests/entries/terminal-cmd.desktop b/tests/entries/terminal-cmd.desktop index cf26df6..70cf76a 100644 --- a/tests/entries/terminal-cmd.desktop +++ b/tests/entries/terminal-cmd.desktop @@ -2,4 +2,4 @@ Exec=glxgears -info Terminal=true Type=Application -Name=Alacritty \ No newline at end of file +Name=GlxGearTerminal \ No newline at end of file diff --git a/tests/entries/unmatched-quotes.desktop b/tests/entries/unmatched-quotes.desktop index 98bda6c..6f8f6ef 100644 --- a/tests/entries/unmatched-quotes.desktop +++ b/tests/entries/unmatched-quotes.desktop @@ -2,4 +2,4 @@ Exec="alacritty -e Terminal=false Type=Application -Name=Alacritty \ No newline at end of file +Name=InvalidCommand \ No newline at end of file