Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "freedesktop-desktop-entry"
version = "0.5.4"
version = "0.6.0"
authors = ["Michael Aaron Murphy <[email protected]>"]
edition = "2021"
homepage = "https://github.com/pop-os/freedesktop-desktop-entry"
Expand All @@ -18,3 +18,9 @@ textdistance = "1.0.2"
strsim = "0.11.1"
thiserror = "1"
xdg = "2.4.0"
udev = "0.8.0"
zbus = "4.2.2"
log = "0.4.21"

[dev-dependencies]
speculoos = "0.11.0"
13 changes: 13 additions & 0 deletions examples/de_launch.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
use freedesktop_desktop_entry::{get_languages_from_env, DesktopEntry};
use std::env;
use std::path::PathBuf;

fn main() {
let args: Vec<String> = env::args().collect();
let path = &args.get(1).expect("Not enough arguments");
let path = PathBuf::from(path);
let locales = get_languages_from_env();
let de = DesktopEntry::from_path(path, &locales).expect("Error decoding desktop entry");
de.launch_with_uris(&[], false, &locales)
.expect("Failed to run desktop entry");
}
10 changes: 5 additions & 5 deletions src/decoder.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// Copyright 2021 System76 <[email protected]>
// SPDX-License-Identifier: MPL-2.0

use std::{
borrow::Cow,
collections::BTreeMap,
Expand Down Expand Up @@ -68,10 +71,7 @@ impl<'a> DesktopEntry<'a> {
}

/// Return an owned [`DesktopEntry`]
pub fn from_path<L>(
path: PathBuf,
locales: &[L],
) -> Result<DesktopEntry<'static>, DecodeError>
pub fn from_path<L>(path: PathBuf, locales: &[L]) -> Result<DesktopEntry<'static>, DecodeError>
where
L: AsRef<str>,
{
Expand Down Expand Up @@ -194,7 +194,7 @@ where
}

/// Ex: if a locale equal fr_FR, add fr
fn add_generic_locales<'a, L: AsRef<str>>(locales: &'a [L]) -> Vec<&'a str> {
fn add_generic_locales<L: AsRef<str>>(locales: &[L]) -> Vec<&str> {
let mut v = Vec::with_capacity(locales.len() + 1);

for l in locales {
Expand Down
121 changes: 121 additions & 0 deletions src/exec/dbus.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// Copyright 2021 System76 <[email protected]>
// SPDX-License-Identifier: MPL-2.0

use crate::exec::error::ExecError;
use crate::exec::graphics::Gpus;
use crate::DesktopEntry;
use std::collections::HashMap;
use zbus::blocking::Connection;
use zbus::names::OwnedBusName;
use zbus::proxy;
use zbus::zvariant::{OwnedValue, Str};

// https://specifications.freedesktop.org/desktop-entry-spec/1.1/ar01s07.html
#[proxy(interface = "org.freedesktop.Application")]
trait Application {
fn activate(&self, platform_data: HashMap<String, OwnedValue>) -> zbus::Result<()>;

fn open(&self, uris: &[&str], platform_data: HashMap<String, OwnedValue>) -> zbus::Result<()>;

// XXX: https://gitlab.freedesktop.org/xdg/xdg-specs/-/issues/134
fn activate_action(
&self,
action_name: &str,
parameters: &[&str],
platform_data: HashMap<String, OwnedValue>,
) -> zbus::Result<()>;
}

impl DesktopEntry<'_> {
pub(crate) fn should_launch_on_dbus(&self) -> Option<Connection> {
match self.desktop_entry_bool("DBusActivatable") {
true => match Connection::session() {
Ok(conn) => {
if self.is_bus_actionable(&conn) {
Some(conn)
} else {
None
}
}
Err(e) => {
log::error!("can't open dbus session: {}", e);
None
}
},
false => None,
}
}

fn is_bus_actionable(&self, conn: &Connection) -> bool {
let dbus_proxy = zbus::blocking::fdo::DBusProxy::new(conn);

if dbus_proxy.is_err() {
return false;
}

let dbus_proxy = dbus_proxy.unwrap();
let dbus_names = dbus_proxy.list_activatable_names();

if dbus_names.is_err() {
return false;
}

let dbus_names = dbus_names.unwrap();

dbus_names
.into_iter()
.map(OwnedBusName::into_inner)
.any(|name| name.as_str() == self.appid)
}

pub(crate) fn dbus_launch(&self, conn: &Connection, uris: &[&str]) -> Result<(), ExecError> {
let app_proxy = self.get_app_proxy(conn)?;
let platform_data = self.get_platform_data();

if !uris.is_empty() {
app_proxy.open(uris, platform_data)?;
} else {
app_proxy.activate(platform_data)?;
}

Ok(())
}

pub(crate) fn dbus_launch_action(
&self,
conn: &Connection,
action_name: &str,
uris: &[&str],
) -> Result<(), ExecError> {
let app_proxy = self.get_app_proxy(conn)?;
let platform_data = self.get_platform_data();
app_proxy.activate_action(action_name, uris, platform_data)?;

Ok(())
}

fn get_app_proxy(&self, conn: &Connection) -> Result<ApplicationProxyBlocking, ExecError> {
let dbus_path = self.appid.replace('.', "/").replace('-', "_");
let dbus_path = format!("/{dbus_path}");
let app_proxy = ApplicationProxyBlocking::builder(conn)
.destination(self.appid.as_ref())?
.path(dbus_path)?
.build()?;
Ok(app_proxy)
}

// todo: XDG_ACTIVATION_TOKEN and DESKTOP_STARTUP_ID ?
// https://github.com/pop-os/libcosmic/blob/master/src/app/mod.rs
fn get_platform_data(&self) -> HashMap<String, OwnedValue> {
let mut platform_data = HashMap::new();
if self.prefers_non_default_gpu() {
let gpus = Gpus::load();
if let Some(gpu) = gpus.non_default() {
for (opt, value) in gpu.launch_options() {
platform_data.insert(opt, OwnedValue::from(Str::from(value.as_str())));
}
}
}
platform_data
}
}
49 changes: 49 additions & 0 deletions src/exec/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright 2021 System76 <[email protected]>
// SPDX-License-Identifier: MPL-2.0

use std::env::VarError;
use std::io;
use std::path::Path;
use thiserror::Error;

#[derive(Debug, Error)]
pub enum ExecError<'a> {
#[error("{0}")]
WrongFormat(String),

#[error("Exec string is empty")]
EmptyExecString,

#[error("$SHELL environment variable is not set")]
ShellNotFound(#[from] VarError),

#[error("Failed to run Exec command")]
IoError(#[from] io::Error),

#[error("Exec command '{exec}' exited with status code '{status:?}'")]
NonZeroStatusCode { status: Option<i32>, exec: String },

#[error("Unknown field code: '{0}'")]
UnknownFieldCode(String),

#[error("Deprecated field code: '{0}'")]
DeprecatedFieldCode(String),

#[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),
}
Loading