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
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
/* 
desktop_entry.rs
Rusified in 2021 Copyright Israel Dahl. All rights reserved.

       /VVVV\
     /V      V\
   /V          V\
  /      0 0     \
  \|\|\</\/\>/|/|/
       \_/\_/

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2 as
published by the Free Software Foundation.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

*/

extern crate tini;
use tini::Ini;
use std::path::Path;

use crate::utils::get_language;

/// This specification defines 3 types of desktop entries:
/// * Application (type 1),
/// * Link (type 2)
/// * Directory (type 3)
///
/// To allow the addition of new types in the future, implementations should ignore desktop entries with an "unknown" type.
#[derive(Debug)]
pub enum DesktopType {
	Application,
	Link,
	Directory,
	/// Ignore 'Unknown' type in your program, it really just means it is anything other than the above known types
	Unknown,
}

fn to_bool(value:Option<String>)->Option<bool> {
	if value.is_none() {
		return None
	}

	let mut val = value.unwrap();
	val.make_ascii_lowercase();
	if val == "true" {
		return Some(true)
	}
	Some(false)
}

/// This function converts a Option<String> to a DesktopType
pub fn convert_desktop_type(desktop_type_option:Option<String>)->DesktopType {
	if desktop_type_option.is_some() {
		let dt = desktop_type_option.unwrap();
		if dt == "Application" {
			return DesktopType::Application
		}
		else if dt == "Link" {
			return DesktopType::Link
		}
		else if dt == "Directory" {
			return DesktopType::Directory
		}
	}
	return DesktopType::Unknown
}

/// This function will return a Option<String> from a DesktopType
pub fn display_desktop_type(dt:DesktopType)->Option<String> {
	match dt {
		DesktopType::Application => return Some(String::from("Application")),
		DesktopType::Link => return Some(String::from("Link")),
		DesktopType::Directory => return Some(String::from("Directory")),
		_=> return None,
	}
}

#[derive(Debug)]
/// # Recognized desktop entry keys
///
/// [XDG specs](https://specifications.freedesktop.org)
///
/// `Option<String>` will be used for all localeString/String
///
/// `Option<Vec<String>>` for all localeString(s)/String(s)
///
/// `Option<bool>` will be used for all boolean

///
/// `f32` for version number
///
/// `DesktopType` enumerates known "Type" fields and the "Unknown" reserved "Type"
///
/// type is a reserved word in rust, so it is called 'desktop_type'
///
/// I copied and pasted the website here and made things rustified
///
/// No camelcase inside structs, all lowercase/underscore version of CamelCase
///
/// Comments are just because I didn't delete the rest of the text from the webpage
pub struct DesktopEntry {
//type is a reserved word in rust and other languages so we will use desktop_type
	/// 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. 	Stringing	YES
	pub desktop_type:DesktopType,
	/// 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. 	Stringing	NO	1-3
	pub version:f32,
	/// Specific name of the application, for example "Mozilla". 	localeStringing	YES	1-3
	pub name:Option<String>,
	/// Generic name of the application, for example "Web Browser". 	localeStringing	NO	1-3
	pub generic_name:Option<String>,
	/// 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). 	boolean	NO	1-3
	pub no_display:Option<bool>,
	/// Tooltip for the entry, for example "View sites on the Internet". The value should not be redundant with the values of Name and GenericName. 	localeStringing	NO	1-3
	pub comment:Option<String>,
	/// 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. 	iconStringing	NO	1-3
	pub icon:Option<String>,
	/// Hidden should have been called Deleted. It means the user deleted (at his level) something that was present (at an upper level, e.g. in the system dirs). It's Stringictly 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. 	boolean	NO	1-3
	pub hidden:Option<bool>,
	/// A list of Strings identifying the desktop environments that should display/not display a given desktop entry.
/// 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.
/// If $XDG_CURRENT_DESKTOP is set then it contains a colon-separated list of Strings. In order, each Stringing 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).
/// $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.
/// The same desktop name may not appear in both OnlyShowIn and NotShowIn of a group. Stringing(s)	NO	1-3
	pub only_show_in:Option<Vec<String>>,
	pub not_show_in:Option<Vec<String>>,
	/// 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. 	boolean	NO
	pub dbus_activatable:Option<bool>,
	/// 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). 	Stringing	NO	1
	pub try_exec:Option<String>,
	/// 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. 	Stringing	NO	1
	pub exec:Option<String>,
	/// If entry is of type Application, the working directory to run the program in. 	Stringing	NO	1
	pub path:Option<String>,
	/// Whether the program runs in a terminal window. 	boolean	NO	1
	pub terminal:Option<bool>,
	/// 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. 	Stringing(s)	NO	1
	pub actions:Option<Vec<String>>,
	/// The MIME type(s) supported by this application. 	Stringing(s)	NO	1
	pub mime_type:Option<Vec<String>>,
	/// Categories in which the entry should be shown in a menu (for possible values see the Desktop Menu Specification). 	Stringing(s)	NO	1
	pub categories:Option<Vec<String>>,
	/// 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. 	Stringing(s)	NO
	pub implements:Option<Vec<String>>,
	/// 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. 	localeStringing(s)	NO	1
	pub keywords:Option<Vec<String>>,
	/// 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 for more details). 	boolean	NO	1
	pub startup_notify:Option<bool>,
	/// If specified, it is known that the application will map at least one window with the given Stringing as its WM class or WM name hint (see the Startup Notification Protocol Specification for more details). 	Stringing	NO	1
	pub startup_wm_class:Option<String>,
	/// If entry is Link type, the URL to access. 	Stringing	YES	2
	pub url:Option<String>,
	/// 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. 	boolean	NO	1
	pub prefers_non_default_gpu:Option<bool>,
}

///  Implementations
impl DesktopEntry {
	/// Creates a blank entry with a specified type
	pub fn empty(initial_type:DesktopType)->Self where Self:Sized {

		DesktopEntry {
			desktop_type:initial_type,
			version:1.0,
			name:None,
			generic_name:None,
			no_display:None,
			comment:None,
			icon:None,
			hidden:None,
			only_show_in:None,
			not_show_in:None,
			dbus_activatable:None,
			try_exec:None,
			exec:None,
			path:None,
			terminal:None,
			actions:None,
			mime_type:None,
			categories:None,
			implements:None,
			keywords:None,
			startup_notify:None,
			startup_wm_class:None,
			url:None,
			prefers_non_default_gpu:None,
		}
    }
    /// Creates a 'new' DesktopEntry from reading a file
    pub fn new(file_name:String)->Self where Self:Sized {
		let test_ini = Ini::from_file(&file_name);
		if test_ini.is_err() {
			//TODO println!("ERROR!!!{}",file_name);
			return Self::empty(DesktopType::Application)
		}
		let conf = test_ini.unwrap();

		let section = "Desktop Entry";
		let mut locale:bool = false;
		let lang_var: Option<String> = get_language();
		if lang_var.is_some() {
			locale = true;
		}
		let lang:String = lang_var.unwrap_or(String::from(""));
		//TODO
		let mut lang_two:String = lang.to_owned();
		let mut use_two_lang:bool = false;
		let pos = lang_two.chars().position(|c| c == '_');
		if pos.is_some() {
			use_two_lang = true;
			let posi = pos.unwrap();
			if posi < lang_two.len() {
				// throw away variable for excess of trim
				let _junk = lang_two.split_off(posi);
			}
		}

		//Populate our struct
		let dt:Option<String>                   = conf.get(section, "Type");
		let mut ver:Option<String>                  = conf.get(section, "Version");
		let nd:Option<String>                   = conf.get(section, "NoDisplay");
		let com:Option<String>                  = conf.get(section,"Comment");
		let ic:Option<String>                   = conf.get(section, "Icon");
		let hid:Option<String>                  = conf.get(section, "Hidden");
		let only:Option<Vec<String>>    = conf.get_vec_with_sep(section, "OnlyShowIn",";");
		let not:Option<Vec<String>>     = conf.get_vec_with_sep(section, "NotShowIn",";");
		let dbus:Option<String>                 = conf.get(section, "DBusActivatable");
		let tex:Option<String>                  = conf.get(section, "TryExec");
		let ex:Option<String>                   = conf.get(section, "Exec");
		let pth:Option<String>                  = conf.get(section, "Path");
		let term:Option<String>                 = conf.get(section, "Terminal");
		let act:Option<Vec<String>>     = conf.get_vec_with_sep(section, "Actions",";");
		let mime:Option<Vec<String>>    = conf.get_vec_with_sep(section, "MimeType",";");
		let cat:Option<Vec<String>>     = conf.get_vec_with_sep(section, "Categories",";");
		let imp:Option<Vec<String>>     = conf.get_vec_with_sep(section, "Implements",";");
		let start:Option<String>                = conf.get(section,"StartupNotify");
		let wm:Option<String>                   = conf.get(section,"StartupWMClass");
		let ur:Option<String>                   = conf.get(section,"URL");
		let gpu:Option<String>                  = conf.get(section,"PrefersNonDefaultGPU");
		//LOCALE
		let nom:Option<String>              = conf.get(section,"Name"); //why was is 'ref' again?
		let gen_nom:Option<String>              = conf.get(section, "GenericName");
		let keyw:Option<Vec<String>>    = conf.get_vec_with_sep(section,"Keywords",";");

// these are the 'return' variables for the struct
		let mut local_name:Option<String> = None;
		let mut local_gen:Option<String> = None;
		let mut local_key:Option<Vec<String>> = None;

		// Need to parse for locale specific strings
		if locale {
			
			// NAME
			let item:String = format!("{}{}{}{}","Name","[",lang,"]");
			let attempt_n: Option<String> = conf.get(section,item.as_str());
			if attempt_n.is_none() {
				if use_two_lang {
					let itm2:String = format!("{}{}{}{}","Name","[",lang_two,"]");
					let attmpt2: Option<String> = conf.get(section, itm2.as_str());
					if attmpt2.is_some() {
						local_name = attmpt2.to_owned();
					}
				}
			}
			else {
				local_name = attempt_n;
			}
			if local_name.is_none() {
				local_name = nom;
			}

			// GENERIC NAME
			let item1:String = format!("{}{}{}{}","GenericName","[",lang,"]");
			let attempt_gn: Option<String> = conf.get(section,item1.as_str());
			if attempt_gn.is_none() {
				if use_two_lang {
					let itm:String = format!("{}{}{}{}","GenericName","[",lang_two,"]");
					let attmpt3: Option<String> = conf.get(section, itm.as_str());
					if attmpt3.is_some() {
						local_gen = attmpt3;
					}
				}
			}
			else {
				local_gen = attempt_gn;
			}
			if local_gen.is_none() {
				local_gen = gen_nom;
			}

			// KEYWORDS
			let itm1:String = format!("{}{}{}{}","Keywords","[",lang,"]");
			let attempt_k: Option<Vec<String>> = conf.get_vec_with_sep(section,itm1.as_str(),";");
			if attempt_k.is_none() {
				if use_two_lang {
					let it3m:String = format!("{}{}{}{}","Keywords","[",lang_two,"]");
					let attm3pt: Option<Vec<String>> = conf.get_vec_with_sep(section, it3m.as_str(),";");
					if attm3pt.is_some() {
						local_key = attm3pt;
					}
				}
			}
			else {
				local_key = attempt_k;
			}
			if local_key.is_none() {
				local_key = keyw;
			}
		}
		if ver.is_none() {
			ver = Some(String::from("1.0"));// 1.5/1.0 ???
		}
		DesktopEntry {
			desktop_type:convert_desktop_type(dt),
			version:ver.unwrap().parse::<f32>().ok().unwrap(),
			name:local_name,
			generic_name:local_gen,
			no_display:to_bool(nd),
			comment:com,
			icon:ic,
			hidden:to_bool(hid),
			only_show_in:only,
			not_show_in:not,
			dbus_activatable:to_bool(dbus),
			try_exec:tex,
			exec:ex,
			path:pth,
			terminal:to_bool(term),
			actions:act,
			mime_type:mime,
			categories:cat,
			implements:imp,
			keywords:local_key,
			startup_notify:to_bool(start),
			startup_wm_class:wm,
			url:ur,
			prefers_non_default_gpu:to_bool(gpu),
		}
    }
}