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        match list.binary_search(&action.ref_name) {
168            Ok(_) => {
169                if result.contains_key(&action.ref_name) {
170                    return Err(ParseError::KeyError {
171                        msg: format!(
172                            "There are two actions with the same name: {}",
173                            &action.ref_name
174                        ),
175                    });
176                }
177
178                let name = action.ref_name.clone();
179
180                result.insert(
181                    action.ref_name,
182                    DesktopAction {
183                        name: match action.name {
184                            Some(n) => n.try_into()?,
185                            None => {
186                                return Err(ParseError::KeyError {
187                                    msg: format!(
188                                        "The name of the action {} must be specified",
189                                        name
190                                    ),
191                                })
192                            }
193                        },
194                        exec: action.exec,
195                        icon: action.icon,
196                    },
197                );
198            }
199            Err(_) => {}
200        }
201    }
202
203    Ok(result)
204}
205
206impl TryInto<DesktopEntry> for DesktopEntryInternal {
207    type Error = ParseError;
208
209    fn try_into(self) -> Result<DesktopEntry, Self::Error> {
210        let entry_type: EntryType = match self.entry_type {
211            Some(EntryTypeInternal::Application) => {
212                let fields = ApplicationFields {
213                    try_exec: self.try_exec,
214                    exec: self.exec,
215                    path: self.path,
216                    terminal: self.terminal,
217                    actions: self.actions,
218                    mime_type: self.mime_type,
219                    categories: self.categories,
220                    implements: self.implements,
221                    keywords: match self.keywords {
222                        Some(l) => Some(l.try_into()?),
223                        None => None,
224                    },
225                    startup_notify: self.startup_notify,
226                    startup_wm_class: self.startup_wm_class,
227                    prefers_non_default_gpu: self.prefers_non_default_gpu,
228                    single_main_window: self.single_main_window,
229                };
230                EntryType::Application(fields)
231            }
232            Some(EntryTypeInternal::Link) => {
233                let fields = LinkFields {
234                    url: match self.url {
235                        Some(url) => url,
236                        None => {
237                            return Err(ParseError::KeyError {
238                                msg: "URL must be specified for the entry type Link".into(),
239                            })
240                        }
241                    },
242                };
243                EntryType::Link(fields)
244            }
245            Some(EntryTypeInternal::Directory) => EntryType::Directory,
246            None => {
247                return Err(ParseError::KeyError {
248                    msg: "Entry Type must be specified".into(),
249                })
250            }
251            _ => EntryType::Unknown,
252        };
253
254        Ok(DesktopEntry {
255            entry_type,
256            version: self.version,
257            name: match self.name {
258                Some(n) => n.try_into()?,
259                None => {
260                    return Err(ParseError::KeyError {
261                        msg: "Entry name must be specified".into(),
262                    })
263                }
264            },
265            generic_name: match self.generic_name {
266                Some(l) => Some(l.try_into()?),
267                None => None,
268            },
269            no_display: self.no_display,
270            comment: match self.comment {
271                Some(l) => Some(l.try_into()?),
272                None => None,
273            },
274            icon: self.icon,
275            hidden: self.hidden,
276            only_show_in: self.only_show_in,
277            not_show_in: self.not_show_in,
278            dbus_activatable: self.dbus_activatable,
279        })
280    }
281}