use std::{
collections::HashSet,
fs::{create_dir_all, File, OpenOptions},
io::{BufRead, BufReader, Write},
os::unix::fs::PermissionsExt,
path::{Path, PathBuf},
};
use log::{debug, warn};
use crate::{Error, TrashContext, TrashItem};
#[derive(Clone, Default, Debug)]
pub struct PlatformTrashContext;
impl PlatformTrashContext {
pub const fn new() -> Self {
PlatformTrashContext
}
}
impl TrashContext {
pub(crate) fn delete_all_canonicalized(&self, full_paths: Vec<PathBuf>) -> Result<(), Error> {
let home_trash = home_trash()?;
let mount_points = get_mount_points()?;
let home_topdir = home_topdir(&mount_points)?;
debug!("The home topdir is {:?}", home_topdir);
let uid = unsafe { libc::getuid() };
for path in full_paths {
debug!("Deleting {:?}", path);
let topdir = get_topdir_for_path(&path, &mount_points);
debug!("The topdir of this file is {:?}", topdir);
if topdir == home_topdir {
debug!("The topdir was identical to the home topdir, so moving to the home trash.");
move_to_trash(path, &home_trash, topdir)?;
} else {
execute_on_mounted_trash_folders(uid, topdir, true, true, |trash_path| {
move_to_trash(&path, trash_path, topdir)
})?;
}
}
Ok(())
}
}
pub fn list() -> Result<Vec<TrashItem>, Error> {
let mut trash_folders = HashSet::new();
let home_error;
match home_trash() {
Ok(home_trash) => {
if !home_trash.is_dir() {
home_error = Some(Error::Unknown {
description:
"The 'home trash' either does not exist or is not a directory (or a link pointing to a dir)"
.into(),
});
} else {
trash_folders.insert(home_trash);
home_error = None;
}
}
Err(e) => {
home_error = Some(e);
}
}
let uid = unsafe { libc::getuid() };
let mount_points = get_mount_points()?;
for mount in &mount_points {
execute_on_mounted_trash_folders(uid, &mount.mnt_dir, false, false, |trash_path| {
trash_folders.insert(trash_path);
Ok(())
})?;
}
if trash_folders.is_empty() {
warn!("No trash folder was found. The error when looking for the 'home trash' was: {:?}", home_error);
return Ok(vec![]);
}
let mut result = Vec::new();
for folder in trash_folders.iter() {
let top_dir = get_topdir_for_path(folder, &mount_points);
let info_folder = folder.join("info");
if !info_folder.is_dir() {
warn!("The path {:?} did not point to a directory, skipping this trash folder.", info_folder);
continue;
}
let read_dir = match std::fs::read_dir(&info_folder) {
Ok(d) => d,
Err(e) => {
warn!("The trash info folder {:?} could not be read. Error was {:?}", info_folder, e);
continue;
}
};
#[cfg_attr(not(feature = "chrono"), allow(unused_labels))]
'trash_item: for entry in read_dir {
let info_entry = match entry {
Ok(entry) => entry,
Err(e) => {
debug!("Tried resolving the trash info `DirEntry` but it failed with: '{}'", e);
continue;
}
};
let file_type = match info_entry.file_type() {
Ok(f_type) => f_type,
Err(e) => {
debug!("Tried getting the file type of the trash info `DirEntry` but failed with: {}", e);
continue;
}
};
let info_path = info_entry.path();
if !file_type.is_file() {
warn!("Found an item that's not a file, among the trash info files. This is unexpected. The path to the item is: '{:?}'", info_path);
continue;
}
let info_file = match File::open(&info_path) {
Ok(file) => file,
Err(e) => {
debug!("Tried opening the trash info '{:?}' but failed with: {}", info_path, e);
continue;
}
};
let id = info_path.clone().into();
let mut name = None;
let mut original_parent: Option<PathBuf> = None;
#[cfg_attr(not(feature = "chrono"), allow(unused_mut))]
let mut time_deleted = None;
let info_reader = BufReader::new(info_file);
'info_lines: for line_result in info_reader.lines().skip(1) {
let line = if let Ok(line) = line_result {
line
} else {
break 'info_lines;
};
let mut split = line.split('=');
let key = split.next().unwrap().trim();
let value = split.next().unwrap().trim();
if key == "Path" {
let mut value_path = Path::new(value).to_owned();
if value_path.is_relative() {
value_path = top_dir.join(value_path);
}
let full_path_utf8 = PathBuf::from(parse_uri_path(&value_path));
name = Some(full_path_utf8.file_name().unwrap().to_str().unwrap().to_owned());
let parent = full_path_utf8.parent().unwrap();
original_parent = Some(parent.into());
} else if key == "DeletionDate" {
#[cfg(feature = "chrono")]
{
use chrono::{NaiveDateTime, TimeZone};
let parsed_time = NaiveDateTime::parse_from_str(value, "%Y-%m-%dT%H:%M:%S");
let naive_local = match parsed_time {
Ok(t) => t,
Err(e) => {
log::error!("Failed to parse the deletion date of the trash item {:?}. The deletion date was '{}'. Parse error was: {:?}", name, value, e);
continue 'trash_item;
}
};
let time = chrono::Local.from_local_datetime(&naive_local).earliest();
match time {
Some(time) => time_deleted = Some(time.timestamp()),
None => {
log::error!(
"Failed to convert the local time to a UTC time. Local time was {:?}",
naive_local
);
continue 'trash_item;
}
}
}
}
}
if let Some(name) = name {
if let Some(original_parent) = original_parent {
if time_deleted.is_none() {
warn!("Could not determine the deletion time of the trash item. (The `DeletionDate` field is probably missing from the info file.) The info file path is: '{:?}'", info_path);
}
result.push(TrashItem { id, name, original_parent, time_deleted: time_deleted.unwrap_or(-1) });
} else {
warn!("Could not determine the original parent folder of the trash item. (The `Path` field is probably missing from the info file.) The info file path is: '{:?}'", info_path);
}
} else {
warn!("Could not determine the name of the trash item. (The `Path` field is probably missing from the info file.) The info file path is: '{:?}'", info_path);
}
}
}
Ok(result)
}
pub fn purge_all<I>(items: I) -> Result<(), Error>
where
I: IntoIterator<Item = TrashItem>,
{
for item in items.into_iter() {
let info_file = &item.id;
let file = restorable_file_in_trash_from_info_file(info_file);
assert!(file.exists());
if file.is_dir() {
std::fs::remove_dir_all(&file).map_err(|e| fsys_err_to_unknown(&file, e))?;
} else {
std::fs::remove_file(&file).map_err(|e| fsys_err_to_unknown(&file, e))?;
}
std::fs::remove_file(info_file).map_err(|e| fsys_err_to_unknown(info_file, e))?;
}
Ok(())
}
fn restorable_file_in_trash_from_info_file(info_file: impl AsRef<std::ffi::OsStr>) -> PathBuf {
let info_file = info_file.as_ref();
let trash_folder = Path::new(info_file).parent().unwrap().parent().unwrap();
let name_in_trash = Path::new(info_file).file_stem().unwrap();
trash_folder.join("files").join(name_in_trash)
}
pub fn restore_all<I>(items: I) -> Result<(), Error>
where
I: IntoIterator<Item = TrashItem>,
{
let mut iter = items.into_iter();
while let Some(item) = iter.next() {
let info_file = &item.id;
let file = restorable_file_in_trash_from_info_file(info_file);
assert!(file.exists());
let original_path = item.original_path();
create_dir_all(&item.original_parent).map_err(|e| fsys_err_to_unknown(&item.original_parent, e))?;
let mut collision = false;
if file.is_dir() {
if let Err(e) = std::fs::create_dir(&original_path) {
if e.kind() == std::io::ErrorKind::AlreadyExists {
collision = true;
} else {
return Err(fsys_err_to_unknown(&original_path, e));
}
}
} else {
if let Err(e) = OpenOptions::new().create_new(true).write(true).open(&original_path) {
if e.kind() == std::io::ErrorKind::AlreadyExists {
collision = true;
} else {
return Err(fsys_err_to_unknown(&original_path, e));
}
}
}
if collision {
let remaining: Vec<_> = std::iter::once(item).chain(iter).collect();
return Err(Error::RestoreCollision { path: original_path, remaining_items: remaining });
}
std::fs::rename(&file, &original_path).map_err(|e| fsys_err_to_unknown(&file, e))?;
std::fs::remove_file(info_file).map_err(|e| fsys_err_to_unknown(info_file, e))?;
}
Ok(())
}
fn execute_on_mounted_trash_folders<F: FnMut(PathBuf) -> Result<(), Error>>(
uid: u32,
topdir: impl AsRef<Path>,
first_only: bool,
create_folder: bool,
mut op: F,
) -> Result<(), Error> {
let topdir = topdir.as_ref();
let trash_path = topdir.join(".Trash");
if trash_path.is_dir() {
let validity = folder_validity(&trash_path)?;
if validity == TrashValidity::Valid {
let users_trash_path = trash_path.join(uid.to_string());
if users_trash_path.exists() && users_trash_path.is_dir() {
op(users_trash_path)?;
if first_only {
return Ok(());
}
}
} else {
warn!("A Trash folder was found at '{:?}', but it's invalid because it's {:?}", trash_path, validity);
}
}
let trash_path = topdir.join(format!(".Trash-{uid}"));
let should_execute;
if !trash_path.exists() || !trash_path.is_dir() {
if create_folder {
std::fs::create_dir(&trash_path).map_err(|e| fsys_err_to_unknown(&trash_path, e))?;
should_execute = true;
} else {
should_execute = false;
}
} else {
should_execute = true;
}
if should_execute {
op(trash_path)?;
}
Ok(())
}
fn move_to_trash(
src: impl AsRef<Path>,
trash_folder: impl AsRef<Path>,
_topdir: impl AsRef<Path>,
) -> Result<(), Error> {
let src = src.as_ref();
let trash_folder = trash_folder.as_ref();
let files_folder = trash_folder.join("files");
let info_folder = trash_folder.join("info");
create_dir_all(&files_folder).map_err(|e| fsys_err_to_unknown(&files_folder, e))?;
create_dir_all(&info_folder).map_err(|e| fsys_err_to_unknown(&info_folder, e))?;
let filename = src.file_name().unwrap();
let mut appendage = 0;
loop {
use std::io;
appendage += 1;
let in_trash_name = if appendage > 1 {
format!("{}.{}", filename.to_str().unwrap(), appendage)
} else {
filename.to_str().unwrap().into()
};
let info_name = format!("{in_trash_name}.trashinfo");
let info_file_path = info_folder.join(&info_name);
let info_result = OpenOptions::new().create_new(true).write(true).open(&info_file_path);
match info_result {
Err(error) => {
if error.kind() == io::ErrorKind::AlreadyExists {
continue;
} else {
debug!("Failed to create the new file {:?}", info_file_path);
return Err(fsys_err_to_unknown(info_file_path, error));
}
}
Ok(mut file) => {
debug!("Successfully created {:?}", info_file_path);
writeln!(file, "[Trash Info]")
.and_then(|_| {
let absolute_uri = encode_uri_path(src);
writeln!(file, "Path={absolute_uri}").and_then(|_| {
#[cfg(feature = "chrono")]
{
let now = chrono::Local::now();
writeln!(file, "DeletionDate={}", now.format("%Y-%m-%dT%H:%M:%S"))
}
#[cfg(not(feature = "chrono"))]
{
Ok(())
}
})
})
.map_err(|e| fsys_err_to_unknown(&info_file_path, e))?;
}
}
let path = files_folder.join(&in_trash_name);
match move_items_no_replace(src, &path) {
Err(error) => {
debug!("Failed moving item to the trash (this is usually OK). {:?}", error);
if let Err(info_err) = std::fs::remove_file(info_file_path) {
warn!("Created the trash info file, then failed to move the item to the trash. So far it's OK, but then failed remove the initial info file. There's either a bug in this program or another faulty program is manupulating the Trash. The error was: {:?}", info_err);
}
if error.kind() == io::ErrorKind::AlreadyExists {
continue;
} else {
return Err(fsys_err_to_unknown(path, error));
}
}
Ok(_) => {
break;
}
}
}
Ok(())
}
fn execute_src_to_dst_operation<S1, D1>(
src: S1,
dst: D1,
dir: &'static dyn Fn(&Path) -> Result<(), std::io::Error>,
file: &'static dyn Fn(&Path, &Path) -> Result<(), std::io::Error>,
) -> Result<(), std::io::Error>
where
S1: AsRef<Path>,
D1: AsRef<Path>,
{
let src = src.as_ref();
let dst = dst.as_ref();
let metadata = src.symlink_metadata()?;
if metadata.is_dir() {
dir(dst)?;
let dir_entries = std::fs::read_dir(src)?;
for entry in dir_entries {
let entry = entry?;
let entry_src = entry.path();
let entry_dst = dst.join(entry.file_name());
execute_src_to_dst_operation(entry_src, entry_dst, dir, file)?;
}
} else {
file(src, dst)?;
}
Ok(())
}
fn move_items_no_replace(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> Result<(), std::io::Error> {
let src = src.as_ref();
let dst = dst.as_ref();
try_creating_placeholders(src, dst)?;
execute_src_to_dst_operation(src, dst, &|_| Ok(()), &|src, dst| {
if let Some(parent) = dst.parent() {
if let Err(err) = std::fs::create_dir_all(parent) {
warn!("Failed to create destination directory. It probably already exists. {:?}", err);
}
}
std::fs::rename(src, dst)
})?;
if src.is_dir() {
std::fs::remove_dir_all(src)?;
}
Ok(())
}
fn try_creating_placeholders(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> Result<(), std::io::Error> {
let src = src.as_ref();
let dst = dst.as_ref();
let metadata = src.symlink_metadata()?;
if metadata.is_dir() {
std::fs::create_dir(dst)?;
} else {
OpenOptions::new().create_new(true).write(true).open(dst)?;
}
Ok(())
}
fn parse_uri_path(absolute_file_path: impl AsRef<Path>) -> String {
let file_path_chars = absolute_file_path.as_ref().to_str().unwrap().chars();
let url: String = "file://".chars().chain(file_path_chars).collect();
return url::Url::parse(&url).unwrap().to_file_path().unwrap().to_str().unwrap().into();
}
fn encode_uri_path(absolute_file_path: impl AsRef<Path>) -> String {
let url = url::Url::from_file_path(absolute_file_path.as_ref()).unwrap();
url.path().to_owned()
}
#[derive(Eq, PartialEq, Debug)]
enum TrashValidity {
Valid,
InvalidSymlink,
InvalidNotSticky,
}
fn folder_validity(path: impl AsRef<Path>) -> Result<TrashValidity, Error> {
const S_ISVTX: u32 = 0x1000;
let metadata = path.as_ref().symlink_metadata().map_err(|e| fsys_err_to_unknown(path, e))?;
if metadata.file_type().is_symlink() {
return Ok(TrashValidity::InvalidSymlink);
}
let mode = metadata.permissions().mode();
let no_sticky_bit = (mode & S_ISVTX) == 0;
if no_sticky_bit {
return Ok(TrashValidity::InvalidNotSticky);
}
Ok(TrashValidity::Valid)
}
fn home_trash() -> Result<PathBuf, Error> {
if let Some(data_home) = std::env::var_os("XDG_DATA_HOME") {
if data_home.len() > 0 {
let data_home_path = AsRef::<Path>::as_ref(data_home.as_os_str());
return Ok(data_home_path.join("Trash"));
}
}
if let Some(home) = std::env::var_os("HOME") {
if home.len() > 0 {
let home_path = AsRef::<Path>::as_ref(home.as_os_str());
return Ok(home_path.join(".local/share/Trash"));
}
}
Err(Error::Unknown { description: "Neither the XDG_DATA_HOME nor the HOME environment variable was found".into() })
}
fn home_topdir(mnt_points: &[MountPoint]) -> Result<PathBuf, Error> {
if let Some(data_home) = std::env::var_os("XDG_DATA_HOME") {
if data_home.len() > 0 {
let data_home_path = AsRef::<Path>::as_ref(data_home.as_os_str());
return Ok(get_topdir_for_path(data_home_path, mnt_points).to_owned());
}
}
if let Some(home) = std::env::var_os("HOME") {
if home.len() > 0 {
let home_path = AsRef::<Path>::as_ref(home.as_os_str());
return Ok(get_topdir_for_path(home_path, mnt_points).to_owned());
}
}
Err(Error::Unknown { description: "Neither the XDG_DATA_HOME nor the HOME environment variable was found".into() })
}
fn get_topdir_for_path<'a>(path: &Path, mnt_points: &'a [MountPoint]) -> &'a Path {
let root: &'static Path = Path::new("/");
let mut topdir = None;
for mount_point in mnt_points.iter() {
if mount_point.mnt_dir == root {
continue;
}
if path.starts_with(&mount_point.mnt_dir) {
topdir = Some(&mount_point.mnt_dir);
break;
}
}
if let Some(t) = topdir {
t
} else {
root
}
}
struct MountPoint {
mnt_dir: PathBuf,
_mnt_type: String,
_mnt_fsname: String,
}
#[cfg(target_os = "linux")]
fn get_mount_points() -> Result<Vec<MountPoint>, Error> {
use once_cell::sync::Lazy;
use scopeguard::defer;
use std::ffi::{CStr, CString};
use std::sync::Mutex;
static LOCK: Lazy<Mutex<()>> = Lazy::new(|| Mutex::new(()));
let _lock = LOCK.lock().unwrap();
let read_arg = CString::new("r").unwrap();
let mounts_path = CString::new("/proc/mounts").unwrap();
let mut file = unsafe { libc::fopen(mounts_path.as_c_str().as_ptr(), read_arg.as_c_str().as_ptr()) };
if file.is_null() {
let mtab_path = CString::new("/etc/mtab").unwrap();
file = unsafe { libc::fopen(mtab_path.as_c_str().as_ptr(), read_arg.as_c_str().as_ptr()) };
}
if file.is_null() {
return Err(Error::Unknown { description: "Neither '/proc/mounts' nor '/etc/mtab' could be opened.".into() });
}
defer! { unsafe { libc::fclose(file); } }
let mut result = Vec::new();
loop {
let mntent = unsafe { libc::getmntent(file) };
if mntent.is_null() {
break;
}
let dir = unsafe { CStr::from_ptr((*mntent).mnt_dir).to_str().unwrap() };
if dir.bytes().len() == 0 {
continue;
}
let mount_point = unsafe {
MountPoint {
mnt_dir: dir.into(),
_mnt_fsname: CStr::from_ptr((*mntent).mnt_fsname).to_str().unwrap().into(),
_mnt_type: CStr::from_ptr((*mntent).mnt_type).to_str().unwrap().into(),
}
};
result.push(mount_point);
}
if result.is_empty() {
return Err(Error::Unknown {
description: "A mount points file could be opened, but the call to `getmntent` returned NULL.".into(),
});
}
Ok(result)
}
#[cfg(any(target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))]
fn get_mount_points() -> Result<Vec<MountPoint>, Error> {
use once_cell::sync::Lazy;
use std::sync::Mutex;
static LOCK: Lazy<Mutex<()>> = Lazy::new(|| Mutex::new(()));
let _lock = LOCK.lock().unwrap();
fn c_buf_to_str(buf: &[libc::c_char]) -> Option<&str> {
let buf: &[u8] = unsafe { std::slice::from_raw_parts(buf.as_ptr() as _, buf.len()) };
if let Some(pos) = buf.iter().position(|x| *x == 0) {
std::str::from_utf8(&buf[..pos]).ok()
} else {
std::str::from_utf8(buf).ok()
}
}
let mut fs_infos: *mut libc::statfs = std::ptr::null_mut();
let count = unsafe { libc::getmntinfo(&mut fs_infos, libc::MNT_WAIT) };
if count < 1 {
return Ok(Vec::new());
}
let fs_infos: &[libc::statfs] = unsafe { std::slice::from_raw_parts(fs_infos as _, count as _) };
let mut result = Vec::new();
for fs_info in fs_infos {
if fs_info.f_mntfromname[0] == 0 || fs_info.f_mntonname[0] == 0 {
continue;
}
let fs_type = c_buf_to_str(&fs_info.f_fstypename).unwrap_or_default();
let mount_to = match c_buf_to_str(&fs_info.f_mntonname) {
Some(m) => m,
None => {
debug!("Cannot get disk mount point, ignoring it.");
continue;
}
};
let mount_from = c_buf_to_str(&fs_info.f_mntfromname).unwrap_or_default();
let mount_point =
MountPoint { mnt_dir: mount_to.into(), _mnt_fsname: mount_from.into(), _mnt_type: fs_type.into() };
result.push(mount_point);
}
Ok(result)
}
#[cfg(test)]
mod tests {
use serial_test::serial;
use std::{
collections::{hash_map::Entry, HashMap},
env,
ffi::OsString,
fmt,
fs::File,
path::{Path, PathBuf},
process::Command,
};
use log::warn;
use crate::{
canonicalize_paths, delete_all,
os_limited::{list, purge_all},
tests::get_unique_name,
Error,
};
#[test]
#[serial]
fn test_list() {
crate::tests::init_logging();
let file_name_prefix = get_unique_name();
let batches: usize = 2;
let files_per_batch: usize = 3;
let names: Vec<_> = (0..files_per_batch).map(|i| format!("{}#{}", file_name_prefix, i)).collect();
for _ in 0..batches {
for path in names.iter() {
File::create(path).unwrap();
}
let result = delete_all_using_system_program(&names);
if let Err(SystemTrashError::NoTrashProgram) = &result {
warn!("No system default trashing utility was found, using this crate's implementation");
delete_all(&names).unwrap();
} else {
result.unwrap();
}
}
let items = list().unwrap();
let items: HashMap<_, Vec<_>> =
items.into_iter().filter(|x| x.name.starts_with(&file_name_prefix)).fold(HashMap::new(), |mut map, x| {
match map.entry(x.name.clone()) {
Entry::Occupied(mut entry) => {
entry.get_mut().push(x);
}
Entry::Vacant(entry) => {
entry.insert(vec![x]);
}
}
map
});
for name in names {
match items.get(&name) {
Some(items) => assert_eq!(items.len(), batches),
None => panic!("ERROR Could not find '{}' in {:#?}", name, items),
}
}
let _ = purge_all(items.into_iter().map(|(_name, item)| item).flatten());
}
#[derive(Debug)]
pub enum SystemTrashError {
NoTrashProgram,
Other(Error),
}
impl fmt::Display for SystemTrashError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "SystemTrashError during a `trash` operation: {:?}", self)
}
}
impl std::error::Error for SystemTrashError {}
fn is_program_in_path(program: &str) -> bool {
if let Some(path_vars) = std::env::var_os("PATH") {
for path in std::env::split_paths(&path_vars) {
let full_path = path.join(program);
if full_path.is_file() {
return true;
}
}
}
false
}
pub fn delete_all_canonicalized_using_system_program(full_paths: Vec<PathBuf>) -> Result<(), SystemTrashError> {
static DEFAULT_TRASH: &str = "gio";
let trash = {
let desktop_env = get_desktop_environment();
if desktop_env == DesktopEnvironment::Kde4 || desktop_env == DesktopEnvironment::Kde5 {
"kioclient5"
} else if desktop_env == DesktopEnvironment::Kde3 {
"kioclient"
} else {
DEFAULT_TRASH
}
};
let mut argv = Vec::<OsString>::with_capacity(full_paths.len() + 2);
if trash == "kioclient5" || trash == "kioclient" {
argv.push("move".into());
for full_path in full_paths.iter() {
argv.push(full_path.into());
}
argv.push("trash:/".into());
} else {
argv.push("trash".into());
for full_path in full_paths.iter() {
argv.push(full_path.into());
}
}
if !is_program_in_path(trash) {
return Err(SystemTrashError::NoTrashProgram);
}
let mut command = Command::new(trash);
command.args(argv);
let result = command.output().map_err(|e| {
SystemTrashError::Other(Error::Unknown {
description: format!("Tried executing: {:?} - Error was: {}", command, e),
})
})?;
if !result.status.success() {
let stderr = String::from_utf8_lossy(&result.stderr);
return Err(SystemTrashError::Other(Error::Unknown {
description: format!("Used '{}', stderr: {}", trash, stderr),
}));
}
Ok(())
}
pub fn delete_all_using_system_program<I, T>(paths: I) -> Result<(), SystemTrashError>
where
I: IntoIterator<Item = T>,
T: AsRef<Path>,
{
let full_paths = canonicalize_paths(paths).map_err(SystemTrashError::Other)?;
delete_all_canonicalized_using_system_program(full_paths)
}
#[derive(PartialEq)]
enum DesktopEnvironment {
Other,
Cinnamon,
Gnome,
Kde3,
Kde4,
Kde5,
Pantheon,
Unity,
Xfce,
}
fn env_has_var(name: &str) -> bool {
env::var_os(name).is_some()
}
fn get_desktop_environment() -> DesktopEnvironment {
static KDE_SESSION_ENV_VAR: &str = "KDE_SESSION_VERSION";
if let Ok(xdg_current_desktop) = env::var("XDG_CURRENT_DESKTOP") {
for value in xdg_current_desktop.split(':') {
let value = value.trim();
if value.is_empty() {
continue;
}
match value {
"Unity" => {
if let Ok(desktop_session) = env::var("DESKTOP_SESSION") {
if desktop_session.contains("gnome-fallback") {
return DesktopEnvironment::Gnome;
}
}
return DesktopEnvironment::Unity;
}
"GNOME" => {
return DesktopEnvironment::Gnome;
}
"X-Cinnamon" => {
return DesktopEnvironment::Cinnamon;
}
"KDE" => {
if let Ok(kde_session) = env::var(KDE_SESSION_ENV_VAR) {
if kde_session == "5" {
return DesktopEnvironment::Kde5;
}
}
return DesktopEnvironment::Kde4;
}
"Pantheon" => {
return DesktopEnvironment::Pantheon;
}
"XFCE" => {
return DesktopEnvironment::Xfce;
}
_ => {}
}
}
}
if let Ok(desktop_session) = env::var("DESKTOP_SESSION") {
match desktop_session.as_str() {
"gnome" | "mate" => {
return DesktopEnvironment::Gnome;
}
"kde4" | "kde-plasma" => {
return DesktopEnvironment::Kde4;
}
"kde" => {
if env_has_var(KDE_SESSION_ENV_VAR) {
return DesktopEnvironment::Kde4;
}
return DesktopEnvironment::Kde3;
}
"xubuntu" => {
return DesktopEnvironment::Xfce;
}
_ => {}
}
if desktop_session.contains("xfce") {
return DesktopEnvironment::Xfce;
}
}
if env_has_var("GNOME_DESKTOP_SESSION_ID") {
return DesktopEnvironment::Gnome;
} else if env_has_var("KDE_FULL_SESSION") {
if env_has_var(KDE_SESSION_ENV_VAR) {
return DesktopEnvironment::Kde4;
}
return DesktopEnvironment::Kde3;
}
DesktopEnvironment::Other
}
}
fn fsys_err_to_unknown<P: AsRef<Path>>(path: P, orig: std::io::Error) -> Error {
Error::FileSystem { path: path.as_ref().to_owned(), kind: orig.kind() }
}