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
126
use std::process::{Child, Command, Stdio};

#[derive(Debug, Clone)]
pub struct Exec {
    name: String,
    exec: String,
    terminal: bool,
}

// TODO: find out how to make it do something more intelligent with '%'s
fn process_exec(exec: &str) -> String {
    let res: String = exec
        .split(' ')
        .filter(|arg| (*arg).chars().next().unwrap() != '%')
        .map(|sv| sv.chars())
        .flatten()
        .collect();

    res
}

impl Exec {
    fn new(name: &str, exec: &str, terminal: bool) -> Self {
        Self {
            name: String::from(name),
            exec: String::from(exec),
            terminal,
        }
    }

    /// spawns the process with `exec` and provided `term_cmd` if
    /// the desktop entry contained `Terminal=true`
    pub fn run(&self, term_cmd: &str) {
        let exec = process_exec(&self.exec);

        if self.terminal {
            spawn(&format!("{} {}", term_cmd, &exec));
        } else {
            spawn(&exec);
        }
    }
}

/// runs `dmenu_cmd` and returns exec corresponding to dmenu's
/// output
pub fn run_dmenu(execs: &Vec<Exec>, dmenu_cmd: &str) -> Option<Exec> {
    use std::io::Write;

    let names: Vec<String> = execs.iter().map(|exec| exec.name.clone()).collect();
    let names = names.join("\n");

    let mut dmenu = spawn(dmenu_cmd)?;

    dmenu
        .stdin
        .as_mut()
        .unwrap()
        .write_all(names.as_bytes())
        .ok()?;
    let output = dmenu.wait_with_output().ok()?;
    let output = String::from_utf8(output.stdout).ok()?;

    Some((execs.iter().find(|exec| exec.name == output.trim_end())?).clone())
}

use freedesktop_entry_parser::parse_entry;
use std::fs::{self, DirEntry};
use std::io;
use std::path::Path;

fn get_entries_from_path(dir: &Path) -> io::Result<Vec<DirEntry>> {
    let mut entries: Vec<DirEntry> = vec![];

    if dir.is_dir() {
        for entry in fs::read_dir(dir)? {
            let entry = entry?;
            let path = entry.path();
            if path.is_dir() {
                let mut sub_entries = get_entries_from_path(&path)?;
                entries.append(&mut sub_entries);
            } else {
                entries.push(entry);
            }
        }
    }
    Ok(entries)
}

pub fn get_execs(paths: Vec<&str>) -> Vec<Exec> {
    let execs: Vec<Exec> = paths
        .iter()
        .map(|path| get_entries_from_path(Path::new(path)).unwrap())
        .flatten()
        .filter_map(|direntry| {
            let ent = parse_entry(direntry.path()).ok()?;
            if let Some(nodisplay) = ent.section("Desktop Entry").attr("NoDisplay") {
                if nodisplay == "true" {
                    return None;
                }
            }

            let exec = Exec::new(
                ent.section("Desktop Entry").attr("Name")?,
                ent.section("Desktop Entry").attr("Exec")?,
                match ent.section("Desktop Entry").attr("Terminal") {
                    Some("true") => true,
                    _ => false,
                },
            );

            Some(exec)
        })
        .collect();

    execs
}

fn spawn(cmd: &str) -> Option<Child> {
    let mut args = cmd.split(' ');
    Command::new(args.next()?)
        .args(args)
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .spawn()
        .ok()
}