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}