freedesktop_file_parser/
internal_structs.rs

1use std::collections::HashMap;
2
3use crate::{
4    ApplicationFields, DesktopAction, DesktopEntry, EntryType, IconString, LinkFields,
5    LocaleString, LocaleStringList, ParseError,
6};
7
8#[derive(Debug, Clone)]
9#[doc(hidden)]
10pub enum Header {
11    DesktopEntry,
12    DesktopAction { name: String },
13    Other { name: String },
14}
15
16#[derive(Debug, Clone, Default)]
17#[doc(hidden)]
18pub enum EntryTypeInternal {
19    #[default]
20    Application,
21    Link,
22    Directory,
23    Unknown(String),
24}
25
26impl From<&str> for EntryTypeInternal {
27    fn from(value: &str) -> Self {
28        match value {
29            "Application" => Self::Application,
30            "Link" => Self::Link,
31            "Directory" => Self::Directory,
32            _ => Self::Unknown(value.into()),
33        }
34    }
35}
36
37#[derive(Debug, Clone, Default)]
38#[doc(hidden)]
39pub struct LocaleStringInternal {
40    pub default: Option<String>, // required
41    pub variants: HashMap<String, String>,
42}
43
44#[derive(Debug, Clone, Default)]
45#[doc(hidden)]
46pub struct LocaleStringListInternal {
47    pub default: Option<Vec<String>>,
48    pub variants: HashMap<String, Vec<String>>,
49}
50
51#[derive(Debug, Clone, Default)]
52#[doc(hidden)]
53pub struct DesktopEntryInternal {
54    /// This specification defines 3 types of desktop entries: Application (type 1), Link (type 2) and Directory (type 3). To allow the addition of new types in the future, implementations should ignore desktop entries with an unknown type.
55    pub entry_type: Option<EntryTypeInternal>, // required
56    /// Version of the Desktop Entry Specification that the desktop entry conforms with. Entries that confirm with this version of the specification should use 1.5. Note that the version field is not required to be present.
57    pub version: Option<String>,
58    /// Specific name of the application, for example "Mozilla".
59    pub name: Option<LocaleStringInternal>, // required
60    /// Generic name of the application, for example "Web Browser".
61    pub generic_name: Option<LocaleStringInternal>,
62    /// NoDisplay means "this application exists, but don't display it in the menus". This can be useful to e.g. associate this application with MIME types, so that it gets launched from a file manager (or other apps), without having a menu entry for it (there are tons of good reasons for this, including e.g. the netscape -remote, or kfmclient openURL kind of stuff).
63    pub no_display: Option<bool>,
64    /// Tooltip for the entry, for example "View sites on the Internet". The value should not be redundant with the values of Name and GenericName.
65    pub comment: Option<LocaleStringInternal>,
66    /// Icon to display in file manager, menus, etc. If the name is an absolute path, the given file will be used. If the name is not an absolute path, the algorithm described in the Icon Theme Specification will be used to locate the icon.
67    pub icon: Option<IconString>,
68    /// Hidden should have been called Deleted. It means the user deleted (at their level) something that was present (at an upper level, e.g. in the system dirs). It's strictly equivalent to the .desktop file not existing at all, as far as that user is concerned. This can also be used to "uninstall" existing files (e.g. due to a renaming) - by letting make install install a file with Hidden=true in it.
69    pub hidden: Option<bool>,
70    /// A list of strings identifying the desktop environments that should display/not display a given desktop entry.
71    /// By default, a desktop file should be shown, unless an OnlyShowIn key is present, in which case, the default is for the file not to be shown.
72    /// If $XDG_CURRENT_DESKTOP is set then it contains a colon-separated list of strings. In order, each string is considered. If a matching entry is found in OnlyShowIn then the desktop file is shown. If an entry is found in NotShowIn then the desktop file is not shown. If none of the strings match then the default action is taken (as above).
73    /// $XDG_CURRENT_DESKTOP should have been set by the login manager, according to the value of the DesktopNames found in the session file. The entry in the session file has multiple values separated in the usual way: with a semicolon.
74    /// The same desktop name may not appear in both OnlyShowIn and NotShowIn of a group.
75    pub only_show_in: Option<Vec<String>>,
76    /// A list of strings identifying the desktop environments that should display/not display a given desktop entry.
77    /// By default, a desktop file should be shown, unless an OnlyShowIn key is present, in which case, the default is for the file not to be shown.
78    /// If $XDG_CURRENT_DESKTOP is set then it contains a colon-separated list of strings. In order, each string is considered. If a matching entry is found in OnlyShowIn then the desktop file is shown. If an entry is found in NotShowIn then the desktop file is not shown. If none of the strings match then the default action is taken (as above).
79    /// $XDG_CURRENT_DESKTOP should have been set by the login manager, according to the value of the DesktopNames found in the session file. The entry in the session file has multiple values separated in the usual way: with a semicolon.
80    /// The same desktop name may not appear in both OnlyShowIn and NotShowIn of a group.
81    pub not_show_in: Option<Vec<String>>,
82    /// A boolean value specifying if D-Bus activation is supported for this application. If this key is missing, the default value is false. If the value is true then implementations should ignore the Exec key and send a D-Bus message to launch the application. See D-Bus Activation for more information on how this works. Applications should still include Exec= lines in their desktop files for compatibility with implementations that do not understand the DBusActivatable key.
83    pub dbus_activatable: Option<bool>,
84    /// Path to an executable file on disk used to determine if the program is actually installed. If the path is not an absolute path, the file is looked up in the $PATH environment variable. If the file is not present or if it is not executable, the entry may be ignored (not be used in menus, for example).
85    pub try_exec: Option<String>,
86    /// Program to execute, possibly with arguments. See the Exec key for details on how this key works. The Exec key is required if DBusActivatable is not set to true. Even if DBusActivatable is true, Exec should be specified for compatibility with implementations that do not understand DBusActivatable.
87    pub exec: Option<String>,
88    /// If entry is of type Application, the working directory to run the program in.
89    pub path: Option<String>,
90    /// Whether the program runs in a terminal window.
91    pub terminal: Option<bool>,
92    /// Identifiers for application actions. This can be used to tell the application to make a specific action, different from the default behavior. The Application actions section describes how actions work.
93    pub actions: Option<Vec<String>>,
94    /// The MIME type(s) supported by this application.
95    pub mime_type: Option<Vec<String>>,
96    /// Categories in which the entry should be shown in a menu (for possible values see the Desktop Menu Specification).
97    pub categories: Option<Vec<String>>,
98    /// A list of interfaces that this application implements. By default, a desktop file implements no interfaces. See Interfaces for more information on how this works.
99    pub implements: Option<Vec<String>>,
100    /// A list of strings which may be used in addition to other metadata to describe this entry. This can be useful e.g. to facilitate searching through entries. The values are not meant for display, and should not be redundant with the values of Name or GenericName.
101    pub keywords: Option<LocaleStringListInternal>,
102    /// If true, it is KNOWN that the application will send a "remove" message when started with the DESKTOP_STARTUP_ID environment variable set. If false, it is KNOWN that the application does not work with startup notification at all (does not shown any window, breaks even when using StartupWMClass, etc.). If absent, a reasonable handling is up to implementations (assuming false, using StartupWMClass, etc.). (See the [Startup Notification Protocol Specification](https://www.freedesktop.org/wiki/Specifications/startup-notification-spec/) for more details).
103    pub startup_notify: Option<bool>,
104    /// If specified, it is known that the application will map at least one window with the given string as its WM class or WM name hint (see the [Startup Notification Protocol Specification](https://www.freedesktop.org/wiki/Specifications/startup-notification-spec/) for more details).
105    pub startup_wm_class: Option<String>,
106    /// If entry is Link type, the URL to access. Required if entry_type is link
107    pub url: Option<String>,
108    /// If true, the application prefers to be run on a more powerful discrete GPU if available, which we describe as “a GPU other than the default one” in this spec to avoid the need to define what a discrete GPU is and in which cases it might be considered more powerful than the default GPU. This key is only a hint and support might not be present depending on the implementation.
109    pub prefers_non_default_gpu: Option<bool>,
110    /// If true, the application has a single main window, and does not support having an additional one opened. This key is used to signal to the implementation to avoid offering a UI to launch another window of the app. This key is only a hint and support might not be present depending on the implementation.
111    pub single_main_window: Option<bool>,
112}
113
114#[derive(Default, Clone, Debug)]
115#[doc(hidden)]
116pub struct DesktopActionInternal {
117    pub ref_name: String,
118    pub name: Option<LocaleStringInternal>, // required
119    pub exec: Option<String>,
120    pub icon: Option<IconString>,
121}
122
123impl TryInto<LocaleString> for LocaleStringInternal {
124    type Error = ParseError;
125
126    fn try_into(self) -> Result<LocaleString, Self::Error> {
127        Ok(LocaleString {
128            default: match self.default {
129                Some(d) => d,
130                None => {
131                    return Err(ParseError::KeyError {
132                        msg: "The default value of locale string must be specified".into(),
133                    })
134                }
135            },
136            variants: self.variants,
137        })
138    }
139}
140
141impl TryInto<LocaleStringList> for LocaleStringListInternal {
142    type Error = ParseError;
143
144    fn try_into(self) -> Result<LocaleStringList, Self::Error> {
145        Ok(LocaleStringList {
146            default: match self.default {
147                Some(d) => d,
148                None => {
149                    return Err(ParseError::KeyError {
150                        msg: "The default value of locale string must be specified".into(),
151                    })
152                }
153            },
154            variants: self.variants,
155        })
156    }
157}
158
159pub fn vec_to_map(
160    vec: Vec<DesktopActionInternal>,
161    list: &mut [String],
162) -> Result<HashMap<String, DesktopAction>, ParseError> {
163    let mut result = HashMap::new();
164    list.sort();
165
166    for action in vec.into_iter() {
167        if list.binary_search(&action.ref_name).is_ok() {
168            if result.contains_key(&action.ref_name) {
169                return Err(ParseError::KeyError {
170                    msg: format!(
171                        "There are two actions with the same name: {}",
172                        &action.ref_name
173                    ),
174                });
175            }
176
177            let name = action.ref_name.clone();
178
179            result.insert(
180                action.ref_name,
181                DesktopAction {
182                    name: match action.name {
183                        Some(n) => n.try_into()?,
184                        None => {
185                            return Err(ParseError::KeyError {
186                                msg: format!("The name of the action {} must be specified", name),
187                            })
188                        }
189                    },
190                    exec: action.exec,
191                    icon: action.icon,
192                },
193            );
194        }
195    }
196
197    Ok(result)
198}
199
200impl TryInto<DesktopEntry> for DesktopEntryInternal {
201    type Error = ParseError;
202
203    fn try_into(self) -> Result<DesktopEntry, Self::Error> {
204        let entry_type: EntryType = match self.entry_type {
205            Some(EntryTypeInternal::Application) => {
206                let fields = ApplicationFields {
207                    try_exec: self.try_exec,
208                    exec: self.exec,
209                    path: self.path,
210                    terminal: self.terminal,
211                    actions: self.actions,
212                    mime_type: self.mime_type,
213                    categories: self.categories,
214                    implements: self.implements,
215                    keywords: match self.keywords {
216                        Some(l) => Some(l.try_into()?),
217                        None => None,
218                    },
219                    startup_notify: self.startup_notify,
220                    startup_wm_class: self.startup_wm_class,
221                    prefers_non_default_gpu: self.prefers_non_default_gpu,
222                    single_main_window: self.single_main_window,
223                };
224                EntryType::Application(fields)
225            }
226            Some(EntryTypeInternal::Link) => {
227                let fields = LinkFields {
228                    url: match self.url {
229                        Some(url) => url,
230                        None => {
231                            return Err(ParseError::KeyError {
232                                msg: "URL must be specified for the entry type Link".into(),
233                            })
234                        }
235                    },
236                };
237                EntryType::Link(fields)
238            }
239            Some(EntryTypeInternal::Directory) => EntryType::Directory,
240            None => {
241                return Err(ParseError::KeyError {
242                    msg: "Entry Type must be specified".into(),
243                })
244            }
245            _ => EntryType::Unknown,
246        };
247
248        Ok(DesktopEntry {
249            entry_type,
250            version: self.version,
251            name: match self.name {
252                Some(n) => n.try_into()?,
253                None => {
254                    return Err(ParseError::KeyError {
255                        msg: "Entry name must be specified".into(),
256                    })
257                }
258            },
259            generic_name: match self.generic_name {
260                Some(l) => Some(l.try_into()?),
261                None => None,
262            },
263            no_display: self.no_display,
264            comment: match self.comment {
265                Some(l) => Some(l.try_into()?),
266                None => None,
267            },
268            icon: self.icon,
269            hidden: self.hidden,
270            only_show_in: self.only_show_in,
271            not_show_in: self.not_show_in,
272            dbus_activatable: self.dbus_activatable,
273        })
274    }
275}