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}