diff --git a/Cargo.lock b/Cargo.lock
index db0ce410..a8276c38 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1311,7 +1311,6 @@ dependencies = [
"unicode-segmentation",
"uuid",
"xdg",
- "xdg-utils",
]
[[package]]
@@ -2652,9 +2651,3 @@ name = "xdg"
version = "2.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "213b7324336b53d2414b2db8537e56544d981803139155afa84f76eeebb7a546"
-
-[[package]]
-name = "xdg-utils"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "db9fefe62d5969721e2cfc529e6a760901cc0da422b6d67e7bfd18e69490dba6"
diff --git a/meli/src/mail/view/envelope.rs b/meli/src/mail/view/envelope.rs
index 631bcde1..8f397cbd 100644
--- a/meli/src/mail/view/envelope.rs
+++ b/meli/src/mail/view/envelope.rs
@@ -22,7 +22,7 @@
use std::process::{Command, Stdio};
use linkify::LinkFinder;
-use melib::xdg_utils::query_default_app;
+use melib::utils::xdg::query_default_app;
use super::*;
use crate::ThreadEvent;
diff --git a/meli/src/mail/view/html.rs b/meli/src/mail/view/html.rs
index dda48070..3d8c4674 100644
--- a/meli/src/mail/view/html.rs
+++ b/meli/src/mail/view/html.rs
@@ -24,7 +24,7 @@ use std::{
process::{Command, Stdio},
};
-use melib::xdg_utils::query_default_app;
+use melib::utils::xdg::query_default_app;
use super::*;
diff --git a/melib/Cargo.toml b/melib/Cargo.toml
index 5fb23fb6..b9ba6624 100644
--- a/melib/Cargo.toml
+++ b/melib/Cargo.toml
@@ -54,7 +54,6 @@ socket2 = { version = "0.4", features = [] }
unicode-segmentation = { version = "1.2.1", default-features = false, optional = true }
uuid = { version = "^1", features = ["serde", "v4", "v5"] }
xdg = "2.1.0"
-xdg-utils = "^0.4.0"
[dev-dependencies]
mailin-embedded = { version = "0.7", features = ["rtls"] }
diff --git a/melib/src/email/compose.rs b/melib/src/email/compose.rs
index b7e93b7f..00f8ce22 100644
--- a/melib/src/email/compose.rs
+++ b/melib/src/email/compose.rs
@@ -29,7 +29,6 @@ use std::{
};
use data_encoding::BASE64_MIME;
-use xdg_utils::query_mime_info;
use super::*;
use crate::{
@@ -37,7 +36,7 @@ use crate::{
attachment_types::{Charset, ContentTransferEncoding, ContentType, MultipartType},
attachments::AttachmentBuilder,
},
- utils::{datetime, shellexpand::ShellExpandTrait},
+ utils::{datetime, shellexpand::ShellExpandTrait, xdg::query_mime_info},
};
pub mod mime;
diff --git a/melib/src/lib.rs b/melib/src/lib.rs
index 510fd715..42f112bb 100644
--- a/melib/src/lib.rs
+++ b/melib/src/lib.rs
@@ -190,7 +190,6 @@ pub extern crate indexmap;
pub extern crate smallvec;
pub extern crate smol;
pub extern crate uuid;
-pub extern crate xdg_utils;
#[derive(Debug, Copy, Clone)]
#[repr(transparent)]
diff --git a/melib/src/utils/mod.rs b/melib/src/utils/mod.rs
index bb62e10f..970e7f6a 100644
--- a/melib/src/utils/mod.rs
+++ b/melib/src/utils/mod.rs
@@ -32,6 +32,7 @@ pub mod percent_encoding;
pub mod shellexpand;
#[cfg(feature = "sqlite3")]
pub mod sqlite3;
+pub mod xdg;
pub mod html_escape {
//! HTML Coded Character Set
diff --git a/melib/src/utils/xdg/mod.rs b/melib/src/utils/xdg/mod.rs
new file mode 100644
index 00000000..8f4b7ec5
--- /dev/null
+++ b/melib/src/utils/xdg/mod.rs
@@ -0,0 +1,394 @@
+/* xdg-utils library
+ *
+ * Copyright 2019-2020 Manos Pitsidianakis
+ *
+ * xdg-utils is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * xdg-utils is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with xdg-utils. If not, see .
+ */
+
+//! Query system for default apps using XDG MIME databases.
+//!
+//! The xdg-utils library provides dependency-free (except for `std`) Rust
+//! implementations of some common functions in the freedesktop project
+//! `xdg-utils`.
+//!
+//! # What is implemented?
+//! * Function query_default_app performs
+//! like the xdg-utils function `binary_to_desktop_file`
+//! * Function query_mime_info launches the
+//! `mimetype` or else the `file` command.
+//!
+//! Some of the utils may be implemented by combining these functions with other
+//! functions in the Rust standard library.
+//!
+//! | Name | Function | Implemented functionalities|
+//! |-----------------|--------------------------------------------------------|----------------------------|
+//! |`xdg-desktop-menu`| Install desktop menu items | no
+//! |`xdg-desktop-icon`| Install icons to the desktop | no
+//! |`xdg-icon-resource`| Install icon resources | no
+//! |`xdg-mime` | Query information about file type handling and install descriptions for new file types| queries only
+//! |`xdg-open` | Open a file or URL in the user's preferred application | all (combine crate functions with `std::process::Command`)
+//! |`xdg-email` | Send mail using the user's preferred e-mail composer | no
+//! |`xdg-screensaver` | Control the screensaver | no
+//!
+//! # Specification
+//!
+//!
+//! # Reference implementation
+//!
+
+use std::{
+ collections::HashMap,
+ env, fs,
+ fs::File,
+ io::{Error, ErrorKind, Read, Result},
+ path::{Path, PathBuf},
+ process::{Command, Stdio},
+ str,
+};
+
+macro_rules! split_and_chain {
+ ($xdg_vars:ident[$key:literal]) => {
+ $xdg_vars.get($key).map(String::as_str).unwrap_or("").split(':')
+ };
+ ($xdg_vars:ident[$key:literal], $($tail_xdg_vars:ident[$tail_key:literal]),+$(,)*) => {
+
+ split_and_chain!($xdg_vars[$key]).chain(split_and_chain!($($tail_xdg_vars[$tail_key]),+))
+ }
+}
+
+struct Ini(String);
+
+impl Ini {
+ fn from_filename(filename: &Path) -> Result {
+ let mut file: File = File::open(filename)?;
+
+ let mut contents: Vec = vec![];
+ file.read_to_end(&mut contents)?;
+
+ let contents_str =
+ String::from_utf8(contents).map_err(|err| Error::new(ErrorKind::InvalidData, err))?;
+ Ok(Self(contents_str))
+ }
+
+ fn iter_section(&self, section: &str) -> impl Iterator {
+ let section = format!("[{}]", section);
+ let mut lines = self.0.lines();
+
+ // Eat lines until we find the beginning of our section.
+ loop {
+ let line = lines.next();
+ if let Some(line) = line {
+ if line == section {
+ break;
+ }
+ } else {
+ break;
+ }
+ }
+
+ // Then take all foo=bar lines until the next section.
+ lines
+ .filter(|line| !line.starts_with('#'))
+ .take_while(|line| !line.starts_with('['))
+ .filter_map(|line| {
+ let split: Vec<_> = line.splitn(2, '=').collect();
+ if split.len() != 2 {
+ None
+ } else {
+ Some((split[0], split[1]))
+ }
+ })
+ }
+}
+
+/// Returns the command string of the desktop file that is the default
+/// application of given MIME type `query`
+///
+/// # Example
+/// ```no_run
+/// use xdg_utils::query_default_app;
+///
+/// // The crate author recommends firefox.
+/// assert_eq!(
+/// Ok("firefox".into()),
+/// query_default_app("text/html").map_err(|_| ())
+/// );
+/// ```
+pub fn query_default_app>(query: T) -> Result {
+ // Values are directory paths separated by : in case it's more than one.
+ let mut xdg_vars: HashMap = HashMap::new();
+ let env_vars: env::Vars = env::vars();
+
+ for (k, v) in env_vars {
+ if k.starts_with("XDG_CONFIG")
+ || k.starts_with("XDG_DATA")
+ || k.starts_with("XDG_CURRENT_DESKTOP")
+ || k == "HOME"
+ {
+ xdg_vars.insert(k.to_string(), v.to_string());
+ }
+ }
+
+ // Insert defaults if variables are missing
+ if xdg_vars.contains_key("HOME") && !xdg_vars.contains_key("XDG_DATA_HOME") {
+ let h = xdg_vars["HOME"].clone();
+ xdg_vars.insert("XDG_DATA_HOME".to_string(), format!("{}/.local/share", h));
+ }
+
+ if xdg_vars.contains_key("HOME") && !xdg_vars.contains_key("XDG_CONFIG_HOME") {
+ let h = xdg_vars["HOME"].clone();
+ xdg_vars.insert("XDG_CONFIG_HOME".to_string(), format!("{}/.config", h));
+ }
+
+ if !xdg_vars.contains_key("XDG_DATA_DIRS") {
+ xdg_vars.insert(
+ "XDG_DATA_DIRS".to_string(),
+ "/usr/local/share:/usr/share".to_string(),
+ );
+ }
+
+ if !xdg_vars.contains_key("XDG_CONFIG_DIRS") {
+ xdg_vars.insert("XDG_CONFIG_DIRS".to_string(), "/etc/xdg".to_string());
+ }
+
+ let desktops: Option> = if xdg_vars.contains_key("XDG_CURRENT_DESKTOP") {
+ let list = xdg_vars["XDG_CURRENT_DESKTOP"]
+ .trim()
+ .split(':')
+ .map(str::to_ascii_lowercase)
+ .collect();
+ Some(list)
+ } else {
+ None
+ };
+
+ // Search for mime entry in files.
+ for p in split_and_chain!(
+ xdg_vars["XDG_CONFIG_HOME"],
+ xdg_vars["XDG_CONFIG_DIRS"],
+ xdg_vars["XDG_DATA_HOME"],
+ xdg_vars["XDG_DATA_DIRS"],
+ ) {
+ if let Some(ref d) = desktops {
+ for desktop in d {
+ let pb: PathBuf = PathBuf::from(format!(
+ "{var_value}/{desktop_val}-mimeapps.list",
+ var_value = p,
+ desktop_val = desktop
+ ));
+ if pb.exists() {
+ if let Some(ret) = check_mimeapps_list(&pb, &xdg_vars, &query)? {
+ return Ok(ret);
+ }
+ }
+ }
+ }
+ let pb: PathBuf = PathBuf::from(format!("{var_value}/mimeapps.list", var_value = p));
+ if pb.exists() {
+ if let Some(ret) = check_mimeapps_list(&pb, &xdg_vars, &query)? {
+ return Ok(ret);
+ }
+ }
+ }
+
+ // Search again but for different paths.
+ for p in split_and_chain!(xdg_vars["XDG_DATA_HOME"], xdg_vars["XDG_DATA_DIRS"]) {
+ if let Some(ref d) = desktops {
+ for desktop in d {
+ let pb: PathBuf = PathBuf::from(format!(
+ "{var_value}/applications/{desktop_val}-mimeapps.list",
+ var_value = p,
+ desktop_val = desktop
+ ));
+ if pb.exists() {
+ if let Some(ret) = check_mimeapps_list(&pb, &xdg_vars, &query)? {
+ return Ok(ret);
+ }
+ }
+ }
+ }
+ let pb: PathBuf = PathBuf::from(format!(
+ "{var_value}/applications/mimeapps.list",
+ var_value = p
+ ));
+ if pb.exists() {
+ if let Some(ret) = check_mimeapps_list(&pb, &xdg_vars, &query)? {
+ return Ok(ret);
+ }
+ }
+ }
+
+ Err(Error::new(
+ ErrorKind::NotFound,
+ format!("No results for mime query: {}", query.as_ref()),
+ ))
+}
+
+fn check_mimeapps_list>(
+ filename: &Path,
+ xdg_vars: &HashMap,
+ query: T,
+) -> Result