~goorzhel/radm

ref: b4ca8d0dde821420d66d488db969c0f7c5da5f6b radm/src/session.rs -rw-r--r-- 3.7 KiB
b4ca8d0d — Antonio Gurgel 0.3.0 4 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
//! Desktop entries.

use std::{
    fmt::{Debug, Display},
    fs::read_dir,
    path::Path,
};

use anyhow::{anyhow, Error, Result};
use configparser::ini::Ini;

fn log_if_err<T, E: Display>(result: Result<T, E>, msg: String) -> Option<T> {
    match result {
        Ok(v) => Some(v),
        Err(e) => {
            error!("{}: {}", msg, e);
            None
        }
    }
}

/// Searches directories non-recursively for session files, returning any that parse correctly
/// or an `Err` if none found.
pub fn find_sessions<P: AsRef<Path> + Debug>(dirs: &[P]) -> Result<Vec<Desktop>> {
    let mut out = dirs
        .iter()
        .filter_map(|path| log_if_err(read_dir(path), format!("Couldn't read dir {:?}", path)))
        .flat_map(|dir| {
            dir.filter_map(|entry| log_if_err(entry, "Couldn't read an entry (I/O error)".into()))
        })
        .inspect(|file| trace!("Parsing desktop file {}", file.path().to_string_lossy()))
        .filter_map(|file| {
            let path = file.path();
            log_if_err(
                Desktop::from_path(&path),
                format!("Couldn't parse desktop file {}", path.to_string_lossy()),
            )
        })
        .collect::<Vec<_>>();
    if out.is_empty() {
        return Err(anyhow!("Found no session files in: {:?}", dirs));
    }
    out.sort();
    Ok(out)
}

/// A representation of a [desktop entry](https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html) file.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Desktop {
    pub name: String,
    pub comment: Option<String>,
    pub exec: String, // PathBuf was overkill; I spent more time converting it to a String than using its features.
    pub protocol: Protocol,
}

/// The protocol with which to run a desktop environment.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum Protocol {
    Wayland,
    X11,
}

/// Detects the display protocol a desktop file is meant for by inspecting its path.
impl Protocol {
    fn from_path<P: AsRef<Path>>(path: P) -> Result<Self> {
        let path = path.as_ref().to_string_lossy();
        if path.contains("wayland-sessions") {
            return Ok(Self::Wayland);
        }
        if path.contains("xsessions") {
            return Ok(Self::X11);
        }
        Err(anyhow!("Couldn't guess at display protocol of {}", path))
    }
}
impl Desktop {
    /// Reads a desktop file from a path.
    fn from_path<P: AsRef<Path>>(path: P) -> Result<Self> {
        let mut out = Ini::new();
        let path = path.as_ref();

        out.load(path).map_err(Error::msg)?;
        let protocol = Protocol::from_path(path)?;
        let name = out
            .get("desktop entry", "name")
            .unwrap_or_else(|| "<unnamed>".to_string());
        let exec = out
            .get("desktop entry", "exec")
            .ok_or_else(|| anyhow!("No exec defined for this desktop entry"))?;
        let comment = out.get("desktop entry", "comment");
        Ok(Self {
            name,
            exec,
            comment,
            protocol,
        })
    }
}

#[cfg(test)]
mod test {
    use super::find_sessions;

    const WAYLAND_PATHS: [&str; 2] = [
        "/usr/share/wayland-sessions/",
        "/usr/local/share/wayland-sessions",
    ];
    const BOGUS_PATHS: [&str; 4] = ["\n", "aaaaaa", "🦀/wayland-sessions/", "/"];

    #[test]
    fn find_sessions_finds_sessions() {
        let result = find_sessions(&WAYLAND_PATHS);
        assert!(result.is_ok())
    }

    #[test]
    fn find_sessions_bails_when_none_available() {
        let result = find_sessions(&BOGUS_PATHS);
        assert!(result
            .err()
            .unwrap()
            .to_string()
            .starts_with("Found no session files in"));
    }
}