cargo_mobile2/os/linux/
mod.rs

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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
pub(super) mod info;
mod xdg;

use std::{
    ffi::{OsStr, OsString},
    io,
    path::{Path, PathBuf},
};
use thiserror::Error;

use crate::DuctExpressionExt;
pub use crate::{
    env::{Env, ExplicitEnv},
    util::ln,
};

#[derive(Debug, Error)]
pub enum DetectEditorError {
    #[error("No default editor is set: xdg-mime queries for \"text/rust\" and \"text/plain\" both failed")]
    NoDefaultEditorSet,
    #[error("Entry Not Found: xdg-mime returned an entry name that could not be found")]
    FreeDesktopEntryNotFound,
    #[error(
        "Entry Parse Error: xdg-mime returned an entry that could not be parsed. Caused by {0}"
    )]
    FreeDesktopEntryParseError(io::Error),
    #[error("Entry Parse Error: file lookup failed. Caused by {0}")]
    FreeDesktopEntryLookupFailed(io::Error),
    #[error("Exec field on desktop entry was not found")]
    ExecFieldMissing,
}

#[derive(Debug, Error)]
pub enum OpenFileError {
    #[error("Launch failed: {0}")]
    LaunchFailed(std::io::Error),
    #[error("Command parsing failed")]
    CommandParsingFailed,
}

#[derive(Debug)]
pub struct Application {
    exec_command: OsString,
    icon: Option<OsString>,
    xdg_entry_path: PathBuf,
}

impl Application {
    pub fn detect_editor() -> Result<Self, DetectEditorError> {
        // Try a rust code editor, then a plain text editor. If neither are available,
        // then return an error.
        let entry = xdg::query_mime_entry("text/rust")
            .or_else(|| xdg::query_mime_entry("text/plain"))
            .ok_or(DetectEditorError::NoDefaultEditorSet)?;

        xdg::get_xdg_data_dirs()
            .iter()
            .find_map(|dir| {
                let dir = dir.join("applications");
                xdg::find_entry_in_dir(&dir, &entry)
                    // If finding an entry (a filename) in that directory, returns an error (such as if the directory
                    // is non existent, or the directory exists but for whatever reason listing its contents failed),
                    // we should skip it, as per the XDG Base Directory Specification v0.7 (latest as of today)
                    .ok()? // This returns None on error, continuing the search (skiping this dir)
                    .map(|entry_filepath| {
                        // If something was found, we have to try parsing it, which may fail as well
                        xdg::parse(&entry_filepath)
                            .map_err(DetectEditorError::FreeDesktopEntryParseError)
                            .and_then(|parsed_entry| {
                                Ok(Self {
                                    // We absolutely want the Exec value
                                    exec_command: parsed_entry
                                        .section("Desktop Entry")
                                        .attr("Exec")
                                        .ok_or(DetectEditorError::ExecFieldMissing)?
                                        .into(),
                                    // The icon is optional, we try getting it because the Exec value may need it
                                    icon: parsed_entry
                                        .section("Desktop Entry")
                                        .attr("Icon")
                                        .map(Into::into),
                                    xdg_entry_path: entry_filepath,
                                })
                            })
                    })
            })
            // If this returns None, no errors ocurred, and no elements were found
            .unwrap_or(Err(DetectEditorError::FreeDesktopEntryNotFound))
    }

    pub fn open_file(&self, path: impl AsRef<Path>) -> Result<(), OpenFileError> {
        let path = path.as_ref();

        let maybe_icon = self.icon.as_deref();

        // Parse the xdg command field with all the needed data
        let command_parts = xdg::parse_command(
            &self.exec_command,
            path.as_os_str(),
            maybe_icon,
            Some(&self.xdg_entry_path),
        );

        if !command_parts.is_empty() {
            // If command_parts has at least one element this works. If it has a single
            // element, &command_parts[1..] should be an empty slice (&[]) and duct
            // does not add any argument on that case
            duct::cmd(&command_parts[0], &command_parts[1..])
                .run_and_detach()
                .map_err(OpenFileError::LaunchFailed)
        } else {
            Err(OpenFileError::CommandParsingFailed)
        }
    }
}

pub fn open_file_with(
    application: impl AsRef<OsStr>,
    path: impl AsRef<OsStr>,
    env: &Env,
) -> Result<(), OpenFileError> {
    let app_str = application.as_ref();
    let path_str = path.as_ref();

    let command_parts = xdg::get_xdg_data_dirs()
        .iter()
        .find_map(|dir| {
            let dir = dir.join("applications");
            let (entry, entry_path) = xdg::find_entry_by_app_name(&dir, app_str)?;

            let command_parts = entry
                .section("Desktop Entry")
                .attr("Exec")
                .map(|str_entry| {
                    let osstring_entry: OsString = str_entry.into();
                    xdg::parse_command(
                        &osstring_entry,
                        path_str,
                        entry
                            .section("Desktop Entry")
                            .attr("Icon")
                            .map(|s| s.as_ref()),
                        Some(&entry_path),
                    )
                })?;
            // This could go outside, but we'd better have a proper error for it then
            if !command_parts.is_empty() {
                Some(command_parts) // This guarantees that command_parts has at least one element
            } else {
                None
            }
        })
        // Here is why we ought to change this function's return type, to fit this error
        .unwrap_or_else(|| vec![app_str.to_os_string()]);

    // If command_parts has at least one element, this won't panic from Out of Bounds
    duct::cmd(&command_parts[0], &command_parts[1..])
        .vars(env.explicit_env())
        .run_and_detach()
        .map_err(OpenFileError::LaunchFailed)
}

// We use "sh" in order to access "command -v", as that is a bultin command on sh.
// Linux does not require a binary "command" in path, so this seems the way to go.
#[cfg(target_os = "linux")]
pub fn command_path(name: &str) -> std::io::Result<std::process::Output> {
    duct::cmd("sh", ["-c", format!("command -v {name}").as_str()]).run()
}

pub fn code_command() -> duct::Expression {
    duct::cmd!("code")
}

pub fn replace_path_separator(path: OsString) -> OsString {
    path
}

pub mod consts {
    pub const CLANG: &str = "clang";
    pub const CLANGXX: &str = "clang++";
    pub const AR: &str = "ar";
    pub const LD: &str = "ld";
    pub const READELF: &str = "readelf";
    pub const NDK_STACK: &str = "ndk-stack";
}