2
* Copyright (C) 2010 Michal Hruby <michal.mhr@gmail.com>
4
* This library is free software; you can redistribute it and/or
5
* modify it under the terms of the GNU Lesser General Public
6
* License as published by the Free Software Foundation; either
7
* version 2.1 of the License, or (at your option) any later version.
9
* This library is distributed in the hope that it will be useful,
10
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
* Lesser General Public License for more details.
14
* You should have received a copy of the GNU Lesser General Public License
15
* along with this program. If not, see <http://www.gnu.org/licenses/>.
17
* Authored by Michal Hruby <michal.mhr@gmail.com>
18
* Alberto Aldegheri <albyrock87+dev@gmail.com>
24
errordomain DesktopFileError
29
public class DesktopFileInfo: Object
31
// registered environments from http://standards.freedesktop.org/menu-spec/latest
34
public enum EnvironmentType
51
public string desktop_id { get; construct set; }
52
public string name { get; construct set; }
53
public string generic_name { get; construct set; }
54
public string comment { get; set; default = ""; }
55
public string icon_name { get; construct set; default = ""; }
56
public string gettext_domain { get; construct set; }
58
public bool needs_terminal { get; set; default = false; }
59
public string filename { get; construct set; }
61
public string exec { get; set; }
63
public bool is_hidden { get; private set; default = false; }
64
public bool is_valid { get; private set; default = true; }
66
public string[] mime_types = null;
68
private string? name_folded = null;
69
public unowned string get_name_folded ()
71
if (name_folded == null) name_folded = name.casefold ();
75
public EnvironmentType show_in { get; set; default = EnvironmentType.ALL; }
77
private const string[] SUPPORTED_GETTEXT_DOMAINS_KEYS = {"X-Ubuntu-Gettext-Domain", "X-GNOME-Gettext-Domain"};
78
private const string GROUP = "Desktop Entry";
80
public DesktopFileInfo.for_keyfile (string path, KeyFile keyfile,
83
Object (filename: path, desktop_id: desktop_id);
85
init_from_keyfile (keyfile);
88
private EnvironmentType parse_environments (string[] environments)
90
EnvironmentType result = 0;
91
foreach (unowned string env in environments)
93
string env_up = env.up ();
96
case "GNOME": result |= EnvironmentType.GNOME; break;
97
case "PANTHEON": result |= EnvironmentType.PANTHEON; break;
98
case "KDE": result |= EnvironmentType.KDE; break;
99
case "LXDE": result |= EnvironmentType.LXDE; break;
100
case "MATE": result |= EnvironmentType.MATE; break;
101
case "RAZOR": result |= EnvironmentType.RAZOR; break;
102
case "ROX": result |= EnvironmentType.ROX; break;
103
case "TDE": result |= EnvironmentType.TDE; break;
104
case "UNITY": result |= EnvironmentType.UNITY; break;
105
case "XFCE": result |= EnvironmentType.XFCE; break;
106
case "OLD": result |= EnvironmentType.OLD; break;
107
default: warning ("%s is not understood", env); break;
113
private void init_from_keyfile (KeyFile keyfile)
117
if (keyfile.get_string (GROUP, "Type") != "Application")
119
throw new DesktopFileError.UNINTERESTING_ENTRY ("Not Application-type desktop entry");
122
if (keyfile.has_key (GROUP, "Categories"))
124
string[] categories = keyfile.get_string_list (GROUP, "Categories");
125
if ("Screensaver" in categories)
127
throw new DesktopFileError.UNINTERESTING_ENTRY ("Screensaver desktop entry");
130
foreach (var domain_key in SUPPORTED_GETTEXT_DOMAINS_KEYS) {
131
if (keyfile.has_key (GROUP, domain_key)) {
132
gettext_domain = keyfile.get_string (GROUP, domain_key);
137
DesktopAppInfo app_info;
138
app_info = new DesktopAppInfo.from_keyfile (keyfile);
140
if (app_info == null)
142
throw new DesktopFileError.UNINTERESTING_ENTRY ("Unable to create AppInfo");
145
name = app_info.get_name ();
146
generic_name = app_info.get_generic_name () ?? "";
147
exec = app_info.get_commandline ();
150
throw new DesktopFileError.UNINTERESTING_ENTRY ("Unable to get exec for %s".printf (name));
153
// check for hidden desktop files
154
if (keyfile.has_key (GROUP, "Hidden") &&
155
keyfile.get_boolean (GROUP, "Hidden"))
159
if (keyfile.has_key (GROUP, "NoDisplay") &&
160
keyfile.get_boolean (GROUP, "NoDisplay"))
165
comment = app_info.get_description () ?? "";
167
var icon = app_info.get_icon () ??
168
new ThemedIcon ("application-default-icon");
169
icon_name = icon.to_string ();
171
if (keyfile.has_key (GROUP, "MimeType"))
173
mime_types = keyfile.get_string_list (GROUP, "MimeType");
175
if (keyfile.has_key (GROUP, "Terminal"))
177
needs_terminal = keyfile.get_boolean (GROUP, "Terminal");
179
if (keyfile.has_key (GROUP, "OnlyShowIn"))
181
show_in = parse_environments (keyfile.get_string_list (GROUP,
184
else if (keyfile.has_key (GROUP, "NotShowIn"))
186
var not_show = parse_environments (keyfile.get_string_list (GROUP,
188
show_in = EnvironmentType.ALL ^ not_show;
191
// special case these, people are using them quite often and wonder
192
// why they don't appear
193
if (filename.has_suffix ("gconf-editor.desktop") ||
194
filename.has_suffix ("dconf-editor.desktop"))
201
Utils.Logger.warning (this, "%s", err.message);
207
public class DesktopFileService : Object
209
private static unowned DesktopFileService? instance;
210
private Utils.AsyncOnce<bool> init_once;
212
// singleton that can be easily destroyed
213
public static DesktopFileService get_default ()
215
return instance ?? new DesktopFileService ();
218
private DesktopFileService ()
222
private Gee.List<FileMonitor> directory_monitors;
223
private Gee.List<DesktopFileInfo> all_desktop_files;
224
private Gee.List<DesktopFileInfo> non_hidden_desktop_files;
225
private Gee.Map<unowned string, Gee.List<DesktopFileInfo> > mimetype_map;
226
private Gee.Map<string, Gee.List<DesktopFileInfo> > exec_map;
227
private Gee.Map<string, DesktopFileInfo> desktop_id_map;
228
private Gee.MultiMap<string, string> mimetype_parent_map;
234
directory_monitors = new Gee.ArrayList<FileMonitor> ();
235
all_desktop_files = new Gee.ArrayList<DesktopFileInfo> ();
236
non_hidden_desktop_files = new Gee.ArrayList<DesktopFileInfo> ();
237
mimetype_parent_map = new Gee.HashMultiMap<string, string> ();
238
init_once = new Utils.AsyncOnce<bool> ();
243
~DesktopFileService ()
248
public async void initialize ()
250
if (init_once.is_initialized ()) return;
251
var is_locked = yield init_once.enter ();
252
if (!is_locked) return;
254
get_environment_type ();
255
DesktopAppInfo.set_desktop_env (session_type_str);
257
Idle.add_full (Priority.LOW, initialize.callback);
260
yield load_all_desktop_files ();
262
init_once.leave (true);
265
private DesktopFileInfo.EnvironmentType session_type =
266
DesktopFileInfo.EnvironmentType.GNOME;
267
private string session_type_str = "GNOME";
269
public DesktopFileInfo.EnvironmentType get_environment ()
271
return this.session_type;
274
private void get_environment_type ()
276
unowned string? session_var;
277
session_var = Environment.get_variable ("XDG_CURRENT_DESKTOP");
278
if (session_var == null)
280
session_var = Environment.get_variable ("DESKTOP_SESSION");
283
if (session_var == null) return;
285
string session = session_var.down ();
287
if (session.has_prefix ("unity") || session.has_prefix ("ubuntu"))
289
session_type = DesktopFileInfo.EnvironmentType.UNITY;
290
session_type_str = "Unity";
292
else if (session.has_prefix ("kde"))
294
session_type = DesktopFileInfo.EnvironmentType.KDE;
295
session_type_str = "KDE";
297
else if (session.has_prefix ("gnome"))
299
session_type = DesktopFileInfo.EnvironmentType.GNOME;
300
session_type_str = "GNOME";
302
else if (session.has_prefix ("lx"))
304
session_type = DesktopFileInfo.EnvironmentType.LXDE;
305
session_type_str = "LXDE";
307
else if (session.has_prefix ("xfce"))
309
session_type = DesktopFileInfo.EnvironmentType.XFCE;
310
session_type_str = "XFCE";
312
else if (session.has_prefix ("mate"))
314
session_type = DesktopFileInfo.EnvironmentType.MATE;
315
session_type_str = "MATE";
317
else if (session.has_prefix ("razor"))
319
session_type = DesktopFileInfo.EnvironmentType.RAZOR;
320
session_type_str = "Razor";
322
else if (session.has_prefix ("tde"))
324
session_type = DesktopFileInfo.EnvironmentType.TDE;
325
session_type_str = "TDE";
327
else if (session.has_prefix ("rox"))
329
session_type = DesktopFileInfo.EnvironmentType.ROX;
330
session_type_str = "ROX";
332
else if (session.has_prefix ("pantheon"))
334
session_type = DesktopFileInfo.EnvironmentType.PANTHEON;
335
session_type_str = "Pantheon";
339
warning ("Desktop session type is not recognized, assuming GNOME.");
343
private async void process_directory (File directory,
345
Gee.Set<File> monitored_dirs)
349
string path = directory.get_path ();
350
// we need to skip menu-xdg directory, see lp:686624
351
if (path != null && path.has_suffix ("menu-xdg")) return;
352
// screensavers don't interest us, skip those
353
if (path != null && path.has_suffix ("/screensavers")) return;
355
Utils.Logger.debug (this, "Searching for desktop files in: %s", path);
356
bool exists = yield Utils.query_exists_async (directory);
358
/* Check if we already scanned this directory // lp:686624 */
359
foreach (var scanned_dir in monitored_dirs)
361
if (path == scanned_dir.get_path ()) return;
363
monitored_dirs.add (directory);
364
var enumerator = yield directory.enumerate_children_async (
365
FileAttribute.STANDARD_NAME + "," + FileAttribute.STANDARD_TYPE,
367
var files = yield enumerator.next_files_async (1024, 0);
368
foreach (var f in files)
370
unowned string name = f.get_name ();
371
if (f.get_file_type () == FileType.DIRECTORY)
373
// FIXME: this could cause too many open files error, or?
374
var subdir = directory.get_child (name);
375
var new_prefix = "%s%s-".printf (id_prefix, subdir.get_basename ());
376
yield process_directory (subdir, new_prefix, monitored_dirs);
381
if (name.has_suffix ("synapse.desktop")) continue;
382
if (name.has_suffix (".desktop"))
384
yield load_desktop_file (directory.get_child (name), id_prefix);
391
warning ("%s", err.message);
395
private async void load_all_desktop_files ()
397
string[] data_dirs = Environment.get_system_data_dirs ();
398
data_dirs += Environment.get_user_data_dir ();
400
Gee.Set<File> desktop_file_dirs = new Gee.HashSet<File> ();
402
mimetype_parent_map.clear ();
404
foreach (unowned string data_dir in data_dirs)
406
string dir_path = Path.build_filename (data_dir, "applications", null);
407
var directory = File.new_for_path (dir_path);
408
yield process_directory (directory, "", desktop_file_dirs);
409
dir_path = Path.build_filename (data_dir, "mime", "subclasses");
410
yield load_mime_parents_from_file (dir_path);
415
directory_monitors = new Gee.ArrayList<FileMonitor> ();
416
foreach (File d in desktop_file_dirs)
420
FileMonitor monitor = d.monitor_directory (0, null);
421
monitor.changed.connect (this.desktop_file_directory_changed);
422
directory_monitors.add (monitor);
426
warning ("Unable to monitor directory: %s", err.message);
431
private uint timer_id = 0;
433
public signal void reload_started ();
434
public signal void reload_done ();
436
private void desktop_file_directory_changed ()
441
Source.remove (timer_id);
444
timer_id = Timeout.add (5000, () =>
447
reload_desktop_files.begin ();
452
private async void reload_desktop_files ()
454
debug ("Reloading desktop files...");
455
all_desktop_files.clear ();
456
non_hidden_desktop_files.clear ();
457
yield load_all_desktop_files ();
462
private async void load_desktop_file (File file, string id_prefix)
466
uint8[] file_contents;
467
bool success = yield file.load_contents_async (null, out file_contents,
471
var keyfile = new KeyFile ();
472
keyfile.load_from_data ((string) file_contents,
473
file_contents.length, 0);
475
var desktop_id = "%s%s".printf (id_prefix, file.get_basename ());
476
var dfi = new DesktopFileInfo.for_keyfile (file.get_path (),
481
all_desktop_files.add (dfi);
482
if (!dfi.is_hidden && session_type in dfi.show_in)
484
non_hidden_desktop_files.add (dfi);
491
warning ("%s", err.message);
495
private void create_indices ()
497
// create mimetype maps
499
new Gee.HashMap<unowned string, Gee.List<DesktopFileInfo> > ();
502
new Gee.HashMap<string, Gee.List<DesktopFileInfo> > ();
503
// and desktop id map
505
new Gee.HashMap<string, DesktopFileInfo> ();
510
exec_re = new Regex ("%[fFuU]");
514
critical ("%s", err.message);
518
foreach (var dfi in all_desktop_files)
523
exec = exec_re.replace_literal (dfi.exec, -1, 0, "");
525
catch (RegexError err)
527
Utils.Logger.error (this, "%s", err.message);
529
exec = exec.strip ();
531
Gee.List<DesktopFileInfo>? exec_list = exec_map[exec];
532
if (exec_list == null)
534
exec_list = new Gee.ArrayList<DesktopFileInfo> ();
535
exec_map[exec] = exec_list;
539
// update desktop id map
540
var desktop_id = dfi.desktop_id ?? Path.get_basename (dfi.filename);
541
desktop_id_map[desktop_id] = dfi;
543
// update mimetype map
544
if (dfi.is_hidden || dfi.mime_types == null) continue;
546
foreach (unowned string mime_type in dfi.mime_types)
548
Gee.List<DesktopFileInfo>? list = mimetype_map[mime_type];
551
list = new Gee.ArrayList<DesktopFileInfo> ();
552
mimetype_map[mime_type] = list;
559
private async void load_mime_parents_from_file (string fi)
561
var file = File.new_for_path (fi);
562
bool exists = yield Utils.query_exists_async (file);
566
var fis = yield file.read_async (GLib.Priority.DEFAULT);
567
var dis = new DataInputStream (fis);
569
string[] mimes = null;
571
// Read lines until end of file (null) is reached
573
line = yield dis.read_line_async (GLib.Priority.DEFAULT);
574
if (line == null) break;
575
if (line.has_prefix ("#")) continue; //comment line
576
mimes = line.split (" ");
577
len = (int)GLib.strv_length (mimes);
578
if (len != 2) continue;
579
// cannot be parent of myself!
580
if (mimes[0] == mimes[1]) continue;
581
//debug ("Map %s -> %s", mimes[0], mimes[1]);
582
mimetype_parent_map.set (mimes[0], mimes[1]);
584
} catch (GLib.Error err) { /* can't read file */ }
587
private void add_dfi_for_mime (string mime, Gee.Set<DesktopFileInfo> ret)
589
var dfis = mimetype_map[mime];
590
if (dfis != null) ret.add_all (dfis);
592
var parents = mimetype_parent_map[mime];
593
if (parents == null) return;
594
foreach (string parent in parents)
595
add_dfi_for_mime (parent, ret);
598
// retuns desktop files available on the system (without hidden ones)
599
public Gee.List<DesktopFileInfo> get_desktop_files ()
601
return non_hidden_desktop_files.read_only_view;
604
// returns all desktop files available on the system (even the ones which
605
// are hidden by default)
606
public Gee.List<DesktopFileInfo> get_all_desktop_files ()
608
return all_desktop_files.read_only_view;
611
public Gee.List<DesktopFileInfo> get_desktop_files_for_type (string mime_type)
613
var dfi_set = new Gee.HashSet<DesktopFileInfo> ();
614
add_dfi_for_mime (mime_type, dfi_set);
615
var ret = new Gee.ArrayList<DesktopFileInfo> ();
616
ret.add_all (dfi_set);
620
public Gee.List<DesktopFileInfo> get_desktop_files_for_exec (string exec)
622
return exec_map[exec] ?? new Gee.ArrayList<DesktopFileInfo> ();
625
public DesktopFileInfo? get_desktop_file_for_id (string desktop_id)
627
return desktop_id_map[desktop_id];
2
* Copyright (c) 2010 Michal Hruby <michal.mhr@gmail.com>
5
* This program is free software; you can redistribute it and/or
6
* modify it under the terms of the GNU General Public
7
* License as published by the Free Software Foundation; either
8
* version 2 of the License, or (at your option) any later version.
10
* This program is distributed in the hope that it will be useful,
11
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
* General Public License for more details.
15
* You should have received a copy of the GNU General Public
16
* License along with this program; if not, write to the
17
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18
* Boston, MA 02110-1301 USA
20
* Authored by: Michal Hruby <michal.mhr@gmail.com>
24
errordomain DesktopFileError {
28
public class DesktopFileInfo: Object {
29
// registered environments from http://standards.freedesktop.org/menu-spec/latest
32
public enum EnvironmentType {
48
public string desktop_id { get; construct set; }
49
public string name { get; construct set; }
50
public string generic_name { get; construct set; }
51
public string comment { get; set; default = ""; }
52
public string icon_name { get; construct set; default = ""; }
53
public string gettext_domain { get; construct set; }
55
public bool needs_terminal { get; set; default = false; }
56
public string filename { get; construct set; }
58
public string exec { get; set; }
60
public bool is_hidden { get; private set; default = false; }
61
public bool is_valid { get; private set; default = true; }
63
public string[] mime_types = null;
65
private string? name_folded = null;
66
public unowned string get_name_folded () {
67
if (name_folded == null) {
68
name_folded = name.casefold ();
74
public EnvironmentType show_in { get; set; default = EnvironmentType.ALL; }
76
private const string[] SUPPORTED_GETTEXT_DOMAINS_KEYS = {"X-Ubuntu-Gettext-Domain", "X-GNOME-Gettext-Domain"};
77
private const string GROUP = "Desktop Entry";
79
public DesktopFileInfo.for_keyfile (string path, KeyFile keyfile, string desktop_id) {
80
Object (filename: path, desktop_id: desktop_id);
82
init_from_keyfile (keyfile);
85
private EnvironmentType parse_environments (string[] environments) {
86
EnvironmentType result = 0;
87
foreach (unowned string env in environments) {
88
string env_up = env.up ();
91
result |= EnvironmentType.GNOME;
94
result |= EnvironmentType.PANTHEON;
97
result |= EnvironmentType.KDE;
100
result |= EnvironmentType.LXDE;
103
result |= EnvironmentType.MATE;
106
result |= EnvironmentType.RAZOR;
108
case "ROX": result |= EnvironmentType.ROX; break;
109
case "TDE": result |= EnvironmentType.TDE; break;
110
case "UNITY": result |= EnvironmentType.UNITY; break;
111
case "XFCE": result |= EnvironmentType.XFCE; break;
112
case "OLD": result |= EnvironmentType.OLD; break;
113
default: warning ("%s is not understood", env); break;
119
private void init_from_keyfile (KeyFile keyfile) {
121
if (keyfile.get_string (GROUP, "Type") != "Application") {
122
throw new DesktopFileError.UNINTERESTING_ENTRY ("Not Application-type desktop entry");
125
if (keyfile.has_key (GROUP, "Categories")) {
126
string[] categories = keyfile.get_string_list (GROUP, "Categories");
127
if ("Screensaver" in categories) {
128
throw new DesktopFileError.UNINTERESTING_ENTRY ("Screensaver desktop entry");
131
foreach (var domain_key in SUPPORTED_GETTEXT_DOMAINS_KEYS) {
132
if (keyfile.has_key (GROUP, domain_key)) {
133
gettext_domain = keyfile.get_string (GROUP, domain_key);
138
DesktopAppInfo app_info;
139
app_info = new DesktopAppInfo.from_keyfile (keyfile);
141
if (app_info == null) {
142
throw new DesktopFileError.UNINTERESTING_ENTRY ("Unable to create AppInfo");
145
name = app_info.get_name ();
146
generic_name = app_info.get_generic_name () ?? "";
147
exec = app_info.get_commandline ();
149
throw new DesktopFileError.UNINTERESTING_ENTRY ("Unable to get exec for %s".printf (name));
152
// check for hidden desktop files
153
if (keyfile.has_key (GROUP, "Hidden") && keyfile.get_boolean (GROUP, "Hidden")) {
156
if (keyfile.has_key (GROUP, "NoDisplay") && keyfile.get_boolean (GROUP, "NoDisplay")) {
160
comment = app_info.get_description () ?? "";
162
var icon = app_info.get_icon () ??
163
new ThemedIcon ("application-default-icon");
164
icon_name = icon.to_string ();
166
if (keyfile.has_key (GROUP, "MimeType")) {
167
mime_types = keyfile.get_string_list (GROUP, "MimeType");
169
if (keyfile.has_key (GROUP, "Terminal")) {
170
needs_terminal = keyfile.get_boolean (GROUP, "Terminal");
172
if (keyfile.has_key (GROUP, "OnlyShowIn")) {
173
show_in = parse_environments (keyfile.get_string_list (GROUP, "OnlyShowIn"));
174
} else if (keyfile.has_key (GROUP, "NotShowIn")) {
175
var not_show = parse_environments (keyfile.get_string_list (GROUP, "NotShowIn"));
176
show_in = EnvironmentType.ALL ^ not_show;
179
// special case these, people are using them quite often and wonder
180
// why they don't appear
181
if (filename.has_suffix ("gconf-editor.desktop") || filename.has_suffix ("dconf-editor.desktop")) {
184
} catch (Error err) {
185
Utils.Logger.warning (this, "%s", err.message);
191
public class DesktopFileService : Object {
192
private static unowned DesktopFileService? instance;
193
private Utils.AsyncOnce<bool> init_once;
195
// singleton that can be easily destroyed
196
public static DesktopFileService get_default () {
197
return instance ?? new DesktopFileService ();
200
private DesktopFileService () { }
202
private Gee.List<FileMonitor> directory_monitors;
203
private Gee.List<DesktopFileInfo> all_desktop_files;
204
private Gee.List<DesktopFileInfo> non_hidden_desktop_files;
205
private Gee.Map<unowned string, Gee.List<DesktopFileInfo> > mimetype_map;
206
private Gee.Map<string, Gee.List<DesktopFileInfo> > exec_map;
207
private Gee.Map<string, DesktopFileInfo> desktop_id_map;
208
private Gee.MultiMap<string, string> mimetype_parent_map;
213
directory_monitors = new Gee.ArrayList<FileMonitor> ();
214
all_desktop_files = new Gee.ArrayList<DesktopFileInfo> ();
215
non_hidden_desktop_files = new Gee.ArrayList<DesktopFileInfo> ();
216
mimetype_parent_map = new Gee.HashMultiMap<string, string> ();
217
init_once = new Utils.AsyncOnce<bool> ();
222
~DesktopFileService () {
226
public async void initialize () {
227
if (init_once.is_initialized ()) {
230
var is_locked = yield init_once.enter ();
235
get_environment_type ();
236
DesktopAppInfo.set_desktop_env (session_type_str);
238
Idle.add_full (Priority.LOW, initialize.callback);
240
yield load_all_desktop_files ();
242
init_once.leave (true);
245
private DesktopFileInfo.EnvironmentType session_type = DesktopFileInfo.EnvironmentType.GNOME;
246
private string session_type_str = "GNOME";
248
public DesktopFileInfo.EnvironmentType get_environment () {
249
return this.session_type;
252
private void get_environment_type () {
253
unowned string? session_var;
254
session_var = Environment.get_variable ("XDG_CURRENT_DESKTOP");
255
if (session_var == null) {
256
session_var = Environment.get_variable ("DESKTOP_SESSION");
259
if (session_var == null) {
263
string session = session_var.down ();
265
if (session.has_prefix ("unity") || session.has_prefix ("ubuntu")) {
266
session_type = DesktopFileInfo.EnvironmentType.UNITY;
267
session_type_str = "Unity";
268
} else if (session.has_prefix ("kde")) {
269
session_type = DesktopFileInfo.EnvironmentType.KDE;
270
session_type_str = "KDE";
271
} else if (session.has_prefix ("gnome")) {
272
session_type = DesktopFileInfo.EnvironmentType.GNOME;
273
session_type_str = "GNOME";
274
} else if (session.has_prefix ("lx")) {
275
session_type = DesktopFileInfo.EnvironmentType.LXDE;
276
session_type_str = "LXDE";
277
} else if (session.has_prefix ("xfce")) {
278
session_type = DesktopFileInfo.EnvironmentType.XFCE;
279
session_type_str = "XFCE";
280
} else if (session.has_prefix ("mate")) {
281
session_type = DesktopFileInfo.EnvironmentType.MATE;
282
session_type_str = "MATE";
283
} else if (session.has_prefix ("razor")) {
284
session_type = DesktopFileInfo.EnvironmentType.RAZOR;
285
session_type_str = "Razor";
286
} else if (session.has_prefix ("tde")) {
287
session_type = DesktopFileInfo.EnvironmentType.TDE;
288
session_type_str = "TDE";
289
} else if (session.has_prefix ("rox")) {
290
session_type = DesktopFileInfo.EnvironmentType.ROX;
291
session_type_str = "ROX";
292
} else if (session.has_prefix ("pantheon")) {
293
session_type = DesktopFileInfo.EnvironmentType.PANTHEON;
294
session_type_str = "Pantheon";
296
warning ("Desktop session type is not recognized, assuming GNOME.");
300
private async void process_directory (File directory, string id_prefix, Gee.Set<File> monitored_dirs) {
302
string path = directory.get_path ();
303
// we need to skip menu-xdg directory, see lp:686624
304
if (path != null && path.has_suffix ("menu-xdg")) {
307
// screensavers don't interest us, skip those
308
if (path != null && path.has_suffix ("/screensavers")) {
312
Utils.Logger.debug (this, "Searching for desktop files in: %s", path);
313
bool exists = yield Utils.query_exists_async (directory);
317
/* Check if we already scanned this directory // lp:686624 */
318
foreach (var scanned_dir in monitored_dirs) {
319
if (path == scanned_dir.get_path ()) {
323
monitored_dirs.add (directory);
324
var enumerator = yield directory.enumerate_children_async (FileAttribute.STANDARD_NAME + "," + FileAttribute.STANDARD_TYPE, 0, 0);
325
var files = yield enumerator.next_files_async (1024, 0);
326
foreach (var f in files) {
327
unowned string name = f.get_name ();
328
if (f.get_file_type () == FileType.DIRECTORY) {
329
// FIXME: this could cause too many open files error, or?
330
var subdir = directory.get_child (name);
331
var new_prefix = "%s%s-".printf (id_prefix, subdir.get_basename ());
332
yield process_directory (subdir, new_prefix, monitored_dirs);
335
if (name.has_suffix ("synapse.desktop")) {
338
if (name.has_suffix (".desktop")) {
339
yield load_desktop_file (directory.get_child (name), id_prefix);
343
} catch (Error err) {
344
warning ("%s", err.message);
348
private async void load_all_desktop_files () {
349
string[] data_dirs = Environment.get_system_data_dirs ();
350
data_dirs += Environment.get_user_data_dir ();
352
Gee.Set<File> desktop_file_dirs = new Gee.HashSet<File> ();
353
mimetype_parent_map.clear ();
355
foreach (unowned string data_dir in data_dirs) {
356
string dir_path = Path.build_filename (data_dir, "applications", null);
357
var directory = File.new_for_path (dir_path);
358
yield process_directory (directory, "", desktop_file_dirs);
359
dir_path = Path.build_filename (data_dir, "mime", "subclasses");
360
yield load_mime_parents_from_file (dir_path);
365
directory_monitors = new Gee.ArrayList<FileMonitor> ();
366
foreach (File d in desktop_file_dirs) {
368
FileMonitor monitor = d.monitor_directory (0, null);
369
monitor.changed.connect (this.desktop_file_directory_changed);
370
directory_monitors.add (monitor);
371
} catch (Error err) {
372
warning ("Unable to monitor directory: %s", err.message);
377
private uint timer_id = 0;
379
public signal void reload_started ();
380
public signal void reload_done ();
382
private void desktop_file_directory_changed () {
385
Source.remove (timer_id);
388
timer_id = Timeout.add (5000, () => {
390
reload_desktop_files.begin ();
395
private async void reload_desktop_files () {
396
debug ("Reloading desktop files...");
397
all_desktop_files.clear ();
398
non_hidden_desktop_files.clear ();
399
yield load_all_desktop_files ();
404
private async void load_desktop_file (File file, string id_prefix) {
406
uint8[] file_contents;
407
bool success = yield file.load_contents_async (null, out file_contents, null);
409
var keyfile = new KeyFile ();
410
keyfile.load_from_data ((string) file_contents,
411
file_contents.length, 0);
413
var desktop_id = "%s%s".printf (id_prefix, file.get_basename ());
414
var dfi = new DesktopFileInfo.for_keyfile (file.get_path (), keyfile, desktop_id);
416
all_desktop_files.add (dfi);
417
if (!dfi.is_hidden && session_type in dfi.show_in) {
418
non_hidden_desktop_files.add (dfi);
422
} catch (Error err) {
423
warning ("%s", err.message);
427
private void create_indices () {
428
// create mimetype maps
429
mimetype_map = new Gee.HashMap<unowned string, Gee.List<DesktopFileInfo> > ();
431
exec_map = new Gee.HashMap<string, Gee.List<DesktopFileInfo> > ();
432
// and desktop id map
433
desktop_id_map = new Gee.HashMap<string, DesktopFileInfo> ();
437
exec_re = new Regex ("%[fFuU]");
438
} catch (Error err) {
439
critical ("%s", err.message);
443
foreach (var dfi in all_desktop_files) {
446
exec = exec_re.replace_literal (dfi.exec, -1, 0, "");
447
} catch (RegexError err) {
448
Utils.Logger.error (this, "%s", err.message);
450
exec = exec.strip ();
452
Gee.List<DesktopFileInfo>? exec_list = exec_map[exec];
453
if (exec_list == null) {
454
exec_list = new Gee.ArrayList<DesktopFileInfo> ();
455
exec_map[exec] = exec_list;
459
// update desktop id map
460
var desktop_id = dfi.desktop_id ?? Path.get_basename (dfi.filename);
461
desktop_id_map[desktop_id] = dfi;
463
// update mimetype map
464
if (dfi.is_hidden || dfi.mime_types == null) {
468
foreach (unowned string mime_type in dfi.mime_types) {
469
Gee.List<DesktopFileInfo>? list = mimetype_map[mime_type];
471
list = new Gee.ArrayList<DesktopFileInfo> ();
472
mimetype_map[mime_type] = list;
479
private async void load_mime_parents_from_file (string fi) {
480
var file = File.new_for_path (fi);
481
bool exists = yield Utils.query_exists_async (file);
487
var fis = yield file.read_async (GLib.Priority.DEFAULT);
488
var dis = new DataInputStream (fis);
490
string[] mimes = null;
492
// Read lines until end of file (null) is reached
494
line = yield dis.read_line_async (GLib.Priority.DEFAULT);
498
if (line.has_prefix ("#")) {
499
continue; //comment line
501
mimes = line.split (" ");
502
len = (int)GLib.strv_length (mimes);
506
// cannot be parent of myself!
507
if (mimes[0] == mimes[1]) {
510
//debug ("Map %s -> %s", mimes[0], mimes[1]);
511
mimetype_parent_map.set (mimes[0], mimes[1]);
513
} catch (GLib.Error err) {
514
warning ("Can't read file.");
518
private void add_dfi_for_mime (string mime, Gee.Set<DesktopFileInfo> ret) {
519
var dfis = mimetype_map[mime];
524
var parents = mimetype_parent_map[mime];
525
if (parents == null) {
528
foreach (string parent in parents) {
529
add_dfi_for_mime (parent, ret);
533
// retuns desktop files available on the system (without hidden ones)
534
public Gee.List<DesktopFileInfo> get_desktop_files () {
535
return non_hidden_desktop_files.read_only_view;
538
// returns all desktop files available on the system (even the ones which
539
// are hidden by default)
540
public Gee.List<DesktopFileInfo> get_all_desktop_files () {
541
return all_desktop_files.read_only_view;
544
public Gee.List<DesktopFileInfo> get_desktop_files_for_type (string mime_type) {
545
var dfi_set = new Gee.HashSet<DesktopFileInfo> ();
546
add_dfi_for_mime (mime_type, dfi_set);
547
var ret = new Gee.ArrayList<DesktopFileInfo> ();
548
ret.add_all (dfi_set);
552
public Gee.List<DesktopFileInfo> get_desktop_files_for_exec (string exec) {
553
return exec_map[exec] ?? new Gee.ArrayList<DesktopFileInfo> ();
556
public DesktopFileInfo? get_desktop_file_for_id (string desktop_id) {
557
return desktop_id_map[desktop_id];