nexmon – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 office 1 /* GIO - GLib Input, Output and Streaming Library
2 *
3 * Copyright (C) 2006-2007 Red Hat, Inc.
4 * Copyright © 2007 Ryan Lortie
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General
17 * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
18 *
19 * Author: Alexander Larsson <alexl@redhat.com>
20 * Ryan Lortie <desrt@desrt.ca>
21 */
22  
23 /* Prelude {{{1 */
24  
25 #include "config.h"
26  
27 #include <errno.h>
28 #include <string.h>
29 #include <unistd.h>
30  
31 #ifdef HAVE_CRT_EXTERNS_H
32 #include <crt_externs.h>
33 #endif
34  
35 #include "gcontenttypeprivate.h"
36 #include "gdesktopappinfo.h"
37 #ifdef G_OS_UNIX
38 #include "glib-unix.h"
39 #endif
40 #include "gfile.h"
41 #include "gioerror.h"
42 #include "gthemedicon.h"
43 #include "gfileicon.h"
44 #include <glib/gstdio.h>
45 #include "glibintl.h"
46 #include "giomodule-priv.h"
47 #include "gappinfo.h"
48 #include "gappinfoprivate.h"
49 #include "glocalfilemonitor.h"
50  
51 /**
52 * SECTION:gdesktopappinfo
53 * @title: GDesktopAppInfo
54 * @short_description: Application information from desktop files
55 * @include: gio/gdesktopappinfo.h
56 *
57 * #GDesktopAppInfo is an implementation of #GAppInfo based on
58 * desktop files.
59 *
60 * Note that `<gio/gdesktopappinfo.h>` belongs to the UNIX-specific
61 * GIO interfaces, thus you have to use the `gio-unix-2.0.pc` pkg-config
62 * file when using it.
63 */
64  
65 #define DEFAULT_APPLICATIONS_GROUP "Default Applications"
66 #define ADDED_ASSOCIATIONS_GROUP "Added Associations"
67 #define REMOVED_ASSOCIATIONS_GROUP "Removed Associations"
68 #define MIME_CACHE_GROUP "MIME Cache"
69 #define GENERIC_NAME_KEY "GenericName"
70 #define FULL_NAME_KEY "X-GNOME-FullName"
71 #define KEYWORDS_KEY "Keywords"
72 #define STARTUP_WM_CLASS_KEY "StartupWMClass"
73  
74 enum {
75 PROP_0,
76 PROP_FILENAME
77 };
78  
79 static void g_desktop_app_info_iface_init (GAppInfoIface *iface);
80 static gboolean g_desktop_app_info_ensure_saved (GDesktopAppInfo *info,
81 GError **error);
82  
83 /**
84 * GDesktopAppInfo:
85 *
86 * Information about an installed application from a desktop file.
87 */
88 struct _GDesktopAppInfo
89 {
90 GObject parent_instance;
91  
92 char *desktop_id;
93 char *filename;
94 char *app_id;
95  
96 GKeyFile *keyfile;
97  
98 char *name;
99 char *generic_name;
100 char *fullname;
101 char *comment;
102 char *icon_name;
103 GIcon *icon;
104 char **keywords;
105 char **only_show_in;
106 char **not_show_in;
107 char *try_exec;
108 char *exec;
109 char *binary;
110 char *path;
111 char *categories;
112 char *startup_wm_class;
113 char **mime_types;
114 char **actions;
115  
116 guint nodisplay : 1;
117 guint hidden : 1;
118 guint terminal : 1;
119 guint startup_notify : 1;
120 guint no_fuse : 1;
121 };
122  
123 typedef enum {
124 UPDATE_MIME_NONE = 1 << 0,
125 UPDATE_MIME_SET_DEFAULT = 1 << 1,
126 UPDATE_MIME_SET_NON_DEFAULT = 1 << 2,
127 UPDATE_MIME_REMOVE = 1 << 3,
128 UPDATE_MIME_SET_LAST_USED = 1 << 4,
129 } UpdateMimeFlags;
130  
131 G_DEFINE_TYPE_WITH_CODE (GDesktopAppInfo, g_desktop_app_info, G_TYPE_OBJECT,
132 G_IMPLEMENT_INTERFACE (G_TYPE_APP_INFO, g_desktop_app_info_iface_init))
133  
134 /* DesktopFileDir implementation {{{1 */
135  
136 typedef struct
137 {
138 gchar *path;
139 gchar *alternatively_watching;
140 gboolean is_config;
141 gboolean is_setup;
142 GFileMonitor *monitor;
143 GHashTable *app_names;
144 GHashTable *mime_tweaks;
145 GHashTable *memory_index;
146 GHashTable *memory_implementations;
147 } DesktopFileDir;
148  
149 static DesktopFileDir *desktop_file_dirs;
150 static guint n_desktop_file_dirs;
151 static const guint desktop_file_dir_user_config_index = 0;
152 static guint desktop_file_dir_user_data_index;
153 static GMutex desktop_file_dir_lock;
154  
155 /* Monitor 'changed' signal handler {{{2 */
156 static void desktop_file_dir_reset (DesktopFileDir *dir);
157  
158 /*< internal >
159 * desktop_file_dir_get_alternative_dir:
160 * @dir: a #DesktopFileDir
161 *
162 * Gets the "alternative" directory to monitor in case the path
163 * doesn't exist.
164 *
165 * If the path exists this will return NULL, otherwise it will return a
166 * parent directory of the path.
167 *
168 * This is used to avoid inotify on a non-existent directory (which
169 * results in polling).
170 *
171 * See https://bugzilla.gnome.org/show_bug.cgi?id=522314 for more info.
172 */
173 static gchar *
174 desktop_file_dir_get_alternative_dir (DesktopFileDir *dir)
175 {
176 gchar *parent;
177  
178 /* If the directory itself exists then we need no alternative. */
179 if (g_access (dir->path, R_OK | X_OK) == 0)
180 return NULL;
181  
182 /* Otherwise, try the parent directories until we find one. */
183 parent = g_path_get_dirname (dir->path);
184  
185 while (g_access (parent, R_OK | X_OK) != 0)
186 {
187 gchar *tmp = parent;
188  
189 parent = g_path_get_dirname (tmp);
190  
191 /* If somehow we get to '/' or '.' then just stop... */
192 if (g_str_equal (parent, tmp))
193 {
194 g_free (tmp);
195 break;
196 }
197  
198 g_free (tmp);
199 }
200  
201 return parent;
202 }
203  
204 static void
205 desktop_file_dir_changed (GFileMonitor *monitor,
206 GFile *file,
207 GFile *other_file,
208 GFileMonitorEvent event_type,
209 gpointer user_data)
210 {
211 DesktopFileDir *dir = user_data;
212 gboolean do_nothing = FALSE;
213  
214 /* We are not interested in receiving notifications forever just
215 * because someone asked about one desktop file once.
216 *
217 * After we receive the first notification, reset the dir, destroying
218 * the monitor. We will take this as a hint, next time that we are
219 * asked, that we need to check if everything is up to date.
220 *
221 * If this is a notification for a parent directory (because the
222 * desktop directory didn't exist) then we shouldn't fire the signal
223 * unless something actually changed.
224 */
225 g_mutex_lock (&desktop_file_dir_lock);
226  
227 if (dir->alternatively_watching)
228 {
229 gchar *alternative_dir;
230  
231 alternative_dir = desktop_file_dir_get_alternative_dir (dir);
232 do_nothing = alternative_dir && g_str_equal (dir->alternatively_watching, alternative_dir);
233 g_free (alternative_dir);
234 }
235  
236 if (!do_nothing)
237 desktop_file_dir_reset (dir);
238  
239 g_mutex_unlock (&desktop_file_dir_lock);
240  
241 /* Notify anyone else who may be interested */
242 if (!do_nothing)
243 g_app_info_monitor_fire ();
244 }
245  
246 /* Internal utility functions {{{2 */
247  
248 /*< internal >
249 * desktop_file_dir_app_name_is_masked:
250 * @dir: a #DesktopFileDir
251 * @app_name: an application ID
252 *
253 * Checks if @app_name is masked for @dir.
254 *
255 * An application is masked if a similarly-named desktop file exists in
256 * a desktop file directory with higher precedence. Masked desktop
257 * files should be ignored.
258 */
259 static gboolean
260 desktop_file_dir_app_name_is_masked (DesktopFileDir *dir,
261 const gchar *app_name)
262 {
263 while (dir > desktop_file_dirs)
264 {
265 dir--;
266  
267 if (dir->app_names && g_hash_table_contains (dir->app_names, app_name))
268 return TRUE;
269 }
270  
271 return FALSE;
272 }
273  
274 static const gchar * const *
275 get_lowercase_current_desktops (void)
276 {
277 static gchar **result;
278  
279 if (g_once_init_enter (&result))
280 {
281 const gchar *envvar;
282 gchar **tmp;
283  
284 envvar = g_getenv ("XDG_CURRENT_DESKTOP");
285  
286 if (envvar)
287 {
288 gint i, j;
289  
290 tmp = g_strsplit (envvar, G_SEARCHPATH_SEPARATOR_S, 0);
291  
292 for (i = 0; tmp[i]; i++)
293 for (j = 0; tmp[i][j]; j++)
294 tmp[i][j] = g_ascii_tolower (tmp[i][j]);
295 }
296 else
297 tmp = g_new0 (gchar *, 0 + 1);
298  
299 g_once_init_leave (&result, tmp);
300 }
301  
302 return (const gchar **) result;
303 }
304  
305 static const gchar * const *
306 get_current_desktops (const gchar *value)
307 {
308 static gchar **result;
309  
310 if (g_once_init_enter (&result))
311 {
312 gchar **tmp;
313  
314 if (!value)
315 value = g_getenv ("XDG_CURRENT_DESKTOP");
316  
317 if (!value)
318 value = "";
319  
320 tmp = g_strsplit (value, ":", 0);
321  
322 g_once_init_leave (&result, tmp);
323 }
324  
325 return (const gchar **) result;
326 }
327  
328 /*< internal >
329 * add_to_table_if_appropriate:
330 * @apps: a string to GDesktopAppInfo hash table
331 * @app_name: the name of the application
332 * @info: a #GDesktopAppInfo, or NULL
333 *
334 * If @info is non-%NULL and non-hidden, then add it to @apps, using
335 * @app_name as a key.
336 *
337 * If @info is non-%NULL then this function will consume the passed-in
338 * reference.
339 */
340 static void
341 add_to_table_if_appropriate (GHashTable *apps,
342 const gchar *app_name,
343 GDesktopAppInfo *info)
344 {
345 if (!info)
346 return;
347  
348 if (info->hidden)
349 {
350 g_object_unref (info);
351 return;
352 }
353  
354 g_free (info->desktop_id);
355 info->desktop_id = g_strdup (app_name);
356  
357 g_hash_table_insert (apps, g_strdup (info->desktop_id), info);
358 }
359  
360 enum
361 {
362 DESKTOP_KEY_Comment,
363 DESKTOP_KEY_Exec,
364 DESKTOP_KEY_GenericName,
365 DESKTOP_KEY_Keywords,
366 DESKTOP_KEY_Name,
367 DESKTOP_KEY_X_GNOME_FullName,
368  
369 N_DESKTOP_KEYS
370 };
371  
372 const gchar desktop_key_match_category[N_DESKTOP_KEYS] = {
373 /* Note: lower numbers are a better match.
374 *
375 * In case we want two keys to match at the same level, we can just
376 * use the same number for the two different keys.
377 */
378 [DESKTOP_KEY_Name] = 1,
379 [DESKTOP_KEY_Exec] = 2,
380 [DESKTOP_KEY_Keywords] = 3,
381 [DESKTOP_KEY_GenericName] = 4,
382 [DESKTOP_KEY_X_GNOME_FullName] = 5,
383 [DESKTOP_KEY_Comment] = 6
384 };
385  
386 static gchar *
387 desktop_key_get_name (guint key_id)
388 {
389 switch (key_id)
390 {
391 case DESKTOP_KEY_Comment:
392 return "Comment";
393 case DESKTOP_KEY_Exec:
394 return "Exec";
395 case DESKTOP_KEY_GenericName:
396 return GENERIC_NAME_KEY;
397 case DESKTOP_KEY_Keywords:
398 return KEYWORDS_KEY;
399 case DESKTOP_KEY_Name:
400 return "Name";
401 case DESKTOP_KEY_X_GNOME_FullName:
402 return FULL_NAME_KEY;
403 default:
404 g_assert_not_reached ();
405 }
406 }
407  
408 /* Search global state {{{2
409 *
410 * We only ever search under a global lock, so we can use (and reuse)
411 * some global data to reduce allocations made while searching.
412 *
413 * In short, we keep around arrays of results that we expand as needed
414 * (and never shrink).
415 *
416 * static_token_results: this is where we append the results for each
417 * token within a given desktop directory, as we handle it (which is
418 * a union of all matches for this term)
419 *
420 * static_search_results: this is where we build the complete results
421 * for a single directory (which is an intersection of the matches
422 * found for each term)
423 *
424 * static_total_results: this is where we build the complete results
425 * across all directories (which is a union of the matches found in
426 * each directory)
427 *
428 * The app_names that enter these tables are always pointer-unique (in
429 * the sense that string equality is the same as pointer equality).
430 * This can be guaranteed for two reasons:
431 *
432 * - we mask appids so that a given appid will only ever appear within
433 * the highest-precedence directory that contains it. We never
434 * return search results from a lower-level directory if a desktop
435 * file exists in a higher-level one.
436 *
437 * - within a given directory, the string is unique because it's the
438 * key in the hashtable of all app_ids for that directory.
439 *
440 * We perform a merging of the results in merge_token_results(). This
441 * works by ordering the two lists and moving through each of them (at
442 * the same time) looking for common elements, rejecting uncommon ones.
443 * "Order" here need not mean any particular thing, as long as it is
444 * some order. Because of the uniqueness of our strings, we can use
445 * pointer order. That's what's going on in compare_results() below.
446 */
447 struct search_result
448 {
449 const gchar *app_name;
450 gint category;
451 };
452  
453 static struct search_result *static_token_results;
454 static gint static_token_results_size;
455 static gint static_token_results_allocated;
456 static struct search_result *static_search_results;
457 static gint static_search_results_size;
458 static gint static_search_results_allocated;
459 static struct search_result *static_total_results;
460 static gint static_total_results_size;
461 static gint static_total_results_allocated;
462  
463 /* And some functions for performing nice operations against it */
464 static gint
465 compare_results (gconstpointer a,
466 gconstpointer b)
467 {
468 const struct search_result *ra = a;
469 const struct search_result *rb = b;
470  
471 if (ra->app_name < rb->app_name)
472 return -1;
473  
474 else if (ra->app_name > rb->app_name)
475 return 1;
476  
477 else
478 return ra->category - rb->category;
479 }
480  
481 static gint
482 compare_categories (gconstpointer a,
483 gconstpointer b)
484 {
485 const struct search_result *ra = a;
486 const struct search_result *rb = b;
487  
488 return ra->category - rb->category;
489 }
490  
491 static void
492 add_token_result (const gchar *app_name,
493 guint16 category)
494 {
495 if G_UNLIKELY (static_token_results_size == static_token_results_allocated)
496 {
497 static_token_results_allocated = MAX (16, static_token_results_allocated * 2);
498 static_token_results = g_renew (struct search_result, static_token_results, static_token_results_allocated);
499 }
500  
501 static_token_results[static_token_results_size].app_name = app_name;
502 static_token_results[static_token_results_size].category = category;
503 static_token_results_size++;
504 }
505  
506 static void
507 merge_token_results (gboolean first)
508 {
509 qsort (static_token_results, static_token_results_size, sizeof (struct search_result), compare_results);
510  
511 /* If this is the first token then we are basically merging a list with
512 * itself -- we only perform de-duplication.
513 *
514 * If this is not the first token then we are doing a real merge.
515 */
516 if (first)
517 {
518 const gchar *last_name = NULL;
519 gint i;
520  
521 /* We must de-duplicate, but we do so by taking the best category
522 * in each case.
523 *
524 * The final list can be as large as the input here, so make sure
525 * we have enough room (even if it's too much room).
526 */
527  
528 if G_UNLIKELY (static_search_results_allocated < static_token_results_size)
529 {
530 static_search_results_allocated = static_token_results_allocated;
531 static_search_results = g_renew (struct search_result,
532 static_search_results,
533 static_search_results_allocated);
534 }
535  
536 for (i = 0; i < static_token_results_size; i++)
537 {
538 /* The list is sorted so that the best match for a given id
539 * will be at the front, so once we have copied an id, skip
540 * the rest of the entries for the same id.
541 */
542 if (static_token_results[i].app_name == last_name)
543 continue;
544  
545 last_name = static_token_results[i].app_name;
546  
547 static_search_results[static_search_results_size++] = static_token_results[i];
548 }
549 }
550 else
551 {
552 const gchar *last_name = NULL;
553 gint i, j = 0;
554 gint k = 0;
555  
556 /* We only ever remove items from the results list, so no need to
557 * resize to ensure that we have enough room.
558 */
559 for (i = 0; i < static_token_results_size; i++)
560 {
561 if (static_token_results[i].app_name == last_name)
562 continue;
563  
564 last_name = static_token_results[i].app_name;
565  
566 /* Now we only want to have a result in static_search_results
567 * if we already have it there *and* we have it in
568 * static_token_results as well. The category will be the
569 * lesser of the two.
570 *
571 * Skip past the results in static_search_results that are not
572 * going to be matches.
573 */
574 while (k < static_search_results_size &&
575 static_search_results[k].app_name < static_token_results[i].app_name)
576 k++;
577  
578 if (k < static_search_results_size &&
579 static_search_results[k].app_name == static_token_results[i].app_name)
580 {
581 /* We have a match.
582 *
583 * Category should be the worse of the two (ie:
584 * numerically larger).
585 */
586 static_search_results[j].app_name = static_search_results[k].app_name;
587 static_search_results[j].category = MAX (static_search_results[k].category,
588 static_token_results[i].category);
589 j++;
590 }
591 }
592  
593 static_search_results_size = j;
594 }
595  
596 /* Clear it out for next time... */
597 static_token_results_size = 0;
598 }
599  
600 static void
601 reset_total_search_results (void)
602 {
603 static_total_results_size = 0;
604 }
605  
606 static void
607 sort_total_search_results (void)
608 {
609 qsort (static_total_results, static_total_results_size, sizeof (struct search_result), compare_categories);
610 }
611  
612 static void
613 merge_directory_results (void)
614 {
615 if G_UNLIKELY (static_total_results_size + static_search_results_size > static_total_results_allocated)
616 {
617 static_total_results_allocated = MAX (16, static_total_results_allocated);
618 while (static_total_results_allocated < static_total_results_size + static_search_results_size)
619 static_total_results_allocated *= 2;
620 static_total_results = g_renew (struct search_result, static_total_results, static_total_results_allocated);
621 }
622  
623 memcpy (static_total_results + static_total_results_size,
624 static_search_results,
625 static_search_results_size * sizeof (struct search_result));
626  
627 static_total_results_size += static_search_results_size;
628  
629 /* Clear it out for next time... */
630 static_search_results_size = 0;
631 }
632  
633 /* Support for unindexed DesktopFileDirs {{{2 */
634 static void
635 get_apps_from_dir (GHashTable **apps,
636 const char *dirname,
637 const char *prefix)
638 {
639 const char *basename;
640 GDir *dir;
641  
642 dir = g_dir_open (dirname, 0, NULL);
643  
644 if (dir == NULL)
645 return;
646  
647 while ((basename = g_dir_read_name (dir)) != NULL)
648 {
649 gchar *filename;
650  
651 filename = g_build_filename (dirname, basename, NULL);
652  
653 if (g_str_has_suffix (basename, ".desktop"))
654 {
655 gchar *app_name;
656  
657 app_name = g_strconcat (prefix, basename, NULL);
658  
659 if (*apps == NULL)
660 *apps = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
661  
662 g_hash_table_insert (*apps, app_name, g_strdup (filename));
663 }
664 else if (g_file_test (filename, G_FILE_TEST_IS_DIR))
665 {
666 gchar *subprefix;
667  
668 subprefix = g_strconcat (prefix, basename, "-", NULL);
669 get_apps_from_dir (apps, filename, subprefix);
670 g_free (subprefix);
671 }
672  
673 g_free (filename);
674 }
675  
676 g_dir_close (dir);
677 }
678  
679 typedef struct
680 {
681 gchar **additions;
682 gchar **removals;
683 gchar **defaults;
684 } UnindexedMimeTweaks;
685  
686 static void
687 free_mime_tweaks (gpointer data)
688 {
689 UnindexedMimeTweaks *tweaks = data;
690  
691 g_strfreev (tweaks->additions);
692 g_strfreev (tweaks->removals);
693 g_strfreev (tweaks->defaults);
694  
695 g_slice_free (UnindexedMimeTweaks, tweaks);
696 }
697  
698 static UnindexedMimeTweaks *
699 desktop_file_dir_unindexed_get_tweaks (DesktopFileDir *dir,
700 const gchar *mime_type)
701 {
702 UnindexedMimeTweaks *tweaks;
703 gchar *unaliased_type;
704  
705 unaliased_type = _g_unix_content_type_unalias (mime_type);
706 tweaks = g_hash_table_lookup (dir->mime_tweaks, unaliased_type);
707  
708 if (tweaks == NULL)
709 {
710 tweaks = g_slice_new0 (UnindexedMimeTweaks);
711 g_hash_table_insert (dir->mime_tweaks, unaliased_type, tweaks);
712 }
713 else
714 g_free (unaliased_type);
715  
716 return tweaks;
717 }
718  
719 /* consumes 'to_add' */
720 static void
721 expand_strv (gchar ***strv_ptr,
722 gchar **to_add,
723 gchar * const *blacklist)
724 {
725 guint strv_len, add_len;
726 gchar **strv;
727 guint i, j;
728  
729 if (!*strv_ptr)
730 {
731 *strv_ptr = to_add;
732 return;
733 }
734  
735 strv = *strv_ptr;
736 strv_len = g_strv_length (strv);
737 add_len = g_strv_length (to_add);
738 strv = g_renew (gchar *, strv, strv_len + add_len + 1);
739  
740 for (i = 0; to_add[i]; i++)
741 {
742 /* Don't add blacklisted strings */
743 if (blacklist)
744 for (j = 0; blacklist[j]; j++)
745 if (g_str_equal (to_add[i], blacklist[j]))
746 goto no_add;
747  
748 /* Don't add duplicates already in the list */
749 for (j = 0; j < strv_len; j++)
750 if (g_str_equal (to_add[i], strv[j]))
751 goto no_add;
752  
753 strv[strv_len++] = to_add[i];
754 continue;
755  
756 no_add:
757 g_free (to_add[i]);
758 }
759  
760 strv[strv_len] = NULL;
761 *strv_ptr = strv;
762  
763 g_free (to_add);
764 }
765  
766 static void
767 desktop_file_dir_unindexed_read_mimeapps_list (DesktopFileDir *dir,
768 const gchar *filename,
769 const gchar *added_group,
770 gboolean tweaks_permitted)
771 {
772 UnindexedMimeTweaks *tweaks;
773 char **desktop_file_ids;
774 GKeyFile *key_file;
775 gchar **mime_types;
776 int i;
777  
778 key_file = g_key_file_new ();
779 if (!g_key_file_load_from_file (key_file, filename, G_KEY_FILE_NONE, NULL))
780 {
781 g_key_file_free (key_file);
782 return;
783 }
784  
785 mime_types = g_key_file_get_keys (key_file, added_group, NULL, NULL);
786  
787 if G_UNLIKELY (mime_types != NULL && !tweaks_permitted)
788 {
789 g_warning ("%s contains a [%s] group, but it is not permitted here. Only the non-desktop-specific "
790 "mimeapps.list file may add or remove associations.", filename, added_group);
791 g_strfreev (mime_types);
792 mime_types = NULL;
793 }
794  
795 if (mime_types != NULL)
796 {
797 for (i = 0; mime_types[i] != NULL; i++)
798 {
799 desktop_file_ids = g_key_file_get_string_list (key_file, added_group, mime_types[i], NULL, NULL);
800  
801 if (desktop_file_ids)
802 {
803 tweaks = desktop_file_dir_unindexed_get_tweaks (dir, mime_types[i]);
804 expand_strv (&tweaks->additions, desktop_file_ids, tweaks->removals);
805 }
806 }
807  
808 g_strfreev (mime_types);
809 }
810  
811 mime_types = g_key_file_get_keys (key_file, REMOVED_ASSOCIATIONS_GROUP, NULL, NULL);
812  
813 if G_UNLIKELY (mime_types != NULL && !tweaks_permitted)
814 {
815 g_warning ("%s contains a [%s] group, but it is not permitted here. Only the non-desktop-specific "
816 "mimeapps.list file may add or remove associations.", filename, REMOVED_ASSOCIATIONS_GROUP);
817 g_strfreev (mime_types);
818 mime_types = NULL;
819 }
820  
821 if (mime_types != NULL)
822 {
823 for (i = 0; mime_types[i] != NULL; i++)
824 {
825 desktop_file_ids = g_key_file_get_string_list (key_file, REMOVED_ASSOCIATIONS_GROUP, mime_types[i], NULL, NULL);
826  
827 if (desktop_file_ids)
828 {
829 tweaks = desktop_file_dir_unindexed_get_tweaks (dir, mime_types[i]);
830 expand_strv (&tweaks->removals, desktop_file_ids, tweaks->additions);
831 }
832 }
833  
834 g_strfreev (mime_types);
835 }
836  
837 mime_types = g_key_file_get_keys (key_file, DEFAULT_APPLICATIONS_GROUP, NULL, NULL);
838  
839 if (mime_types != NULL)
840 {
841 for (i = 0; mime_types[i] != NULL; i++)
842 {
843 desktop_file_ids = g_key_file_get_string_list (key_file, DEFAULT_APPLICATIONS_GROUP, mime_types[i], NULL, NULL);
844  
845 if (desktop_file_ids)
846 {
847 tweaks = desktop_file_dir_unindexed_get_tweaks (dir, mime_types[i]);
848 expand_strv (&tweaks->defaults, desktop_file_ids, NULL);
849 }
850 }
851  
852 g_strfreev (mime_types);
853 }
854  
855 g_key_file_free (key_file);
856 }
857  
858 static void
859 desktop_file_dir_unindexed_read_mimeapps_lists (DesktopFileDir *dir)
860 {
861 const gchar * const *desktops;
862 gchar *filename;
863 gint i;
864  
865 dir->mime_tweaks = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, free_mime_tweaks);
866  
867 /* We process in order of precedence, using a blacklisting approach to
868 * avoid recording later instructions that conflict with ones we found
869 * earlier.
870 *
871 * We first start with the XDG_CURRENT_DESKTOP files, in precedence
872 * order.
873 */
874 desktops = get_lowercase_current_desktops ();
875 for (i = 0; desktops[i]; i++)
876 {
877 filename = g_strdup_printf ("%s/%s-mimeapps.list", dir->path, desktops[i]);
878 desktop_file_dir_unindexed_read_mimeapps_list (dir, filename, ADDED_ASSOCIATIONS_GROUP, FALSE);
879 g_free (filename);
880 }
881  
882 /* Next, the non-desktop-specific mimeapps.list */
883 filename = g_strdup_printf ("%s/mimeapps.list", dir->path);
884 desktop_file_dir_unindexed_read_mimeapps_list (dir, filename, ADDED_ASSOCIATIONS_GROUP, TRUE);
885 g_free (filename);
886  
887 /* The remaining files are only checked for in directories that might
888 * contain desktop files (ie: not the config dirs).
889 */
890 if (dir->is_config)
891 return;
892  
893 /* We have 'defaults.list' which was only ever understood by GLib. It
894 * exists widely, but it has never been part of any spec and it should
895 * be treated as deprecated. This will be removed in a future
896 * version.
897 */
898 filename = g_strdup_printf ("%s/defaults.list", dir->path);
899 desktop_file_dir_unindexed_read_mimeapps_list (dir, filename, ADDED_ASSOCIATIONS_GROUP, FALSE);
900 g_free (filename);
901  
902 /* Finally, the mimeinfo.cache, which is just a cached copy of what we
903 * would find in the MimeTypes= lines of all of the desktop files.
904 */
905 filename = g_strdup_printf ("%s/mimeinfo.cache", dir->path);
906 desktop_file_dir_unindexed_read_mimeapps_list (dir, filename, MIME_CACHE_GROUP, TRUE);
907 g_free (filename);
908 }
909  
910 static void
911 desktop_file_dir_unindexed_init (DesktopFileDir *dir)
912 {
913 if (!dir->is_config)
914 get_apps_from_dir (&dir->app_names, dir->path, "");
915  
916 desktop_file_dir_unindexed_read_mimeapps_lists (dir);
917 }
918  
919 static GDesktopAppInfo *
920 desktop_file_dir_unindexed_get_app (DesktopFileDir *dir,
921 const gchar *desktop_id)
922 {
923 const gchar *filename;
924  
925 filename = g_hash_table_lookup (dir->app_names, desktop_id);
926  
927 if (!filename)
928 return NULL;
929  
930 return g_desktop_app_info_new_from_filename (filename);
931 }
932  
933 static void
934 desktop_file_dir_unindexed_get_all (DesktopFileDir *dir,
935 GHashTable *apps)
936 {
937 GHashTableIter iter;
938 gpointer app_name;
939 gpointer filename;
940  
941 if (dir->app_names == NULL)
942 return;
943  
944 g_hash_table_iter_init (&iter, dir->app_names);
945 while (g_hash_table_iter_next (&iter, &app_name, &filename))
946 {
947 if (desktop_file_dir_app_name_is_masked (dir, app_name))
948 continue;
949  
950 add_to_table_if_appropriate (apps, app_name, g_desktop_app_info_new_from_filename (filename));
951 }
952 }
953  
954 typedef struct _MemoryIndexEntry MemoryIndexEntry;
955 typedef GHashTable MemoryIndex;
956  
957 struct _MemoryIndexEntry
958 {
959 const gchar *app_name; /* pointer to the hashtable key */
960 gint match_category;
961 MemoryIndexEntry *next;
962 };
963  
964 static void
965 memory_index_entry_free (gpointer data)
966 {
967 MemoryIndexEntry *mie = data;
968  
969 while (mie)
970 {
971 MemoryIndexEntry *next = mie->next;
972  
973 g_slice_free (MemoryIndexEntry, mie);
974 mie = next;
975 }
976 }
977  
978 static void
979 memory_index_add_token (MemoryIndex *mi,
980 const gchar *token,
981 gint match_category,
982 const gchar *app_name)
983 {
984 MemoryIndexEntry *mie, *first;
985  
986 mie = g_slice_new (MemoryIndexEntry);
987 mie->app_name = app_name;
988 mie->match_category = match_category;
989  
990 first = g_hash_table_lookup (mi, token);
991  
992 if (first)
993 {
994 mie->next = first->next;
995 first->next = mie;
996 }
997 else
998 {
999 mie->next = NULL;
1000 g_hash_table_insert (mi, g_strdup (token), mie);
1001 }
1002 }
1003  
1004 static void
1005 memory_index_add_string (MemoryIndex *mi,
1006 const gchar *string,
1007 gint match_category,
1008 const gchar *app_name)
1009 {
1010 gchar **tokens, **alternates;
1011 gint i;
1012  
1013 tokens = g_str_tokenize_and_fold (string, NULL, &alternates);
1014  
1015 for (i = 0; tokens[i]; i++)
1016 memory_index_add_token (mi, tokens[i], match_category, app_name);
1017  
1018 for (i = 0; alternates[i]; i++)
1019 memory_index_add_token (mi, alternates[i], match_category, app_name);
1020  
1021 g_strfreev (alternates);
1022 g_strfreev (tokens);
1023 }
1024  
1025 static MemoryIndex *
1026 memory_index_new (void)
1027 {
1028 return g_hash_table_new_full (g_str_hash, g_str_equal, g_free, memory_index_entry_free);
1029 }
1030  
1031 static void
1032 desktop_file_dir_unindexed_setup_search (DesktopFileDir *dir)
1033 {
1034 GHashTableIter iter;
1035 gpointer app, path;
1036  
1037 dir->memory_index = memory_index_new ();
1038 dir->memory_implementations = memory_index_new ();
1039  
1040 /* Nothing to search? */
1041 if (dir->app_names == NULL)
1042 return;
1043  
1044 g_hash_table_iter_init (&iter, dir->app_names);
1045 while (g_hash_table_iter_next (&iter, &app, &path))
1046 {
1047 GKeyFile *key_file;
1048  
1049 if (desktop_file_dir_app_name_is_masked (dir, app))
1050 continue;
1051  
1052 key_file = g_key_file_new ();
1053  
1054 if (g_key_file_load_from_file (key_file, path, G_KEY_FILE_NONE, NULL) &&
1055 !g_key_file_get_boolean (key_file, "Desktop Entry", "Hidden", NULL))
1056 {
1057 /* Index the interesting keys... */
1058 gchar **implements;
1059 gint i;
1060  
1061 for (i = 0; i < G_N_ELEMENTS (desktop_key_match_category); i++)
1062 {
1063 const gchar *value;
1064 gchar *raw;
1065  
1066 if (!desktop_key_match_category[i])
1067 continue;
1068  
1069 raw = g_key_file_get_locale_string (key_file, "Desktop Entry", desktop_key_get_name (i), NULL, NULL);
1070 value = raw;
1071  
1072 if (i == DESKTOP_KEY_Exec && raw != NULL)
1073 {
1074 /* Special handling: only match basename of first field */
1075 gchar *space;
1076 gchar *slash;
1077  
1078 /* Remove extra arguments, if any */
1079 space = raw + strcspn (raw, " \t\n"); /* IFS */
1080 *space = '\0';
1081  
1082 /* Skip the pathname, if any */
1083 if ((slash = strrchr (raw, '/')))
1084 value = slash + 1;
1085 }
1086  
1087 if (value)
1088 memory_index_add_string (dir->memory_index, value, desktop_key_match_category[i], app);
1089  
1090 g_free (raw);
1091 }
1092  
1093 /* Make note of the Implements= line */
1094 implements = g_key_file_get_string_list (key_file, "Desktop Entry", "Implements", NULL, NULL);
1095 for (i = 0; implements && implements[i]; i++)
1096 memory_index_add_token (dir->memory_implementations, implements[i], 0, app);
1097 g_strfreev (implements);
1098 }
1099  
1100 g_key_file_free (key_file);
1101 }
1102 }
1103  
1104 static void
1105 desktop_file_dir_unindexed_search (DesktopFileDir *dir,
1106 const gchar *search_token)
1107 {
1108 GHashTableIter iter;
1109 gpointer key, value;
1110  
1111 if (!dir->memory_index)
1112 desktop_file_dir_unindexed_setup_search (dir);
1113  
1114 g_hash_table_iter_init (&iter, dir->memory_index);
1115 while (g_hash_table_iter_next (&iter, &key, &value))
1116 {
1117 MemoryIndexEntry *mie = value;
1118  
1119 if (!g_str_has_prefix (key, search_token))
1120 continue;
1121  
1122 while (mie)
1123 {
1124 add_token_result (mie->app_name, mie->match_category);
1125 mie = mie->next;
1126 }
1127 }
1128 }
1129  
1130 static gboolean
1131 array_contains (GPtrArray *array,
1132 const gchar *str)
1133 {
1134 gint i;
1135  
1136 for (i = 0; i < array->len; i++)
1137 if (g_str_equal (array->pdata[i], str))
1138 return TRUE;
1139  
1140 return FALSE;
1141 }
1142  
1143 static void
1144 desktop_file_dir_unindexed_mime_lookup (DesktopFileDir *dir,
1145 const gchar *mime_type,
1146 GPtrArray *hits,
1147 GPtrArray *blacklist)
1148 {
1149 UnindexedMimeTweaks *tweaks;
1150 gint i;
1151  
1152 tweaks = g_hash_table_lookup (dir->mime_tweaks, mime_type);
1153  
1154 if (!tweaks)
1155 return;
1156  
1157 if (tweaks->additions)
1158 {
1159 for (i = 0; tweaks->additions[i]; i++)
1160 {
1161 gchar *app_name = tweaks->additions[i];
1162  
1163 if (!desktop_file_dir_app_name_is_masked (dir, app_name) &&
1164 !array_contains (blacklist, app_name) && !array_contains (hits, app_name))
1165 g_ptr_array_add (hits, app_name);
1166 }
1167 }
1168  
1169 if (tweaks->removals)
1170 {
1171 for (i = 0; tweaks->removals[i]; i++)
1172 {
1173 gchar *app_name = tweaks->removals[i];
1174  
1175 if (!desktop_file_dir_app_name_is_masked (dir, app_name) &&
1176 !array_contains (blacklist, app_name) && !array_contains (hits, app_name))
1177 g_ptr_array_add (blacklist, app_name);
1178 }
1179 }
1180 }
1181  
1182 static void
1183 desktop_file_dir_unindexed_default_lookup (DesktopFileDir *dir,
1184 const gchar *mime_type,
1185 GPtrArray *results)
1186 {
1187 UnindexedMimeTweaks *tweaks;
1188 gint i;
1189  
1190 tweaks = g_hash_table_lookup (dir->mime_tweaks, mime_type);
1191  
1192 if (!tweaks || !tweaks->defaults)
1193 return;
1194  
1195 for (i = 0; tweaks->defaults[i]; i++)
1196 {
1197 gchar *app_name = tweaks->defaults[i];
1198  
1199 if (!array_contains (results, app_name))
1200 g_ptr_array_add (results, app_name);
1201 }
1202 }
1203  
1204 static void
1205 desktop_file_dir_unindexed_get_implementations (DesktopFileDir *dir,
1206 GList **results,
1207 const gchar *interface)
1208 {
1209 MemoryIndexEntry *mie;
1210  
1211 if (!dir->memory_index)
1212 desktop_file_dir_unindexed_setup_search (dir);
1213  
1214 for (mie = g_hash_table_lookup (dir->memory_implementations, interface); mie; mie = mie->next)
1215 *results = g_list_prepend (*results, g_strdup (mie->app_name));
1216 }
1217  
1218 /* DesktopFileDir "API" {{{2 */
1219  
1220 /*< internal >
1221 * desktop_file_dir_create:
1222 * @array: the #GArray to add a new item to
1223 * @data_dir: an XDG_DATA_DIR
1224 *
1225 * Creates a #DesktopFileDir for the corresponding @data_dir, adding it
1226 * to @array.
1227 */
1228 static void
1229 desktop_file_dir_create (GArray *array,
1230 const gchar *data_dir)
1231 {
1232 DesktopFileDir dir = { 0, };
1233  
1234 dir.path = g_build_filename (data_dir, "applications", NULL);
1235  
1236 g_array_append_val (array, dir);
1237 }
1238  
1239 /*< internal >
1240 * desktop_file_dir_create:
1241 * @array: the #GArray to add a new item to
1242 * @config_dir: an XDG_CONFIG_DIR
1243 *
1244 * Just the same as desktop_file_dir_create() except that it does not
1245 * add the "applications" directory. It also marks the directory as
1246 * config-only, which prevents us from attempting to find desktop files
1247 * here.
1248 */
1249 static void
1250 desktop_file_dir_create_for_config (GArray *array,
1251 const gchar *config_dir)
1252 {
1253 DesktopFileDir dir = { 0, };
1254  
1255 dir.path = g_strdup (config_dir);
1256 dir.is_config = TRUE;
1257  
1258 g_array_append_val (array, dir);
1259 }
1260  
1261 /*< internal >
1262 * desktop_file_dir_reset:
1263 * @dir: a #DesktopFileDir
1264 *
1265 * Cleans up @dir, releasing most resources that it was using.
1266 */
1267 static void
1268 desktop_file_dir_reset (DesktopFileDir *dir)
1269 {
1270 if (dir->alternatively_watching)
1271 {
1272 g_free (dir->alternatively_watching);
1273 dir->alternatively_watching = NULL;
1274 }
1275  
1276 if (dir->monitor)
1277 {
1278 g_signal_handlers_disconnect_by_func (dir->monitor, desktop_file_dir_changed, dir);
1279 g_object_unref (dir->monitor);
1280 dir->monitor = NULL;
1281 }
1282  
1283 if (dir->app_names)
1284 {
1285 g_hash_table_unref (dir->app_names);
1286 dir->app_names = NULL;
1287 }
1288  
1289 if (dir->memory_index)
1290 {
1291 g_hash_table_unref (dir->memory_index);
1292 dir->memory_index = NULL;
1293 }
1294  
1295 if (dir->mime_tweaks)
1296 {
1297 g_hash_table_unref (dir->mime_tweaks);
1298 dir->mime_tweaks = NULL;
1299 }
1300  
1301 if (dir->memory_implementations)
1302 {
1303 g_hash_table_unref (dir->memory_implementations);
1304 dir->memory_implementations = NULL;
1305 }
1306  
1307 dir->is_setup = FALSE;
1308 }
1309  
1310 /*< internal >
1311 * desktop_file_dir_init:
1312 * @dir: a #DesktopFileDir
1313 *
1314 * Does initial setup for @dir
1315 *
1316 * You should only call this if @dir is not already setup.
1317 */
1318 static void
1319 desktop_file_dir_init (DesktopFileDir *dir)
1320 {
1321 const gchar *watch_dir;
1322  
1323 g_assert (!dir->is_setup);
1324  
1325 g_assert (!dir->alternatively_watching);
1326 g_assert (!dir->monitor);
1327  
1328 dir->alternatively_watching = desktop_file_dir_get_alternative_dir (dir);
1329 watch_dir = dir->alternatively_watching ? dir->alternatively_watching : dir->path;
1330  
1331 /* There is a very thin race here if the watch_dir has been _removed_
1332 * between when we checked for it and when we establish the watch.
1333 * Removes probably don't happen in usual operation, and even if it
1334 * does (and we catch the unlikely race), the only degradation is that
1335 * we will fall back to polling.
1336 */
1337 dir->monitor = g_local_file_monitor_new_in_worker (watch_dir, TRUE, G_FILE_MONITOR_NONE,
1338 desktop_file_dir_changed, dir, NULL);
1339  
1340 desktop_file_dir_unindexed_init (dir);
1341  
1342 dir->is_setup = TRUE;
1343 }
1344  
1345 /*< internal >
1346 * desktop_file_dir_get_app:
1347 * @dir: a DesktopFileDir
1348 * @desktop_id: the desktop ID to load
1349 *
1350 * Creates the #GDesktopAppInfo for the given @desktop_id if it exists
1351 * within @dir, even if it is hidden.
1352 *
1353 * This function does not check if @desktop_id would be masked by a
1354 * directory with higher precedence. The caller must do so.
1355 */
1356 static GDesktopAppInfo *
1357 desktop_file_dir_get_app (DesktopFileDir *dir,
1358 const gchar *desktop_id)
1359 {
1360 if (!dir->app_names)
1361 return NULL;
1362  
1363 return desktop_file_dir_unindexed_get_app (dir, desktop_id);
1364 }
1365  
1366 /*< internal >
1367 * desktop_file_dir_get_all:
1368 * @dir: a DesktopFileDir
1369 * @apps: a #GHashTable<string, GDesktopAppInfo>
1370 *
1371 * Loads all desktop files in @dir and adds them to @apps, careful to
1372 * ensure we don't add any files masked by a similarly-named file in a
1373 * higher-precedence directory.
1374 */
1375 static void
1376 desktop_file_dir_get_all (DesktopFileDir *dir,
1377 GHashTable *apps)
1378 {
1379 desktop_file_dir_unindexed_get_all (dir, apps);
1380 }
1381  
1382 /*< internal >
1383 * desktop_file_dir_mime_lookup:
1384 * @dir: a #DesktopFileDir
1385 * @mime_type: the mime type to look up
1386 * @hits: the array to store the hits
1387 * @blacklist: the array to store the blacklist
1388 *
1389 * Does a lookup of a mimetype against one desktop file directory,
1390 * recording any hits and blacklisting and "Removed" associations (so
1391 * later directories don't record them as hits).
1392 *
1393 * The items added to @hits are duplicated, but the ones in @blacklist
1394 * are weak pointers. This facilitates simply freeing the blacklist
1395 * (which is only used for internal bookkeeping) but using the pdata of
1396 * @hits as the result of the operation.
1397 */
1398 static void
1399 desktop_file_dir_mime_lookup (DesktopFileDir *dir,
1400 const gchar *mime_type,
1401 GPtrArray *hits,
1402 GPtrArray *blacklist)
1403 {
1404 desktop_file_dir_unindexed_mime_lookup (dir, mime_type, hits, blacklist);
1405 }
1406  
1407 /*< internal >
1408 * desktop_file_dir_default_lookup:
1409 * @dir: a #DesktopFileDir
1410 * @mime_type: the mime type to look up
1411 * @results: an array to store the results in
1412 *
1413 * Collects the "default" applications for a given mime type from @dir.
1414 */
1415 static void
1416 desktop_file_dir_default_lookup (DesktopFileDir *dir,
1417 const gchar *mime_type,
1418 GPtrArray *results)
1419 {
1420 desktop_file_dir_unindexed_default_lookup (dir, mime_type, results);
1421 }
1422  
1423 /*< internal >
1424 * desktop_file_dir_search:
1425 * @dir: a #DesktopFileDir
1426 * @term: a normalised and casefolded search term
1427 *
1428 * Finds the names of applications in @dir that match @term.
1429 */
1430 static void
1431 desktop_file_dir_search (DesktopFileDir *dir,
1432 const gchar *search_token)
1433 {
1434 desktop_file_dir_unindexed_search (dir, search_token);
1435 }
1436  
1437 static void
1438 desktop_file_dir_get_implementations (DesktopFileDir *dir,
1439 GList **results,
1440 const gchar *interface)
1441 {
1442 desktop_file_dir_unindexed_get_implementations (dir, results, interface);
1443 }
1444  
1445 /* Lock/unlock and global setup API {{{2 */
1446  
1447 static void
1448 desktop_file_dirs_lock (void)
1449 {
1450 gint i;
1451  
1452 g_mutex_lock (&desktop_file_dir_lock);
1453  
1454 if (desktop_file_dirs == NULL)
1455 {
1456 const char * const *dirs;
1457 GArray *tmp;
1458 gint i;
1459  
1460 tmp = g_array_new (FALSE, FALSE, sizeof (DesktopFileDir));
1461  
1462 /* First, the configs. Highest priority: the user's ~/.config */
1463 desktop_file_dir_create_for_config (tmp, g_get_user_config_dir ());
1464  
1465 /* Next, the system configs (/etc/xdg, and so on). */
1466 dirs = g_get_system_config_dirs ();
1467 for (i = 0; dirs[i]; i++)
1468 desktop_file_dir_create_for_config (tmp, dirs[i]);
1469  
1470 /* Now the data. Highest priority: the user's ~/.local/share/applications */
1471 desktop_file_dir_user_data_index = tmp->len;
1472 desktop_file_dir_create (tmp, g_get_user_data_dir ());
1473  
1474 /* Following that, XDG_DATA_DIRS/applications, in order */
1475 dirs = g_get_system_data_dirs ();
1476 for (i = 0; dirs[i]; i++)
1477 desktop_file_dir_create (tmp, dirs[i]);
1478  
1479 /* The list of directories will never change after this. */
1480 desktop_file_dirs = (DesktopFileDir *) tmp->data;
1481 n_desktop_file_dirs = tmp->len;
1482  
1483 g_array_free (tmp, FALSE);
1484 }
1485  
1486 for (i = 0; i < n_desktop_file_dirs; i++)
1487 if (!desktop_file_dirs[i].is_setup)
1488 desktop_file_dir_init (&desktop_file_dirs[i]);
1489 }
1490  
1491 static void
1492 desktop_file_dirs_unlock (void)
1493 {
1494 g_mutex_unlock (&desktop_file_dir_lock);
1495 }
1496  
1497 static void
1498 desktop_file_dirs_invalidate_user_config (void)
1499 {
1500 g_mutex_lock (&desktop_file_dir_lock);
1501  
1502 if (n_desktop_file_dirs)
1503 desktop_file_dir_reset (&desktop_file_dirs[desktop_file_dir_user_config_index]);
1504  
1505 g_mutex_unlock (&desktop_file_dir_lock);
1506 }
1507  
1508 static void
1509 desktop_file_dirs_invalidate_user_data (void)
1510 {
1511 g_mutex_lock (&desktop_file_dir_lock);
1512  
1513 if (n_desktop_file_dirs)
1514 desktop_file_dir_reset (&desktop_file_dirs[desktop_file_dir_user_data_index]);
1515  
1516 g_mutex_unlock (&desktop_file_dir_lock);
1517 }
1518  
1519 /* GDesktopAppInfo implementation {{{1 */
1520 /* GObject implementation {{{2 */
1521 static void
1522 g_desktop_app_info_finalize (GObject *object)
1523 {
1524 GDesktopAppInfo *info;
1525  
1526 info = G_DESKTOP_APP_INFO (object);
1527  
1528 g_free (info->desktop_id);
1529 g_free (info->filename);
1530  
1531 if (info->keyfile)
1532 g_key_file_unref (info->keyfile);
1533  
1534 g_free (info->name);
1535 g_free (info->generic_name);
1536 g_free (info->fullname);
1537 g_free (info->comment);
1538 g_free (info->icon_name);
1539 if (info->icon)
1540 g_object_unref (info->icon);
1541 g_strfreev (info->keywords);
1542 g_strfreev (info->only_show_in);
1543 g_strfreev (info->not_show_in);
1544 g_free (info->try_exec);
1545 g_free (info->exec);
1546 g_free (info->binary);
1547 g_free (info->path);
1548 g_free (info->categories);
1549 g_free (info->startup_wm_class);
1550 g_strfreev (info->mime_types);
1551 g_free (info->app_id);
1552 g_strfreev (info->actions);
1553  
1554 G_OBJECT_CLASS (g_desktop_app_info_parent_class)->finalize (object);
1555 }
1556  
1557 static void
1558 g_desktop_app_info_set_property (GObject *object,
1559 guint prop_id,
1560 const GValue *value,
1561 GParamSpec *pspec)
1562 {
1563 GDesktopAppInfo *self = G_DESKTOP_APP_INFO (object);
1564  
1565 switch (prop_id)
1566 {
1567 case PROP_FILENAME:
1568 self->filename = g_value_dup_string (value);
1569 break;
1570  
1571 default:
1572 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1573 break;
1574 }
1575 }
1576  
1577 static void
1578 g_desktop_app_info_get_property (GObject *object,
1579 guint prop_id,
1580 GValue *value,
1581 GParamSpec *pspec)
1582 {
1583 GDesktopAppInfo *self = G_DESKTOP_APP_INFO (object);
1584  
1585 switch (prop_id)
1586 {
1587 case PROP_FILENAME:
1588 g_value_set_string (value, self->filename);
1589 break;
1590 default:
1591 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1592 break;
1593 }
1594 }
1595  
1596 static void
1597 g_desktop_app_info_class_init (GDesktopAppInfoClass *klass)
1598 {
1599 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
1600  
1601 gobject_class->get_property = g_desktop_app_info_get_property;
1602 gobject_class->set_property = g_desktop_app_info_set_property;
1603 gobject_class->finalize = g_desktop_app_info_finalize;
1604  
1605 /**
1606 * GDesktopAppInfo:filename:
1607 *
1608 * The origin filename of this #GDesktopAppInfo
1609 */
1610 g_object_class_install_property (gobject_class,
1611 PROP_FILENAME,
1612 g_param_spec_string ("filename", "Filename", "", NULL,
1613 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
1614 }
1615  
1616 static void
1617 g_desktop_app_info_init (GDesktopAppInfo *local)
1618 {
1619 }
1620  
1621 /* Construction... {{{2 */
1622  
1623 /*< internal >
1624 * binary_from_exec:
1625 * @exec: an exec line
1626 *
1627 * Returns the first word in an exec line (ie: the binary name).
1628 *
1629 * If @exec is " progname --foo %F" then returns "progname".
1630 */
1631 static char *
1632 binary_from_exec (const char *exec)
1633 {
1634 const char *p, *start;
1635  
1636 p = exec;
1637 while (*p == ' ')
1638 p++;
1639 start = p;
1640 while (*p != ' ' && *p != 0)
1641 p++;
1642  
1643 return g_strndup (start, p - start);
1644 }
1645  
1646 static gboolean
1647 g_desktop_app_info_load_from_keyfile (GDesktopAppInfo *info,
1648 GKeyFile *key_file)
1649 {
1650 char *start_group;
1651 char *type;
1652 char *try_exec;
1653 char *exec;
1654 gboolean bus_activatable;
1655  
1656 start_group = g_key_file_get_start_group (key_file);
1657 if (start_group == NULL || strcmp (start_group, G_KEY_FILE_DESKTOP_GROUP) != 0)
1658 {
1659 g_free (start_group);
1660 return FALSE;
1661 }
1662 g_free (start_group);
1663  
1664 type = g_key_file_get_string (key_file,
1665 G_KEY_FILE_DESKTOP_GROUP,
1666 G_KEY_FILE_DESKTOP_KEY_TYPE,
1667 NULL);
1668 if (type == NULL || strcmp (type, G_KEY_FILE_DESKTOP_TYPE_APPLICATION) != 0)
1669 {
1670 g_free (type);
1671 return FALSE;
1672 }
1673 g_free (type);
1674  
1675 try_exec = g_key_file_get_string (key_file,
1676 G_KEY_FILE_DESKTOP_GROUP,
1677 G_KEY_FILE_DESKTOP_KEY_TRY_EXEC,
1678 NULL);
1679 if (try_exec && try_exec[0] != '\0')
1680 {
1681 char *t;
1682 t = g_find_program_in_path (try_exec);
1683 if (t == NULL)
1684 {
1685 g_free (try_exec);
1686 return FALSE;
1687 }
1688 g_free (t);
1689 }
1690  
1691 exec = g_key_file_get_string (key_file,
1692 G_KEY_FILE_DESKTOP_GROUP,
1693 G_KEY_FILE_DESKTOP_KEY_EXEC,
1694 NULL);
1695 if (exec && exec[0] != '\0')
1696 {
1697 gint argc;
1698 char **argv;
1699 if (!g_shell_parse_argv (exec, &argc, &argv, NULL))
1700 {
1701 g_free (exec);
1702 g_free (try_exec);
1703 return FALSE;
1704 }
1705 else
1706 {
1707 char *t;
1708 t = g_find_program_in_path (argv[0]);
1709 g_strfreev (argv);
1710  
1711 if (t == NULL)
1712 {
1713 g_free (exec);
1714 g_free (try_exec);
1715 return FALSE;
1716 }
1717 g_free (t);
1718 }
1719 }
1720  
1721 info->name = g_key_file_get_locale_string (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_NAME, NULL, NULL);
1722 info->generic_name = g_key_file_get_locale_string (key_file, G_KEY_FILE_DESKTOP_GROUP, GENERIC_NAME_KEY, NULL, NULL);
1723 info->fullname = g_key_file_get_locale_string (key_file, G_KEY_FILE_DESKTOP_GROUP, FULL_NAME_KEY, NULL, NULL);
1724 info->keywords = g_key_file_get_locale_string_list (key_file, G_KEY_FILE_DESKTOP_GROUP, KEYWORDS_KEY, NULL, NULL, NULL);
1725 info->comment = g_key_file_get_locale_string (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_COMMENT, NULL, NULL);
1726 info->nodisplay = g_key_file_get_boolean (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_NO_DISPLAY, NULL) != FALSE;
1727 info->icon_name = g_key_file_get_locale_string (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_ICON, NULL, NULL);
1728 info->only_show_in = g_key_file_get_string_list (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_ONLY_SHOW_IN, NULL, NULL);
1729 info->not_show_in = g_key_file_get_string_list (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_NOT_SHOW_IN, NULL, NULL);
1730 info->try_exec = try_exec;
1731 info->exec = exec;
1732 info->path = g_key_file_get_string (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_PATH, NULL);
1733 info->terminal = g_key_file_get_boolean (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_TERMINAL, NULL) != FALSE;
1734 info->startup_notify = g_key_file_get_boolean (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_STARTUP_NOTIFY, NULL) != FALSE;
1735 info->no_fuse = g_key_file_get_boolean (key_file, G_KEY_FILE_DESKTOP_GROUP, "X-GIO-NoFuse", NULL) != FALSE;
1736 info->hidden = g_key_file_get_boolean (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_HIDDEN, NULL) != FALSE;
1737 info->categories = g_key_file_get_string (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_CATEGORIES, NULL);
1738 info->startup_wm_class = g_key_file_get_string (key_file, G_KEY_FILE_DESKTOP_GROUP, STARTUP_WM_CLASS_KEY, NULL);
1739 info->mime_types = g_key_file_get_string_list (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_MIME_TYPE, NULL, NULL);
1740 bus_activatable = g_key_file_get_boolean (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_DBUS_ACTIVATABLE, NULL);
1741 info->actions = g_key_file_get_string_list (key_file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_ACTIONS, NULL, NULL);
1742  
1743 /* Remove the special-case: no Actions= key just means 0 extra actions */
1744 if (info->actions == NULL)
1745 info->actions = g_new0 (gchar *, 0 + 1);
1746  
1747 info->icon = NULL;
1748 if (info->icon_name)
1749 {
1750 if (g_path_is_absolute (info->icon_name))
1751 {
1752 GFile *file;
1753  
1754 file = g_file_new_for_path (info->icon_name);
1755 info->icon = g_file_icon_new (file);
1756 g_object_unref (file);
1757 }
1758 else
1759 {
1760 char *p;
1761  
1762 /* Work around a common mistake in desktop files */
1763 if ((p = strrchr (info->icon_name, '.')) != NULL &&
1764 (strcmp (p, ".png") == 0 ||
1765 strcmp (p, ".xpm") == 0 ||
1766 strcmp (p, ".svg") == 0))
1767 *p = 0;
1768  
1769 info->icon = g_themed_icon_new (info->icon_name);
1770 }
1771 }
1772  
1773 if (info->exec)
1774 info->binary = binary_from_exec (info->exec);
1775  
1776 if (info->path && info->path[0] == '\0')
1777 {
1778 g_free (info->path);
1779 info->path = NULL;
1780 }
1781  
1782 /* Can only be DBusActivatable if we know the filename, which means
1783 * that this won't work for the load-from-keyfile case.
1784 */
1785 if (bus_activatable && info->filename)
1786 {
1787 gchar *basename;
1788 gchar *last_dot;
1789  
1790 basename = g_path_get_basename (info->filename);
1791 last_dot = strrchr (basename, '.');
1792  
1793 if (last_dot && g_str_equal (last_dot, ".desktop"))
1794 {
1795 *last_dot = '\0';
1796  
1797 if (g_dbus_is_name (basename) && basename[0] != ':')
1798 info->app_id = g_strdup (basename);
1799 }
1800  
1801 g_free (basename);
1802 }
1803  
1804 info->keyfile = g_key_file_ref (key_file);
1805  
1806 return TRUE;
1807 }
1808  
1809 static gboolean
1810 g_desktop_app_info_load_file (GDesktopAppInfo *self)
1811 {
1812 GKeyFile *key_file;
1813 gboolean retval = FALSE;
1814  
1815 g_return_val_if_fail (self->filename != NULL, FALSE);
1816  
1817 self->desktop_id = g_path_get_basename (self->filename);
1818  
1819 key_file = g_key_file_new ();
1820  
1821 if (g_key_file_load_from_file (key_file, self->filename, G_KEY_FILE_NONE, NULL))
1822 retval = g_desktop_app_info_load_from_keyfile (self, key_file);
1823  
1824 g_key_file_unref (key_file);
1825 return retval;
1826 }
1827  
1828 /**
1829 * g_desktop_app_info_new_from_keyfile:
1830 * @key_file: an opened #GKeyFile
1831 *
1832 * Creates a new #GDesktopAppInfo.
1833 *
1834 * Returns: a new #GDesktopAppInfo or %NULL on error.
1835 *
1836 * Since: 2.18
1837 **/
1838 GDesktopAppInfo *
1839 g_desktop_app_info_new_from_keyfile (GKeyFile *key_file)
1840 {
1841 GDesktopAppInfo *info;
1842  
1843 info = g_object_new (G_TYPE_DESKTOP_APP_INFO, NULL);
1844 info->filename = NULL;
1845 if (!g_desktop_app_info_load_from_keyfile (info, key_file))
1846 {
1847 g_object_unref (info);
1848 return NULL;
1849 }
1850 return info;
1851 }
1852  
1853 /**
1854 * g_desktop_app_info_new_from_filename:
1855 * @filename: the path of a desktop file, in the GLib filename encoding
1856 *
1857 * Creates a new #GDesktopAppInfo.
1858 *
1859 * Returns: a new #GDesktopAppInfo or %NULL on error.
1860 **/
1861 GDesktopAppInfo *
1862 g_desktop_app_info_new_from_filename (const char *filename)
1863 {
1864 GDesktopAppInfo *info = NULL;
1865  
1866 info = g_object_new (G_TYPE_DESKTOP_APP_INFO, "filename", filename, NULL);
1867 if (!g_desktop_app_info_load_file (info))
1868 {
1869 g_object_unref (info);
1870 return NULL;
1871 }
1872 return info;
1873 }
1874  
1875 /**
1876 * g_desktop_app_info_new:
1877 * @desktop_id: the desktop file id
1878 *
1879 * Creates a new #GDesktopAppInfo based on a desktop file id.
1880 *
1881 * A desktop file id is the basename of the desktop file, including the
1882 * .desktop extension. GIO is looking for a desktop file with this name
1883 * in the `applications` subdirectories of the XDG
1884 * data directories (i.e. the directories specified in the `XDG_DATA_HOME`
1885 * and `XDG_DATA_DIRS` environment variables). GIO also supports the
1886 * prefix-to-subdirectory mapping that is described in the
1887 * [Menu Spec](http://standards.freedesktop.org/menu-spec/latest/)
1888 * (i.e. a desktop id of kde-foo.desktop will match
1889 * `/usr/share/applications/kde/foo.desktop`).
1890 *
1891 * Returns: a new #GDesktopAppInfo, or %NULL if no desktop file with that id
1892 */
1893 GDesktopAppInfo *
1894 g_desktop_app_info_new (const char *desktop_id)
1895 {
1896 GDesktopAppInfo *appinfo = NULL;
1897 guint i;
1898  
1899 desktop_file_dirs_lock ();
1900  
1901 for (i = 0; i < n_desktop_file_dirs; i++)
1902 {
1903 appinfo = desktop_file_dir_get_app (&desktop_file_dirs[i], desktop_id);
1904  
1905 if (appinfo)
1906 break;
1907 }
1908  
1909 desktop_file_dirs_unlock ();
1910  
1911 if (appinfo == NULL)
1912 return NULL;
1913  
1914 g_free (appinfo->desktop_id);
1915 appinfo->desktop_id = g_strdup (desktop_id);
1916  
1917 if (g_desktop_app_info_get_is_hidden (appinfo))
1918 {
1919 g_object_unref (appinfo);
1920 appinfo = NULL;
1921 }
1922  
1923 return appinfo;
1924 }
1925  
1926 static GAppInfo *
1927 g_desktop_app_info_dup (GAppInfo *appinfo)
1928 {
1929 GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
1930 GDesktopAppInfo *new_info;
1931  
1932 new_info = g_object_new (G_TYPE_DESKTOP_APP_INFO, NULL);
1933  
1934 new_info->filename = g_strdup (info->filename);
1935 new_info->desktop_id = g_strdup (info->desktop_id);
1936  
1937 if (info->keyfile)
1938 new_info->keyfile = g_key_file_ref (info->keyfile);
1939  
1940 new_info->name = g_strdup (info->name);
1941 new_info->generic_name = g_strdup (info->generic_name);
1942 new_info->fullname = g_strdup (info->fullname);
1943 new_info->keywords = g_strdupv (info->keywords);
1944 new_info->comment = g_strdup (info->comment);
1945 new_info->nodisplay = info->nodisplay;
1946 new_info->icon_name = g_strdup (info->icon_name);
1947 if (info->icon)
1948 new_info->icon = g_object_ref (info->icon);
1949 new_info->only_show_in = g_strdupv (info->only_show_in);
1950 new_info->not_show_in = g_strdupv (info->not_show_in);
1951 new_info->try_exec = g_strdup (info->try_exec);
1952 new_info->exec = g_strdup (info->exec);
1953 new_info->binary = g_strdup (info->binary);
1954 new_info->path = g_strdup (info->path);
1955 new_info->app_id = g_strdup (info->app_id);
1956 new_info->hidden = info->hidden;
1957 new_info->terminal = info->terminal;
1958 new_info->startup_notify = info->startup_notify;
1959  
1960 return G_APP_INFO (new_info);
1961 }
1962  
1963 /* GAppInfo interface implementation functions {{{2 */
1964  
1965 static gboolean
1966 g_desktop_app_info_equal (GAppInfo *appinfo1,
1967 GAppInfo *appinfo2)
1968 {
1969 GDesktopAppInfo *info1 = G_DESKTOP_APP_INFO (appinfo1);
1970 GDesktopAppInfo *info2 = G_DESKTOP_APP_INFO (appinfo2);
1971  
1972 if (info1->desktop_id == NULL ||
1973 info2->desktop_id == NULL)
1974 return info1 == info2;
1975  
1976 return strcmp (info1->desktop_id, info2->desktop_id) == 0;
1977 }
1978  
1979 static const char *
1980 g_desktop_app_info_get_id (GAppInfo *appinfo)
1981 {
1982 GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
1983  
1984 return info->desktop_id;
1985 }
1986  
1987 static const char *
1988 g_desktop_app_info_get_name (GAppInfo *appinfo)
1989 {
1990 GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
1991  
1992 if (info->name == NULL)
1993 return _("Unnamed");
1994 return info->name;
1995 }
1996  
1997 static const char *
1998 g_desktop_app_info_get_display_name (GAppInfo *appinfo)
1999 {
2000 GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
2001  
2002 if (info->fullname == NULL)
2003 return g_desktop_app_info_get_name (appinfo);
2004 return info->fullname;
2005 }
2006  
2007 /**
2008 * g_desktop_app_info_get_is_hidden:
2009 * @info: a #GDesktopAppInfo.
2010 *
2011 * A desktop file is hidden if the Hidden key in it is
2012 * set to True.
2013 *
2014 * Returns: %TRUE if hidden, %FALSE otherwise.
2015 **/
2016 gboolean
2017 g_desktop_app_info_get_is_hidden (GDesktopAppInfo *info)
2018 {
2019 return info->hidden;
2020 }
2021  
2022 /**
2023 * g_desktop_app_info_get_filename:
2024 * @info: a #GDesktopAppInfo
2025 *
2026 * When @info was created from a known filename, return it. In some
2027 * situations such as the #GDesktopAppInfo returned from
2028 * g_desktop_app_info_new_from_keyfile(), this function will return %NULL.
2029 *
2030 * Returns: The full path to the file for @info, or %NULL if not known.
2031 * Since: 2.24
2032 */
2033 const char *
2034 g_desktop_app_info_get_filename (GDesktopAppInfo *info)
2035 {
2036 return info->filename;
2037 }
2038  
2039 static const char *
2040 g_desktop_app_info_get_description (GAppInfo *appinfo)
2041 {
2042 GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
2043  
2044 return info->comment;
2045 }
2046  
2047 static const char *
2048 g_desktop_app_info_get_executable (GAppInfo *appinfo)
2049 {
2050 GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
2051  
2052 return info->binary;
2053 }
2054  
2055 static const char *
2056 g_desktop_app_info_get_commandline (GAppInfo *appinfo)
2057 {
2058 GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
2059  
2060 return info->exec;
2061 }
2062  
2063 static GIcon *
2064 g_desktop_app_info_get_icon (GAppInfo *appinfo)
2065 {
2066 GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
2067  
2068 return info->icon;
2069 }
2070  
2071 /**
2072 * g_desktop_app_info_get_categories:
2073 * @info: a #GDesktopAppInfo
2074 *
2075 * Gets the categories from the desktop file.
2076 *
2077 * Returns: The unparsed Categories key from the desktop file;
2078 * i.e. no attempt is made to split it by ';' or validate it.
2079 */
2080 const char *
2081 g_desktop_app_info_get_categories (GDesktopAppInfo *info)
2082 {
2083 return info->categories;
2084 }
2085  
2086 /**
2087 * g_desktop_app_info_get_keywords:
2088 * @info: a #GDesktopAppInfo
2089 *
2090 * Gets the keywords from the desktop file.
2091 *
2092 * Returns: (transfer none): The value of the Keywords key
2093 *
2094 * Since: 2.32
2095 */
2096 const char * const *
2097 g_desktop_app_info_get_keywords (GDesktopAppInfo *info)
2098 {
2099 return (const char * const *)info->keywords;
2100 }
2101  
2102 /**
2103 * g_desktop_app_info_get_generic_name:
2104 * @info: a #GDesktopAppInfo
2105 *
2106 * Gets the generic name from the destkop file.
2107 *
2108 * Returns: The value of the GenericName key
2109 */
2110 const char *
2111 g_desktop_app_info_get_generic_name (GDesktopAppInfo *info)
2112 {
2113 return info->generic_name;
2114 }
2115  
2116 /**
2117 * g_desktop_app_info_get_nodisplay:
2118 * @info: a #GDesktopAppInfo
2119 *
2120 * Gets the value of the NoDisplay key, which helps determine if the
2121 * application info should be shown in menus. See
2122 * #G_KEY_FILE_DESKTOP_KEY_NO_DISPLAY and g_app_info_should_show().
2123 *
2124 * Returns: The value of the NoDisplay key
2125 *
2126 * Since: 2.30
2127 */
2128 gboolean
2129 g_desktop_app_info_get_nodisplay (GDesktopAppInfo *info)
2130 {
2131 return info->nodisplay;
2132 }
2133  
2134 /**
2135 * g_desktop_app_info_get_show_in:
2136 * @info: a #GDesktopAppInfo
2137 * @desktop_env: (nullable): a string specifying a desktop name
2138 *
2139 * Checks if the application info should be shown in menus that list available
2140 * applications for a specific name of the desktop, based on the
2141 * `OnlyShowIn` and `NotShowIn` keys.
2142 *
2143 * @desktop_env should typically be given as %NULL, in which case the
2144 * `XDG_CURRENT_DESKTOP` environment variable is consulted. If you want
2145 * to override the default mechanism then you may specify @desktop_env,
2146 * but this is not recommended.
2147 *
2148 * Note that g_app_info_should_show() for @info will include this check (with
2149 * %NULL for @desktop_env) as well as additional checks.
2150 *
2151 * Returns: %TRUE if the @info should be shown in @desktop_env according to the
2152 * `OnlyShowIn` and `NotShowIn` keys, %FALSE
2153 * otherwise.
2154 *
2155 * Since: 2.30
2156 */
2157 gboolean
2158 g_desktop_app_info_get_show_in (GDesktopAppInfo *info,
2159 const gchar *desktop_env)
2160 {
2161 const gchar *specified_envs[] = { desktop_env, NULL };
2162 const gchar * const *envs;
2163 gint i;
2164  
2165 g_return_val_if_fail (G_IS_DESKTOP_APP_INFO (info), FALSE);
2166  
2167 if (desktop_env)
2168 envs = specified_envs;
2169 else
2170 envs = get_current_desktops (NULL);
2171  
2172 for (i = 0; envs[i]; i++)
2173 {
2174 gint j;
2175  
2176 if (info->only_show_in)
2177 for (j = 0; info->only_show_in[j]; j++)
2178 if (g_str_equal (info->only_show_in[j], envs[i]))
2179 return TRUE;
2180  
2181 if (info->not_show_in)
2182 for (j = 0; info->not_show_in[j]; j++)
2183 if (g_str_equal (info->not_show_in[j], envs[i]))
2184 return FALSE;
2185 }
2186  
2187 return info->only_show_in == NULL;
2188 }
2189  
2190 /* Launching... {{{2 */
2191  
2192 static char *
2193 expand_macro_single (char macro, char *uri)
2194 {
2195 GFile *file;
2196 char *result = NULL;
2197 char *path = NULL;
2198 char *name;
2199  
2200 file = g_file_new_for_uri (uri);
2201  
2202 switch (macro)
2203 {
2204 case 'u':
2205 case 'U':
2206 result = g_shell_quote (uri);
2207 break;
2208 case 'f':
2209 case 'F':
2210 path = g_file_get_path (file);
2211 if (path)
2212 result = g_shell_quote (path);
2213 break;
2214 case 'd':
2215 case 'D':
2216 path = g_file_get_path (file);
2217 if (path)
2218 {
2219 name = g_path_get_dirname (path);
2220 result = g_shell_quote (name);
2221 g_free (name);
2222 }
2223 break;
2224 case 'n':
2225 case 'N':
2226 path = g_file_get_path (file);
2227 if (path)
2228 {
2229 name = g_path_get_basename (path);
2230 result = g_shell_quote (name);
2231 g_free (name);
2232 }
2233 break;
2234 }
2235  
2236 g_object_unref (file);
2237 g_free (path);
2238  
2239 return result;
2240 }
2241  
2242 static void
2243 expand_macro (char macro,
2244 GString *exec,
2245 GDesktopAppInfo *info,
2246 GList **uri_list)
2247 {
2248 GList *uris = *uri_list;
2249 char *expanded;
2250 gboolean force_file_uri;
2251 char force_file_uri_macro;
2252 char *uri;
2253  
2254 g_return_if_fail (exec != NULL);
2255  
2256 /* On %u and %U, pass POSIX file path pointing to the URI via
2257 * the FUSE mount in ~/.gvfs. Note that if the FUSE daemon isn't
2258 * running or the URI doesn't have a POSIX file path via FUSE
2259 * we'll just pass the URI.
2260 */
2261 force_file_uri_macro = macro;
2262 force_file_uri = FALSE;
2263 if (!info->no_fuse)
2264 {
2265 switch (macro)
2266 {
2267 case 'u':
2268 force_file_uri_macro = 'f';
2269 force_file_uri = TRUE;
2270 break;
2271 case 'U':
2272 force_file_uri_macro = 'F';
2273 force_file_uri = TRUE;
2274 break;
2275 default:
2276 break;
2277 }
2278 }
2279  
2280 switch (macro)
2281 {
2282 case 'u':
2283 case 'f':
2284 case 'd':
2285 case 'n':
2286 if (uris)
2287 {
2288 uri = uris->data;
2289 if (!force_file_uri ||
2290 /* Pass URI if it contains an anchor */
2291 strchr (uri, '#') != NULL)
2292 {
2293 expanded = expand_macro_single (macro, uri);
2294 }
2295 else
2296 {
2297 expanded = expand_macro_single (force_file_uri_macro, uri);
2298 if (expanded == NULL)
2299 expanded = expand_macro_single (macro, uri);
2300 }
2301  
2302 if (expanded)
2303 {
2304 g_string_append (exec, expanded);
2305 g_free (expanded);
2306 }
2307 uris = uris->next;
2308 }
2309  
2310 break;
2311  
2312 case 'U':
2313 case 'F':
2314 case 'D':
2315 case 'N':
2316 while (uris)
2317 {
2318 uri = uris->data;
2319  
2320 if (!force_file_uri ||
2321 /* Pass URI if it contains an anchor */
2322 strchr (uri, '#') != NULL)
2323 {
2324 expanded = expand_macro_single (macro, uri);
2325 }
2326 else
2327 {
2328 expanded = expand_macro_single (force_file_uri_macro, uri);
2329 if (expanded == NULL)
2330 expanded = expand_macro_single (macro, uri);
2331 }
2332  
2333 if (expanded)
2334 {
2335 g_string_append (exec, expanded);
2336 g_free (expanded);
2337 }
2338  
2339 uris = uris->next;
2340  
2341 if (uris != NULL && expanded)
2342 g_string_append_c (exec, ' ');
2343 }
2344  
2345 break;
2346  
2347 case 'i':
2348 if (info->icon_name)
2349 {
2350 g_string_append (exec, "--icon ");
2351 expanded = g_shell_quote (info->icon_name);
2352 g_string_append (exec, expanded);
2353 g_free (expanded);
2354 }
2355 break;
2356  
2357 case 'c':
2358 if (info->name)
2359 {
2360 expanded = g_shell_quote (info->name);
2361 g_string_append (exec, expanded);
2362 g_free (expanded);
2363 }
2364 break;
2365  
2366 case 'k':
2367 if (info->filename)
2368 {
2369 expanded = g_shell_quote (info->filename);
2370 g_string_append (exec, expanded);
2371 g_free (expanded);
2372 }
2373 break;
2374  
2375 case 'm': /* deprecated */
2376 break;
2377  
2378 case '%':
2379 g_string_append_c (exec, '%');
2380 break;
2381 }
2382  
2383 *uri_list = uris;
2384 }
2385  
2386 static gboolean
2387 expand_application_parameters (GDesktopAppInfo *info,
2388 const gchar *exec_line,
2389 GList **uris,
2390 int *argc,
2391 char ***argv,
2392 GError **error)
2393 {
2394 GList *uri_list = *uris;
2395 const char *p = exec_line;
2396 GString *expanded_exec;
2397 gboolean res;
2398  
2399 if (exec_line == NULL)
2400 {
2401 g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
2402 _("Desktop file didn't specify Exec field"));
2403 return FALSE;
2404 }
2405  
2406 expanded_exec = g_string_new (NULL);
2407  
2408 while (*p)
2409 {
2410 if (p[0] == '%' && p[1] != '\0')
2411 {
2412 expand_macro (p[1], expanded_exec, info, uris);
2413 p++;
2414 }
2415 else
2416 g_string_append_c (expanded_exec, *p);
2417  
2418 p++;
2419 }
2420  
2421 /* No file substitutions */
2422 if (uri_list == *uris && uri_list != NULL)
2423 {
2424 /* If there is no macro default to %f. This is also what KDE does */
2425 g_string_append_c (expanded_exec, ' ');
2426 expand_macro ('f', expanded_exec, info, uris);
2427 }
2428  
2429 res = g_shell_parse_argv (expanded_exec->str, argc, argv, error);
2430 g_string_free (expanded_exec, TRUE);
2431 return res;
2432 }
2433  
2434 static gboolean
2435 prepend_terminal_to_vector (int *argc,
2436 char ***argv)
2437 {
2438 #ifndef G_OS_WIN32
2439 char **real_argv;
2440 int real_argc;
2441 int i, j;
2442 char **term_argv = NULL;
2443 int term_argc = 0;
2444 char *check;
2445 char **the_argv;
2446  
2447 g_return_val_if_fail (argc != NULL, FALSE);
2448 g_return_val_if_fail (argv != NULL, FALSE);
2449  
2450 /* sanity */
2451 if(*argv == NULL)
2452 *argc = 0;
2453  
2454 the_argv = *argv;
2455  
2456 /* compute size if not given */
2457 if (*argc < 0)
2458 {
2459 for (i = 0; the_argv[i] != NULL; i++)
2460 ;
2461 *argc = i;
2462 }
2463  
2464 term_argc = 2;
2465 term_argv = g_new0 (char *, 3);
2466  
2467 check = g_find_program_in_path ("gnome-terminal");
2468 if (check != NULL)
2469 {
2470 term_argv[0] = check;
2471 /* Note that gnome-terminal takes -x and
2472 * as -e in gnome-terminal is broken we use that. */
2473 term_argv[1] = g_strdup ("-x");
2474 }
2475 else
2476 {
2477 if (check == NULL)
2478 check = g_find_program_in_path ("nxterm");
2479 if (check == NULL)
2480 check = g_find_program_in_path ("color-xterm");
2481 if (check == NULL)
2482 check = g_find_program_in_path ("rxvt");
2483 if (check == NULL)
2484 check = g_find_program_in_path ("xterm");
2485 if (check == NULL)
2486 check = g_find_program_in_path ("dtterm");
2487 if (check == NULL)
2488 {
2489 check = g_strdup ("xterm");
2490 g_warning ("couldn't find a terminal, falling back to xterm");
2491 }
2492 term_argv[0] = check;
2493 term_argv[1] = g_strdup ("-e");
2494 }
2495  
2496 real_argc = term_argc + *argc;
2497 real_argv = g_new (char *, real_argc + 1);
2498  
2499 for (i = 0; i < term_argc; i++)
2500 real_argv[i] = term_argv[i];
2501  
2502 for (j = 0; j < *argc; j++, i++)
2503 real_argv[i] = (char *)the_argv[j];
2504  
2505 real_argv[i] = NULL;
2506  
2507 g_free (*argv);
2508 *argv = real_argv;
2509 *argc = real_argc;
2510  
2511 /* we use g_free here as we sucked all the inner strings
2512 * out from it into real_argv */
2513 g_free (term_argv);
2514 return TRUE;
2515 #else
2516 return FALSE;
2517 #endif /* G_OS_WIN32 */
2518 }
2519  
2520 static GList *
2521 create_files_for_uris (GList *uris)
2522 {
2523 GList *res;
2524 GList *iter;
2525  
2526 res = NULL;
2527  
2528 for (iter = uris; iter; iter = iter->next)
2529 {
2530 GFile *file = g_file_new_for_uri ((char *)iter->data);
2531 res = g_list_prepend (res, file);
2532 }
2533  
2534 return g_list_reverse (res);
2535 }
2536  
2537 typedef struct
2538 {
2539 GSpawnChildSetupFunc user_setup;
2540 gpointer user_setup_data;
2541  
2542 char *pid_envvar;
2543 } ChildSetupData;
2544  
2545 static void
2546 child_setup (gpointer user_data)
2547 {
2548 ChildSetupData *data = user_data;
2549  
2550 if (data->pid_envvar)
2551 {
2552 pid_t pid = getpid ();
2553 char buf[20];
2554 int i;
2555  
2556 /* Write the pid into the space already reserved for it in the
2557 * environment array. We can't use sprintf because it might
2558 * malloc, so we do it by hand. It's simplest to write the pid
2559 * out backwards first, then copy it over.
2560 */
2561 for (i = 0; pid; i++, pid /= 10)
2562 buf[i] = (pid % 10) + '0';
2563 for (i--; i >= 0; i--)
2564 *(data->pid_envvar++) = buf[i];
2565 *data->pid_envvar = '\0';
2566 }
2567  
2568 if (data->user_setup)
2569 data->user_setup (data->user_setup_data);
2570 }
2571  
2572 static void
2573 notify_desktop_launch (GDBusConnection *session_bus,
2574 GDesktopAppInfo *info,
2575 long pid,
2576 const char *display,
2577 const char *sn_id,
2578 GList *uris)
2579 {
2580 GDBusMessage *msg;
2581 GVariantBuilder uri_variant;
2582 GVariantBuilder extras_variant;
2583 GList *iter;
2584 const char *desktop_file_id;
2585 const char *gio_desktop_file;
2586  
2587 if (session_bus == NULL)
2588 return;
2589  
2590 g_variant_builder_init (&uri_variant, G_VARIANT_TYPE ("as"));
2591 for (iter = uris; iter; iter = iter->next)
2592 g_variant_builder_add (&uri_variant, "s", iter->data);
2593  
2594 g_variant_builder_init (&extras_variant, G_VARIANT_TYPE ("a{sv}"));
2595 if (sn_id != NULL && g_utf8_validate (sn_id, -1, NULL))
2596 g_variant_builder_add (&extras_variant, "{sv}",
2597 "startup-id",
2598 g_variant_new ("s",
2599 sn_id));
2600 gio_desktop_file = g_getenv ("GIO_LAUNCHED_DESKTOP_FILE");
2601 if (gio_desktop_file != NULL)
2602 g_variant_builder_add (&extras_variant, "{sv}",
2603 "origin-desktop-file",
2604 g_variant_new_bytestring (gio_desktop_file));
2605 if (g_get_prgname () != NULL)
2606 g_variant_builder_add (&extras_variant, "{sv}",
2607 "origin-prgname",
2608 g_variant_new_bytestring (g_get_prgname ()));
2609 g_variant_builder_add (&extras_variant, "{sv}",
2610 "origin-pid",
2611 g_variant_new ("x",
2612 (gint64)getpid ()));
2613  
2614 if (info->filename)
2615 desktop_file_id = info->filename;
2616 else if (info->desktop_id)
2617 desktop_file_id = info->desktop_id;
2618 else
2619 desktop_file_id = "";
2620  
2621 msg = g_dbus_message_new_signal ("/org/gtk/gio/DesktopAppInfo",
2622 "org.gtk.gio.DesktopAppInfo",
2623 "Launched");
2624 g_dbus_message_set_body (msg, g_variant_new ("(@aysxasa{sv})",
2625 g_variant_new_bytestring (desktop_file_id),
2626 display ? display : "",
2627 (gint64)pid,
2628 &uri_variant,
2629 &extras_variant));
2630 g_dbus_connection_send_message (session_bus,
2631 msg, 0,
2632 NULL,
2633 NULL);
2634 g_object_unref (msg);
2635 }
2636  
2637 #define _SPAWN_FLAGS_DEFAULT (G_SPAWN_SEARCH_PATH)
2638  
2639 static gboolean
2640 g_desktop_app_info_launch_uris_with_spawn (GDesktopAppInfo *info,
2641 GDBusConnection *session_bus,
2642 const gchar *exec_line,
2643 GList *uris,
2644 GAppLaunchContext *launch_context,
2645 GSpawnFlags spawn_flags,
2646 GSpawnChildSetupFunc user_setup,
2647 gpointer user_setup_data,
2648 GDesktopAppLaunchCallback pid_callback,
2649 gpointer pid_callback_data,
2650 GError **error)
2651 {
2652 gboolean completed = FALSE;
2653 GList *old_uris;
2654 char **argv, **envp;
2655 int argc;
2656 ChildSetupData data;
2657  
2658 g_return_val_if_fail (info != NULL, FALSE);
2659  
2660 argv = NULL;
2661  
2662 if (launch_context)
2663 envp = g_app_launch_context_get_environment (launch_context);
2664 else
2665 envp = g_get_environ ();
2666  
2667 do
2668 {
2669 GPid pid;
2670 GList *launched_uris;
2671 GList *iter;
2672 char *sn_id = NULL;
2673  
2674 old_uris = uris;
2675 if (!expand_application_parameters (info, exec_line, &uris, &argc, &argv, error))
2676 goto out;
2677  
2678 /* Get the subset of URIs we're launching with this process */
2679 launched_uris = NULL;
2680 for (iter = old_uris; iter != NULL && iter != uris; iter = iter->next)
2681 launched_uris = g_list_prepend (launched_uris, iter->data);
2682 launched_uris = g_list_reverse (launched_uris);
2683  
2684 if (info->terminal && !prepend_terminal_to_vector (&argc, &argv))
2685 {
2686 g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
2687 _("Unable to find terminal required for application"));
2688 goto out;
2689 }
2690  
2691 data.user_setup = user_setup;
2692 data.user_setup_data = user_setup_data;
2693  
2694 if (info->filename)
2695 {
2696 envp = g_environ_setenv (envp,
2697 "GIO_LAUNCHED_DESKTOP_FILE",
2698 info->filename,
2699 TRUE);
2700 envp = g_environ_setenv (envp,
2701 "GIO_LAUNCHED_DESKTOP_FILE_PID",
2702 "XXXXXXXXXXXXXXXXXXXX", /* filled in child_setup */
2703 TRUE);
2704 data.pid_envvar = (char *)g_environ_getenv (envp, "GIO_LAUNCHED_DESKTOP_FILE_PID");
2705 }
2706 else
2707 {
2708 data.pid_envvar = NULL;
2709 }
2710  
2711 sn_id = NULL;
2712 if (launch_context)
2713 {
2714 GList *launched_files = create_files_for_uris (launched_uris);
2715  
2716 if (info->startup_notify)
2717 {
2718 sn_id = g_app_launch_context_get_startup_notify_id (launch_context,
2719 G_APP_INFO (info),
2720 launched_files);
2721 if (sn_id)
2722 envp = g_environ_setenv (envp, "DESKTOP_STARTUP_ID", sn_id, TRUE);
2723 }
2724  
2725 g_list_free_full (launched_files, g_object_unref);
2726 }
2727  
2728 if (!g_spawn_async (info->path,
2729 argv,
2730 envp,
2731 spawn_flags,
2732 child_setup,
2733 &data,
2734 &pid,
2735 error))
2736 {
2737 if (sn_id)
2738 g_app_launch_context_launch_failed (launch_context, sn_id);
2739  
2740 g_free (sn_id);
2741 g_list_free (launched_uris);
2742  
2743 goto out;
2744 }
2745  
2746 if (pid_callback != NULL)
2747 pid_callback (info, pid, pid_callback_data);
2748  
2749 if (launch_context != NULL)
2750 {
2751 GVariantBuilder builder;
2752 GVariant *platform_data;
2753  
2754 g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY);
2755 g_variant_builder_add (&builder, "{sv}", "pid", g_variant_new_int32 (pid));
2756 if (sn_id)
2757 g_variant_builder_add (&builder, "{sv}", "startup-notification-id", g_variant_new_string (sn_id));
2758 platform_data = g_variant_ref_sink (g_variant_builder_end (&builder));
2759 g_signal_emit_by_name (launch_context, "launched", info, platform_data);
2760 g_variant_unref (platform_data);
2761 }
2762  
2763 notify_desktop_launch (session_bus,
2764 info,
2765 pid,
2766 NULL,
2767 sn_id,
2768 launched_uris);
2769  
2770 g_free (sn_id);
2771 g_list_free (launched_uris);
2772  
2773 g_strfreev (argv);
2774 argv = NULL;
2775 }
2776 while (uris != NULL);
2777  
2778 completed = TRUE;
2779  
2780 out:
2781 g_strfreev (argv);
2782 g_strfreev (envp);
2783  
2784 return completed;
2785 }
2786  
2787 static gchar *
2788 object_path_from_appid (const gchar *appid)
2789 {
2790 gchar *appid_path, *iter;
2791  
2792 appid_path = g_strconcat ("/", appid, NULL);
2793 for (iter = appid_path; *iter; iter++)
2794 {
2795 if (*iter == '.')
2796 *iter = '/';
2797  
2798 if (*iter == '-')
2799 *iter = '_';
2800 }
2801  
2802 return appid_path;
2803 }
2804  
2805 static GVariant *
2806 g_desktop_app_info_make_platform_data (GDesktopAppInfo *info,
2807 GList *uris,
2808 GAppLaunchContext *launch_context)
2809 {
2810 GVariantBuilder builder;
2811  
2812 g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
2813  
2814 if (launch_context)
2815 {
2816 GList *launched_files = create_files_for_uris (uris);
2817  
2818 if (info->startup_notify)
2819 {
2820 gchar *sn_id;
2821  
2822 sn_id = g_app_launch_context_get_startup_notify_id (launch_context, G_APP_INFO (info), launched_files);
2823 if (sn_id)
2824 g_variant_builder_add (&builder, "{sv}", "desktop-startup-id", g_variant_new_take_string (sn_id));
2825 }
2826  
2827 g_list_free_full (launched_files, g_object_unref);
2828 }
2829  
2830 return g_variant_builder_end (&builder);
2831 }
2832  
2833 static gboolean
2834 g_desktop_app_info_launch_uris_with_dbus (GDesktopAppInfo *info,
2835 GDBusConnection *session_bus,
2836 GList *uris,
2837 GAppLaunchContext *launch_context)
2838 {
2839 GVariantBuilder builder;
2840 gchar *object_path;
2841  
2842 g_return_val_if_fail (info != NULL, FALSE);
2843  
2844 g_variant_builder_init (&builder, G_VARIANT_TYPE_TUPLE);
2845  
2846 if (uris)
2847 {
2848 GList *iter;
2849  
2850 g_variant_builder_open (&builder, G_VARIANT_TYPE_STRING_ARRAY);
2851 for (iter = uris; iter; iter = iter->next)
2852 g_variant_builder_add (&builder, "s", iter->data);
2853 g_variant_builder_close (&builder);
2854 }
2855  
2856 g_variant_builder_add_value (&builder, g_desktop_app_info_make_platform_data (info, uris, launch_context));
2857  
2858 /* This is non-blocking API. Similar to launching via fork()/exec()
2859 * we don't wait around to see if the program crashed during startup.
2860 * This is what startup-notification's job is...
2861 */
2862 object_path = object_path_from_appid (info->app_id);
2863 g_dbus_connection_call (session_bus, info->app_id, object_path, "org.freedesktop.Application",
2864 uris ? "Open" : "Activate", g_variant_builder_end (&builder),
2865 NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
2866 g_free (object_path);
2867  
2868 return TRUE;
2869 }
2870  
2871 static gboolean
2872 g_desktop_app_info_launch_uris_internal (GAppInfo *appinfo,
2873 GList *uris,
2874 GAppLaunchContext *launch_context,
2875 GSpawnFlags spawn_flags,
2876 GSpawnChildSetupFunc user_setup,
2877 gpointer user_setup_data,
2878 GDesktopAppLaunchCallback pid_callback,
2879 gpointer pid_callback_data,
2880 GError **error)
2881 {
2882 GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
2883 GDBusConnection *session_bus;
2884 gboolean success = TRUE;
2885  
2886 session_bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
2887  
2888 if (session_bus && info->app_id)
2889 g_desktop_app_info_launch_uris_with_dbus (info, session_bus, uris, launch_context);
2890 else
2891 success = g_desktop_app_info_launch_uris_with_spawn (info, session_bus, info->exec, uris, launch_context,
2892 spawn_flags, user_setup, user_setup_data,
2893 pid_callback, pid_callback_data, error);
2894  
2895 if (session_bus != NULL)
2896 {
2897 /* This asynchronous flush holds a reference until it completes,
2898 * which ensures that the following unref won't immediately kill
2899 * the connection if we were the initial owner.
2900 */
2901 g_dbus_connection_flush (session_bus, NULL, NULL, NULL);
2902 g_object_unref (session_bus);
2903 }
2904  
2905 return success;
2906 }
2907  
2908 static gboolean
2909 g_desktop_app_info_launch_uris (GAppInfo *appinfo,
2910 GList *uris,
2911 GAppLaunchContext *launch_context,
2912 GError **error)
2913 {
2914 return g_desktop_app_info_launch_uris_internal (appinfo, uris,
2915 launch_context,
2916 _SPAWN_FLAGS_DEFAULT,
2917 NULL, NULL, NULL, NULL,
2918 error);
2919 }
2920  
2921 static gboolean
2922 g_desktop_app_info_supports_uris (GAppInfo *appinfo)
2923 {
2924 GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
2925  
2926 return info->exec &&
2927 ((strstr (info->exec, "%u") != NULL) ||
2928 (strstr (info->exec, "%U") != NULL));
2929 }
2930  
2931 static gboolean
2932 g_desktop_app_info_supports_files (GAppInfo *appinfo)
2933 {
2934 GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
2935  
2936 return info->exec &&
2937 ((strstr (info->exec, "%f") != NULL) ||
2938 (strstr (info->exec, "%F") != NULL));
2939 }
2940  
2941 static gboolean
2942 g_desktop_app_info_launch (GAppInfo *appinfo,
2943 GList *files,
2944 GAppLaunchContext *launch_context,
2945 GError **error)
2946 {
2947 GList *uris;
2948 char *uri;
2949 gboolean res;
2950  
2951 uris = NULL;
2952 while (files)
2953 {
2954 uri = g_file_get_uri (files->data);
2955 uris = g_list_prepend (uris, uri);
2956 files = files->next;
2957 }
2958  
2959 uris = g_list_reverse (uris);
2960  
2961 res = g_desktop_app_info_launch_uris (appinfo, uris, launch_context, error);
2962  
2963 g_list_free_full (uris, g_free);
2964  
2965 return res;
2966 }
2967  
2968 /**
2969 * g_desktop_app_info_launch_uris_as_manager:
2970 * @appinfo: a #GDesktopAppInfo
2971 * @uris: (element-type utf8): List of URIs
2972 * @launch_context: (allow-none): a #GAppLaunchContext
2973 * @spawn_flags: #GSpawnFlags, used for each process
2974 * @user_setup: (scope call) (allow-none): a #GSpawnChildSetupFunc, used once
2975 * for each process.
2976 * @user_setup_data: (closure user_setup) (allow-none): User data for @user_setup
2977 * @pid_callback: (scope call) (allow-none): Callback for child processes
2978 * @pid_callback_data: (closure pid_callback) (allow-none): User data for @callback
2979 * @error: return location for a #GError, or %NULL
2980 *
2981 * This function performs the equivalent of g_app_info_launch_uris(),
2982 * but is intended primarily for operating system components that
2983 * launch applications. Ordinary applications should use
2984 * g_app_info_launch_uris().
2985 *
2986 * If the application is launched via traditional UNIX fork()/exec()
2987 * then @spawn_flags, @user_setup and @user_setup_data are used for the
2988 * call to g_spawn_async(). Additionally, @pid_callback (with
2989 * @pid_callback_data) will be called to inform about the PID of the
2990 * created process.
2991 *
2992 * If application launching occurs via some other mechanism (eg: D-Bus
2993 * activation) then @spawn_flags, @user_setup, @user_setup_data,
2994 * @pid_callback and @pid_callback_data are ignored.
2995 *
2996 * Returns: %TRUE on successful launch, %FALSE otherwise.
2997 */
2998 gboolean
2999 g_desktop_app_info_launch_uris_as_manager (GDesktopAppInfo *appinfo,
3000 GList *uris,
3001 GAppLaunchContext *launch_context,
3002 GSpawnFlags spawn_flags,
3003 GSpawnChildSetupFunc user_setup,
3004 gpointer user_setup_data,
3005 GDesktopAppLaunchCallback pid_callback,
3006 gpointer pid_callback_data,
3007 GError **error)
3008 {
3009 return g_desktop_app_info_launch_uris_internal ((GAppInfo*)appinfo,
3010 uris,
3011 launch_context,
3012 spawn_flags,
3013 user_setup,
3014 user_setup_data,
3015 pid_callback,
3016 pid_callback_data,
3017 error);
3018 }
3019  
3020 /* OnlyShowIn API support {{{2 */
3021  
3022 /**
3023 * g_desktop_app_info_set_desktop_env:
3024 * @desktop_env: a string specifying what desktop this is
3025 *
3026 * Sets the name of the desktop that the application is running in.
3027 * This is used by g_app_info_should_show() and
3028 * g_desktop_app_info_get_show_in() to evaluate the
3029 * `OnlyShowIn` and `NotShowIn`
3030 * desktop entry fields.
3031 *
3032 * Should be called only once; subsequent calls are ignored.
3033 *
3034 * Deprecated:2.42:do not use this API. Since 2.42 the value of the
3035 * `XDG_CURRENT_DESKTOP` environment variable will be used.
3036 */
3037 void
3038 g_desktop_app_info_set_desktop_env (const gchar *desktop_env)
3039 {
3040 get_current_desktops (desktop_env);
3041 }
3042  
3043 static gboolean
3044 g_desktop_app_info_should_show (GAppInfo *appinfo)
3045 {
3046 GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
3047  
3048 if (info->nodisplay)
3049 return FALSE;
3050  
3051 return g_desktop_app_info_get_show_in (info, NULL);
3052 }
3053  
3054 /* mime types/default apps support {{{2 */
3055  
3056 typedef enum {
3057 CONF_DIR,
3058 APP_DIR,
3059 MIMETYPE_DIR
3060 } DirType;
3061  
3062 static char *
3063 ensure_dir (DirType type,
3064 GError **error)
3065 {
3066 char *path, *display_name;
3067 int errsv;
3068  
3069 switch (type)
3070 {
3071 case CONF_DIR:
3072 path = g_build_filename (g_get_user_config_dir (), NULL);
3073 break;
3074  
3075 case APP_DIR:
3076 path = g_build_filename (g_get_user_data_dir (), "applications", NULL);
3077 break;
3078  
3079 case MIMETYPE_DIR:
3080 path = g_build_filename (g_get_user_data_dir (), "mime", "packages", NULL);
3081 break;
3082  
3083 default:
3084 g_assert_not_reached ();
3085 }
3086  
3087 errno = 0;
3088 if (g_mkdir_with_parents (path, 0700) == 0)
3089 return path;
3090  
3091 errsv = errno;
3092 display_name = g_filename_display_name (path);
3093 if (type == APP_DIR)
3094 g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv),
3095 _("Can't create user application configuration folder %s: %s"),
3096 display_name, g_strerror (errsv));
3097 else
3098 g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv),
3099 _("Can't create user MIME configuration folder %s: %s"),
3100 display_name, g_strerror (errsv));
3101  
3102 g_free (display_name);
3103 g_free (path);
3104  
3105 return NULL;
3106 }
3107  
3108 static gboolean
3109 update_mimeapps_list (const char *desktop_id,
3110 const char *content_type,
3111 UpdateMimeFlags flags,
3112 GError **error)
3113 {
3114 char *dirname, *filename, *string;
3115 GKeyFile *key_file;
3116 gboolean load_succeeded, res;
3117 char **old_list, **list;
3118 gsize length, data_size;
3119 char *data;
3120 int i, j, k;
3121 char **content_types;
3122  
3123 /* Don't add both at start and end */
3124 g_assert (!((flags & UPDATE_MIME_SET_DEFAULT) &&
3125 (flags & UPDATE_MIME_SET_NON_DEFAULT)));
3126  
3127 dirname = ensure_dir (CONF_DIR, error);
3128 if (!dirname)
3129 return FALSE;
3130  
3131 filename = g_build_filename (dirname, "mimeapps.list", NULL);
3132 g_free (dirname);
3133  
3134 key_file = g_key_file_new ();
3135 load_succeeded = g_key_file_load_from_file (key_file, filename, G_KEY_FILE_NONE, NULL);
3136 if (!load_succeeded ||
3137 (!g_key_file_has_group (key_file, ADDED_ASSOCIATIONS_GROUP) &&
3138 !g_key_file_has_group (key_file, REMOVED_ASSOCIATIONS_GROUP) &&
3139 !g_key_file_has_group (key_file, DEFAULT_APPLICATIONS_GROUP)))
3140 {
3141 g_key_file_free (key_file);
3142 key_file = g_key_file_new ();
3143 }
3144  
3145 if (content_type)
3146 {
3147 content_types = g_new (char *, 2);
3148 content_types[0] = g_strdup (content_type);
3149 content_types[1] = NULL;
3150 }
3151 else
3152 {
3153 content_types = g_key_file_get_keys (key_file, DEFAULT_APPLICATIONS_GROUP, NULL, NULL);
3154 }
3155  
3156 for (k = 0; content_types && content_types[k]; k++)
3157 {
3158 /* set as default, if requested so */
3159 string = g_key_file_get_string (key_file,
3160 DEFAULT_APPLICATIONS_GROUP,
3161 content_types[k],
3162 NULL);
3163  
3164 if (g_strcmp0 (string, desktop_id) != 0 &&
3165 (flags & UPDATE_MIME_SET_DEFAULT))
3166 {
3167 g_free (string);
3168 string = g_strdup (desktop_id);
3169  
3170 /* add in the non-default list too, if it's not already there */
3171 flags |= UPDATE_MIME_SET_NON_DEFAULT;
3172 }
3173  
3174 if (string == NULL || desktop_id == NULL)
3175 g_key_file_remove_key (key_file,
3176 DEFAULT_APPLICATIONS_GROUP,
3177 content_types[k],
3178 NULL);
3179 else
3180 g_key_file_set_string (key_file,
3181 DEFAULT_APPLICATIONS_GROUP,
3182 content_types[k],
3183 string);
3184  
3185 g_free (string);
3186 }
3187  
3188 if (content_type)
3189 {
3190 /* reuse the list from above */
3191 }
3192 else
3193 {
3194 g_strfreev (content_types);
3195 content_types = g_key_file_get_keys (key_file, ADDED_ASSOCIATIONS_GROUP, NULL, NULL);
3196 }
3197  
3198 for (k = 0; content_types && content_types[k]; k++)
3199 {
3200 /* Add to the right place in the list */
3201  
3202 length = 0;
3203 old_list = g_key_file_get_string_list (key_file, ADDED_ASSOCIATIONS_GROUP,
3204 content_types[k], &length, NULL);
3205  
3206 list = g_new (char *, 1 + length + 1);
3207  
3208 i = 0;
3209  
3210 /* if we're adding a last-used hint, just put the application in front of the list */
3211 if (flags & UPDATE_MIME_SET_LAST_USED)
3212 {
3213 /* avoid adding this again as non-default later */
3214 if (flags & UPDATE_MIME_SET_NON_DEFAULT)
3215 flags ^= UPDATE_MIME_SET_NON_DEFAULT;
3216  
3217 list[i++] = g_strdup (desktop_id);
3218 }
3219  
3220 if (old_list)
3221 {
3222 for (j = 0; old_list[j] != NULL; j++)
3223 {
3224 if (g_strcmp0 (old_list[j], desktop_id) != 0)
3225 {
3226 /* rewrite other entries if they're different from the new one */
3227 list[i++] = g_strdup (old_list[j]);
3228 }
3229 else if (flags & UPDATE_MIME_SET_NON_DEFAULT)
3230 {
3231 /* we encountered an old entry which is equal to the one we're adding as non-default,
3232 * don't change its position in the list.
3233 */
3234 flags ^= UPDATE_MIME_SET_NON_DEFAULT;
3235 list[i++] = g_strdup (old_list[j]);
3236 }
3237 }
3238 }
3239  
3240 /* add it at the end of the list */
3241 if (flags & UPDATE_MIME_SET_NON_DEFAULT)
3242 list[i++] = g_strdup (desktop_id);
3243  
3244 list[i] = NULL;
3245  
3246 g_strfreev (old_list);
3247  
3248 if (list[0] == NULL || desktop_id == NULL)
3249 g_key_file_remove_key (key_file,
3250 ADDED_ASSOCIATIONS_GROUP,
3251 content_types[k],
3252 NULL);
3253 else
3254 g_key_file_set_string_list (key_file,
3255 ADDED_ASSOCIATIONS_GROUP,
3256 content_types[k],
3257 (const char * const *)list, i);
3258  
3259 g_strfreev (list);
3260 }
3261  
3262 if (content_type)
3263 {
3264 /* reuse the list from above */
3265 }
3266 else
3267 {
3268 g_strfreev (content_types);
3269 content_types = g_key_file_get_keys (key_file, REMOVED_ASSOCIATIONS_GROUP, NULL, NULL);
3270 }
3271  
3272 for (k = 0; content_types && content_types[k]; k++)
3273 {
3274 /* Remove from removed associations group (unless remove) */
3275  
3276 length = 0;
3277 old_list = g_key_file_get_string_list (key_file, REMOVED_ASSOCIATIONS_GROUP,
3278 content_types[k], &length, NULL);
3279  
3280 list = g_new (char *, 1 + length + 1);
3281  
3282 i = 0;
3283 if (flags & UPDATE_MIME_REMOVE)
3284 list[i++] = g_strdup (desktop_id);
3285 if (old_list)
3286 {
3287 for (j = 0; old_list[j] != NULL; j++)
3288 {
3289 if (g_strcmp0 (old_list[j], desktop_id) != 0)
3290 list[i++] = g_strdup (old_list[j]);
3291 }
3292 }
3293 list[i] = NULL;
3294  
3295 g_strfreev (old_list);
3296  
3297 if (list[0] == NULL || desktop_id == NULL)
3298 g_key_file_remove_key (key_file,
3299 REMOVED_ASSOCIATIONS_GROUP,
3300 content_types[k],
3301 NULL);
3302 else
3303 g_key_file_set_string_list (key_file,
3304 REMOVED_ASSOCIATIONS_GROUP,
3305 content_types[k],
3306 (const char * const *)list, i);
3307  
3308 g_strfreev (list);
3309 }
3310  
3311 g_strfreev (content_types);
3312  
3313 data = g_key_file_to_data (key_file, &data_size, error);
3314 g_key_file_free (key_file);
3315  
3316 res = g_file_set_contents (filename, data, data_size, error);
3317  
3318 desktop_file_dirs_invalidate_user_config ();
3319  
3320 g_free (filename);
3321 g_free (data);
3322  
3323 return res;
3324 }
3325  
3326 static gboolean
3327 g_desktop_app_info_set_as_last_used_for_type (GAppInfo *appinfo,
3328 const char *content_type,
3329 GError **error)
3330 {
3331 GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
3332  
3333 if (!g_desktop_app_info_ensure_saved (info, error))
3334 return FALSE;
3335  
3336 if (!info->desktop_id)
3337 {
3338 g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
3339 _("Application information lacks an identifier"));
3340 return FALSE;
3341 }
3342  
3343 /* both add support for the content type and set as last used */
3344 return update_mimeapps_list (info->desktop_id, content_type,
3345 UPDATE_MIME_SET_NON_DEFAULT |
3346 UPDATE_MIME_SET_LAST_USED,
3347 error);
3348 }
3349  
3350 static gboolean
3351 g_desktop_app_info_set_as_default_for_type (GAppInfo *appinfo,
3352 const char *content_type,
3353 GError **error)
3354 {
3355 GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
3356  
3357 if (!g_desktop_app_info_ensure_saved (info, error))
3358 return FALSE;
3359  
3360 if (!info->desktop_id)
3361 {
3362 g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
3363 _("Application information lacks an identifier"));
3364 return FALSE;
3365 }
3366  
3367 return update_mimeapps_list (info->desktop_id, content_type,
3368 UPDATE_MIME_SET_DEFAULT,
3369 error);
3370 }
3371  
3372 static void
3373 update_program_done (GPid pid,
3374 gint status,
3375 gpointer data)
3376 {
3377 /* Did the application exit correctly */
3378 if (g_spawn_check_exit_status (status, NULL))
3379 {
3380 /* Here we could clean out any caches in use */
3381 }
3382 }
3383  
3384 static void
3385 run_update_command (char *command,
3386 char *subdir)
3387 {
3388 char *argv[3] = {
3389 NULL,
3390 NULL,
3391 NULL,
3392 };
3393 GPid pid = 0;
3394 GError *error = NULL;
3395  
3396 argv[0] = command;
3397 argv[1] = g_build_filename (g_get_user_data_dir (), subdir, NULL);
3398  
3399 if (g_spawn_async ("/", argv,
3400 NULL, /* envp */
3401 G_SPAWN_SEARCH_PATH |
3402 G_SPAWN_STDOUT_TO_DEV_NULL |
3403 G_SPAWN_STDERR_TO_DEV_NULL |
3404 G_SPAWN_DO_NOT_REAP_CHILD,
3405 NULL, NULL, /* No setup function */
3406 &pid,
3407 &error))
3408 g_child_watch_add (pid, update_program_done, NULL);
3409 else
3410 {
3411 /* If we get an error at this point, it's quite likely the user doesn't
3412 * have an installed copy of either 'update-mime-database' or
3413 * 'update-desktop-database'. I don't think we want to popup an error
3414 * dialog at this point, so we just do a g_warning to give the user a
3415 * chance of debugging it.
3416 */
3417 g_warning ("%s", error->message);
3418 g_error_free (error);
3419 }
3420  
3421 g_free (argv[1]);
3422 }
3423  
3424 static gboolean
3425 g_desktop_app_info_set_as_default_for_extension (GAppInfo *appinfo,
3426 const char *extension,
3427 GError **error)
3428 {
3429 char *filename, *basename, *mimetype;
3430 char *dirname;
3431 gboolean res;
3432  
3433 if (!g_desktop_app_info_ensure_saved (G_DESKTOP_APP_INFO (appinfo), error))
3434 return FALSE;
3435  
3436 dirname = ensure_dir (MIMETYPE_DIR, error);
3437 if (!dirname)
3438 return FALSE;
3439  
3440 basename = g_strdup_printf ("user-extension-%s.xml", extension);
3441 filename = g_build_filename (dirname, basename, NULL);
3442 g_free (basename);
3443 g_free (dirname);
3444  
3445 mimetype = g_strdup_printf ("application/x-extension-%s", extension);
3446  
3447 if (!g_file_test (filename, G_FILE_TEST_EXISTS))
3448 {
3449 char *contents;
3450  
3451 contents =
3452 g_strdup_printf ("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
3453 "<mime-info xmlns=\"http://www.freedesktop.org/standards/shared-mime-info\">\n"
3454 " <mime-type type=\"%s\">\n"
3455 " <comment>%s document</comment>\n"
3456 " <glob pattern=\"*.%s\"/>\n"
3457 " </mime-type>\n"
3458 "</mime-info>\n", mimetype, extension, extension);
3459  
3460 g_file_set_contents (filename, contents, -1, NULL);
3461 g_free (contents);
3462  
3463 run_update_command ("update-mime-database", "mime");
3464 }
3465 g_free (filename);
3466  
3467 res = g_desktop_app_info_set_as_default_for_type (appinfo,
3468 mimetype,
3469 error);
3470  
3471 g_free (mimetype);
3472  
3473 return res;
3474 }
3475  
3476 static gboolean
3477 g_desktop_app_info_add_supports_type (GAppInfo *appinfo,
3478 const char *content_type,
3479 GError **error)
3480 {
3481 GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
3482  
3483 if (!g_desktop_app_info_ensure_saved (G_DESKTOP_APP_INFO (info), error))
3484 return FALSE;
3485  
3486 return update_mimeapps_list (info->desktop_id, content_type,
3487 UPDATE_MIME_SET_NON_DEFAULT,
3488 error);
3489 }
3490  
3491 static gboolean
3492 g_desktop_app_info_can_remove_supports_type (GAppInfo *appinfo)
3493 {
3494 return TRUE;
3495 }
3496  
3497 static gboolean
3498 g_desktop_app_info_remove_supports_type (GAppInfo *appinfo,
3499 const char *content_type,
3500 GError **error)
3501 {
3502 GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
3503  
3504 if (!g_desktop_app_info_ensure_saved (G_DESKTOP_APP_INFO (info), error))
3505 return FALSE;
3506  
3507 return update_mimeapps_list (info->desktop_id, content_type,
3508 UPDATE_MIME_REMOVE,
3509 error);
3510 }
3511  
3512 static const char **
3513 g_desktop_app_info_get_supported_types (GAppInfo *appinfo)
3514 {
3515 GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
3516  
3517 return (const char**) info->mime_types;
3518 }
3519  
3520 /* Saving and deleting {{{2 */
3521  
3522 static gboolean
3523 g_desktop_app_info_ensure_saved (GDesktopAppInfo *info,
3524 GError **error)
3525 {
3526 GKeyFile *key_file;
3527 char *dirname;
3528 char *filename;
3529 char *data, *desktop_id;
3530 gsize data_size;
3531 int fd;
3532 gboolean res;
3533  
3534 if (info->filename != NULL)
3535 return TRUE;
3536  
3537 /* This is only used for object created with
3538 * g_app_info_create_from_commandline. All other
3539 * object should have a filename
3540 */
3541  
3542 dirname = ensure_dir (APP_DIR, error);
3543 if (!dirname)
3544 return FALSE;
3545  
3546 key_file = g_key_file_new ();
3547  
3548 g_key_file_set_string (key_file, G_KEY_FILE_DESKTOP_GROUP,
3549 "Encoding", "UTF-8");
3550 g_key_file_set_string (key_file, G_KEY_FILE_DESKTOP_GROUP,
3551 G_KEY_FILE_DESKTOP_KEY_VERSION, "1.0");
3552 g_key_file_set_string (key_file, G_KEY_FILE_DESKTOP_GROUP,
3553 G_KEY_FILE_DESKTOP_KEY_TYPE,
3554 G_KEY_FILE_DESKTOP_TYPE_APPLICATION);
3555 if (info->terminal)
3556 g_key_file_set_boolean (key_file, G_KEY_FILE_DESKTOP_GROUP,
3557 G_KEY_FILE_DESKTOP_KEY_TERMINAL, TRUE);
3558 if (info->nodisplay)
3559 g_key_file_set_boolean (key_file, G_KEY_FILE_DESKTOP_GROUP,
3560 G_KEY_FILE_DESKTOP_KEY_NO_DISPLAY, TRUE);
3561  
3562 g_key_file_set_string (key_file, G_KEY_FILE_DESKTOP_GROUP,
3563 G_KEY_FILE_DESKTOP_KEY_EXEC, info->exec);
3564  
3565 g_key_file_set_string (key_file, G_KEY_FILE_DESKTOP_GROUP,
3566 G_KEY_FILE_DESKTOP_KEY_NAME, info->name);
3567  
3568 if (info->generic_name != NULL)
3569 g_key_file_set_string (key_file, G_KEY_FILE_DESKTOP_GROUP,
3570 GENERIC_NAME_KEY, info->generic_name);
3571  
3572 if (info->fullname != NULL)
3573 g_key_file_set_string (key_file, G_KEY_FILE_DESKTOP_GROUP,
3574 FULL_NAME_KEY, info->fullname);
3575  
3576 g_key_file_set_string (key_file, G_KEY_FILE_DESKTOP_GROUP,
3577 G_KEY_FILE_DESKTOP_KEY_COMMENT, info->comment);
3578  
3579 g_key_file_set_boolean (key_file, G_KEY_FILE_DESKTOP_GROUP,
3580 G_KEY_FILE_DESKTOP_KEY_NO_DISPLAY, TRUE);
3581  
3582 data = g_key_file_to_data (key_file, &data_size, NULL);
3583 g_key_file_free (key_file);
3584  
3585 desktop_id = g_strdup_printf ("userapp-%s-XXXXXX.desktop", info->name);
3586 filename = g_build_filename (dirname, desktop_id, NULL);
3587 g_free (desktop_id);
3588 g_free (dirname);
3589  
3590 fd = g_mkstemp (filename);
3591 if (fd == -1)
3592 {
3593 char *display_name;
3594  
3595 display_name = g_filename_display_name (filename);
3596 g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
3597 _("Can't create user desktop file %s"), display_name);
3598 g_free (display_name);
3599 g_free (filename);
3600 g_free (data);
3601 return FALSE;
3602 }
3603  
3604 desktop_id = g_path_get_basename (filename);
3605  
3606 /* FIXME - actually handle error */
3607 (void) g_close (fd, NULL);
3608  
3609 res = g_file_set_contents (filename, data, data_size, error);
3610 g_free (data);
3611 if (!res)
3612 {
3613 g_free (desktop_id);
3614 g_free (filename);
3615 return FALSE;
3616 }
3617  
3618 info->filename = filename;
3619 info->desktop_id = desktop_id;
3620  
3621 run_update_command ("update-desktop-database", "applications");
3622  
3623 /* We just dropped a file in the user's desktop file directory. Save
3624 * the monitor the bother of having to notice it and invalidate
3625 * immediately.
3626 *
3627 * This means that calls directly following this will be able to see
3628 * the results immediately.
3629 */
3630 desktop_file_dirs_invalidate_user_data ();
3631  
3632 return TRUE;
3633 }
3634  
3635 static gboolean
3636 g_desktop_app_info_can_delete (GAppInfo *appinfo)
3637 {
3638 GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
3639  
3640 if (info->filename)
3641 {
3642 if (strstr (info->filename, "/userapp-"))
3643 return g_access (info->filename, W_OK) == 0;
3644 }
3645  
3646 return FALSE;
3647 }
3648  
3649 static gboolean
3650 g_desktop_app_info_delete (GAppInfo *appinfo)
3651 {
3652 GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
3653  
3654 if (info->filename)
3655 {
3656 if (g_remove (info->filename) == 0)
3657 {
3658 update_mimeapps_list (info->desktop_id, NULL,
3659 UPDATE_MIME_NONE,
3660 NULL);
3661  
3662 g_free (info->filename);
3663 info->filename = NULL;
3664 g_free (info->desktop_id);
3665 info->desktop_id = NULL;
3666  
3667 return TRUE;
3668 }
3669 }
3670  
3671 return FALSE;
3672 }
3673  
3674 /* Create for commandline {{{2 */
3675 /**
3676 * g_app_info_create_from_commandline:
3677 * @commandline: the commandline to use
3678 * @application_name: (allow-none): the application name, or %NULL to use @commandline
3679 * @flags: flags that can specify details of the created #GAppInfo
3680 * @error: a #GError location to store the error occurring, %NULL to ignore.
3681 *
3682 * Creates a new #GAppInfo from the given information.
3683 *
3684 * Note that for @commandline, the quoting rules of the Exec key of the
3685 * [freedesktop.org Desktop Entry Specification](http://freedesktop.org/Standards/desktop-entry-spec)
3686 * are applied. For example, if the @commandline contains
3687 * percent-encoded URIs, the percent-character must be doubled in order to prevent it from
3688 * being swallowed by Exec key unquoting. See the specification for exact quoting rules.
3689 *
3690 * Returns: (transfer full): new #GAppInfo for given command.
3691 **/
3692 GAppInfo *
3693 g_app_info_create_from_commandline (const char *commandline,
3694 const char *application_name,
3695 GAppInfoCreateFlags flags,
3696 GError **error)
3697 {
3698 char **split;
3699 char *basename;
3700 GDesktopAppInfo *info;
3701  
3702 g_return_val_if_fail (commandline, NULL);
3703  
3704 info = g_object_new (G_TYPE_DESKTOP_APP_INFO, NULL);
3705  
3706 info->filename = NULL;
3707 info->desktop_id = NULL;
3708  
3709 info->terminal = (flags & G_APP_INFO_CREATE_NEEDS_TERMINAL) != 0;
3710 info->startup_notify = (flags & G_APP_INFO_CREATE_SUPPORTS_STARTUP_NOTIFICATION) != 0;
3711 info->hidden = FALSE;
3712 if ((flags & G_APP_INFO_CREATE_SUPPORTS_URIS) != 0)
3713 info->exec = g_strconcat (commandline, " %u", NULL);
3714 else
3715 info->exec = g_strconcat (commandline, " %f", NULL);
3716 info->nodisplay = TRUE;
3717 info->binary = binary_from_exec (info->exec);
3718  
3719 if (application_name)
3720 info->name = g_strdup (application_name);
3721 else
3722 {
3723 /* FIXME: this should be more robust. Maybe g_shell_parse_argv and use argv[0] */
3724 split = g_strsplit (commandline, " ", 2);
3725 basename = split[0] ? g_path_get_basename (split[0]) : NULL;
3726 g_strfreev (split);
3727 info->name = basename;
3728 if (info->name == NULL)
3729 info->name = g_strdup ("custom");
3730 }
3731 info->comment = g_strdup_printf (_("Custom definition for %s"), info->name);
3732  
3733 return G_APP_INFO (info);
3734 }
3735  
3736 /* GAppInfo interface init */
3737  
3738 static void
3739 g_desktop_app_info_iface_init (GAppInfoIface *iface)
3740 {
3741 iface->dup = g_desktop_app_info_dup;
3742 iface->equal = g_desktop_app_info_equal;
3743 iface->get_id = g_desktop_app_info_get_id;
3744 iface->get_name = g_desktop_app_info_get_name;
3745 iface->get_description = g_desktop_app_info_get_description;
3746 iface->get_executable = g_desktop_app_info_get_executable;
3747 iface->get_icon = g_desktop_app_info_get_icon;
3748 iface->launch = g_desktop_app_info_launch;
3749 iface->supports_uris = g_desktop_app_info_supports_uris;
3750 iface->supports_files = g_desktop_app_info_supports_files;
3751 iface->launch_uris = g_desktop_app_info_launch_uris;
3752 iface->should_show = g_desktop_app_info_should_show;
3753 iface->set_as_default_for_type = g_desktop_app_info_set_as_default_for_type;
3754 iface->set_as_default_for_extension = g_desktop_app_info_set_as_default_for_extension;
3755 iface->add_supports_type = g_desktop_app_info_add_supports_type;
3756 iface->can_remove_supports_type = g_desktop_app_info_can_remove_supports_type;
3757 iface->remove_supports_type = g_desktop_app_info_remove_supports_type;
3758 iface->can_delete = g_desktop_app_info_can_delete;
3759 iface->do_delete = g_desktop_app_info_delete;
3760 iface->get_commandline = g_desktop_app_info_get_commandline;
3761 iface->get_display_name = g_desktop_app_info_get_display_name;
3762 iface->set_as_last_used_for_type = g_desktop_app_info_set_as_last_used_for_type;
3763 iface->get_supported_types = g_desktop_app_info_get_supported_types;
3764 }
3765  
3766 /* Recommended applications {{{2 */
3767  
3768 /* Converts content_type into a list of itself with all of its parent
3769 * types (if include_fallback is enabled) or just returns a single-item
3770 * list with the unaliased content type.
3771 */
3772 static gchar **
3773 get_list_of_mimetypes (const gchar *content_type,
3774 gboolean include_fallback)
3775 {
3776 gchar *unaliased;
3777 GPtrArray *array;
3778  
3779 array = g_ptr_array_new ();
3780 unaliased = _g_unix_content_type_unalias (content_type);
3781 g_ptr_array_add (array, unaliased);
3782  
3783 if (include_fallback)
3784 {
3785 gint i;
3786  
3787 /* Iterate the array as we grow it, until we have nothing more to add */
3788 for (i = 0; i < array->len; i++)
3789 {
3790 gchar **parents = _g_unix_content_type_get_parents (g_ptr_array_index (array, i));
3791 gint j;
3792  
3793 for (j = 0; parents[j]; j++)
3794 /* Don't add duplicates */
3795 if (!array_contains (array, parents[j]))
3796 g_ptr_array_add (array, parents[j]);
3797 else
3798 g_free (parents[j]);
3799  
3800 /* We already stole or freed each element. Free the container. */
3801 g_free (parents);
3802 }
3803 }
3804  
3805 g_ptr_array_add (array, NULL);
3806  
3807 return (gchar **) g_ptr_array_free (array, FALSE);
3808 }
3809  
3810 static gchar **
3811 g_desktop_app_info_get_desktop_ids_for_content_type (const gchar *content_type,
3812 gboolean include_fallback)
3813 {
3814 GPtrArray *hits, *blacklist;
3815 gchar **types;
3816 gint i, j;
3817  
3818 hits = g_ptr_array_new ();
3819 blacklist = g_ptr_array_new ();
3820  
3821 types = get_list_of_mimetypes (content_type, include_fallback);
3822  
3823 desktop_file_dirs_lock ();
3824  
3825 for (i = 0; types[i]; i++)
3826 for (j = 0; j < n_desktop_file_dirs; j++)
3827 desktop_file_dir_mime_lookup (&desktop_file_dirs[j], types[i], hits, blacklist);
3828  
3829 /* We will keep the hits past unlocking, so we must dup them */
3830 for (i = 0; i < hits->len; i++)
3831 hits->pdata[i] = g_strdup (hits->pdata[i]);
3832  
3833 desktop_file_dirs_unlock ();
3834  
3835 g_ptr_array_add (hits, NULL);
3836  
3837 g_ptr_array_free (blacklist, TRUE);
3838 g_strfreev (types);
3839  
3840 return (gchar **) g_ptr_array_free (hits, FALSE);
3841 }
3842  
3843 /**
3844 * g_app_info_get_recommended_for_type:
3845 * @content_type: the content type to find a #GAppInfo for
3846 *
3847 * Gets a list of recommended #GAppInfos for a given content type, i.e.
3848 * those applications which claim to support the given content type exactly,
3849 * and not by MIME type subclassing.
3850 * Note that the first application of the list is the last used one, i.e.
3851 * the last one for which g_app_info_set_as_last_used_for_type() has been
3852 * called.
3853 *
3854 * Returns: (element-type GAppInfo) (transfer full): #GList of #GAppInfos
3855 * for given @content_type or %NULL on error.
3856 *
3857 * Since: 2.28
3858 **/
3859 GList *
3860 g_app_info_get_recommended_for_type (const gchar *content_type)
3861 {
3862 gchar **desktop_ids;
3863 GList *infos;
3864 gint i;
3865  
3866 g_return_val_if_fail (content_type != NULL, NULL);
3867  
3868 desktop_ids = g_desktop_app_info_get_desktop_ids_for_content_type (content_type, FALSE);
3869  
3870 infos = NULL;
3871 for (i = 0; desktop_ids[i]; i++)
3872 {
3873 GDesktopAppInfo *info;
3874  
3875 info = g_desktop_app_info_new (desktop_ids[i]);
3876 if (info)
3877 infos = g_list_prepend (infos, info);
3878 }
3879  
3880 g_strfreev (desktop_ids);
3881  
3882 return g_list_reverse (infos);
3883 }
3884  
3885 /**
3886 * g_app_info_get_fallback_for_type:
3887 * @content_type: the content type to find a #GAppInfo for
3888 *
3889 * Gets a list of fallback #GAppInfos for a given content type, i.e.
3890 * those applications which claim to support the given content type
3891 * by MIME type subclassing and not directly.
3892 *
3893 * Returns: (element-type GAppInfo) (transfer full): #GList of #GAppInfos
3894 * for given @content_type or %NULL on error.
3895 *
3896 * Since: 2.28
3897 **/
3898 GList *
3899 g_app_info_get_fallback_for_type (const gchar *content_type)
3900 {
3901 gchar **recommended_ids;
3902 gchar **all_ids;
3903 GList *infos;
3904 gint i;
3905  
3906 g_return_val_if_fail (content_type != NULL, NULL);
3907  
3908 recommended_ids = g_desktop_app_info_get_desktop_ids_for_content_type (content_type, FALSE);
3909 all_ids = g_desktop_app_info_get_desktop_ids_for_content_type (content_type, TRUE);
3910  
3911 infos = NULL;
3912 for (i = 0; all_ids[i]; i++)
3913 {
3914 GDesktopAppInfo *info;
3915 gint j;
3916  
3917 /* Don't return the ones on the recommended list */
3918 for (j = 0; recommended_ids[j]; j++)
3919 if (g_str_equal (all_ids[i], recommended_ids[j]))
3920 break;
3921  
3922 if (recommended_ids[j])
3923 continue;
3924  
3925 info = g_desktop_app_info_new (all_ids[i]);
3926  
3927 if (info)
3928 infos = g_list_prepend (infos, info);
3929 }
3930  
3931 g_strfreev (recommended_ids);
3932 g_strfreev (all_ids);
3933  
3934 return g_list_reverse (infos);
3935 }
3936  
3937 /**
3938 * g_app_info_get_all_for_type:
3939 * @content_type: the content type to find a #GAppInfo for
3940 *
3941 * Gets a list of all #GAppInfos for a given content type,
3942 * including the recommended and fallback #GAppInfos. See
3943 * g_app_info_get_recommended_for_type() and
3944 * g_app_info_get_fallback_for_type().
3945 *
3946 * Returns: (element-type GAppInfo) (transfer full): #GList of #GAppInfos
3947 * for given @content_type or %NULL on error.
3948 **/
3949 GList *
3950 g_app_info_get_all_for_type (const char *content_type)
3951 {
3952 gchar **desktop_ids;
3953 GList *infos;
3954 gint i;
3955  
3956 g_return_val_if_fail (content_type != NULL, NULL);
3957  
3958 desktop_ids = g_desktop_app_info_get_desktop_ids_for_content_type (content_type, TRUE);
3959  
3960 infos = NULL;
3961 for (i = 0; desktop_ids[i]; i++)
3962 {
3963 GDesktopAppInfo *info;
3964  
3965 info = g_desktop_app_info_new (desktop_ids[i]);
3966 if (info)
3967 infos = g_list_prepend (infos, info);
3968 }
3969  
3970 g_strfreev (desktop_ids);
3971  
3972 return g_list_reverse (infos);
3973 }
3974  
3975 /**
3976 * g_app_info_reset_type_associations:
3977 * @content_type: a content type
3978 *
3979 * Removes all changes to the type associations done by
3980 * g_app_info_set_as_default_for_type(),
3981 * g_app_info_set_as_default_for_extension(),
3982 * g_app_info_add_supports_type() or
3983 * g_app_info_remove_supports_type().
3984 *
3985 * Since: 2.20
3986 */
3987 void
3988 g_app_info_reset_type_associations (const char *content_type)
3989 {
3990 update_mimeapps_list (NULL, content_type,
3991 UPDATE_MIME_NONE,
3992 NULL);
3993 }
3994  
3995 /**
3996 * g_app_info_get_default_for_type:
3997 * @content_type: the content type to find a #GAppInfo for
3998 * @must_support_uris: if %TRUE, the #GAppInfo is expected to
3999 * support URIs
4000 *
4001 * Gets the default #GAppInfo for a given content type.
4002 *
4003 * Returns: (transfer full): #GAppInfo for given @content_type or
4004 * %NULL on error.
4005 */
4006 GAppInfo *
4007 g_app_info_get_default_for_type (const char *content_type,
4008 gboolean must_support_uris)
4009 {
4010 GPtrArray *blacklist;
4011 GPtrArray *results;
4012 GAppInfo *info;
4013 gchar **types;
4014 gint i, j, k;
4015  
4016 g_return_val_if_fail (content_type != NULL, NULL);
4017  
4018 types = get_list_of_mimetypes (content_type, TRUE);
4019  
4020 blacklist = g_ptr_array_new ();
4021 results = g_ptr_array_new ();
4022 info = NULL;
4023  
4024 desktop_file_dirs_lock ();
4025  
4026 for (i = 0; types[i]; i++)
4027 {
4028 /* Collect all the default apps for this type */
4029 for (j = 0; j < n_desktop_file_dirs; j++)
4030 desktop_file_dir_default_lookup (&desktop_file_dirs[j], types[i], results);
4031  
4032 /* Consider the associations as well... */
4033 for (j = 0; j < n_desktop_file_dirs; j++)
4034 desktop_file_dir_mime_lookup (&desktop_file_dirs[j], types[i], results, blacklist);
4035  
4036 /* (If any), see if one of those apps is installed... */
4037 for (j = 0; j < results->len; j++)
4038 {
4039 const gchar *desktop_id = g_ptr_array_index (results, j);
4040  
4041 for (k = 0; k < n_desktop_file_dirs; k++)
4042 {
4043 info = (GAppInfo *) desktop_file_dir_get_app (&desktop_file_dirs[k], desktop_id);
4044  
4045 if (info)
4046 {
4047 if (!must_support_uris || g_app_info_supports_uris (info))
4048 goto out;
4049  
4050 g_clear_object (&info);
4051 }
4052 }
4053 }
4054  
4055 /* Reset the list, ready to try again with the next (parent)
4056 * mimetype, but keep the blacklist in place.
4057 */
4058 g_ptr_array_set_size (results, 0);
4059 }
4060  
4061 out:
4062 desktop_file_dirs_unlock ();
4063  
4064 g_ptr_array_unref (blacklist);
4065 g_ptr_array_unref (results);
4066 g_strfreev (types);
4067  
4068 return info;
4069 }
4070  
4071 /**
4072 * g_app_info_get_default_for_uri_scheme:
4073 * @uri_scheme: a string containing a URI scheme.
4074 *
4075 * Gets the default application for handling URIs with
4076 * the given URI scheme. A URI scheme is the initial part
4077 * of the URI, up to but not including the ':', e.g. "http",
4078 * "ftp" or "sip".
4079 *
4080 * Returns: (transfer full): #GAppInfo for given @uri_scheme or %NULL on error.
4081 */
4082 GAppInfo *
4083 g_app_info_get_default_for_uri_scheme (const char *uri_scheme)
4084 {
4085 GAppInfo *app_info;
4086 char *content_type, *scheme_down;
4087  
4088 scheme_down = g_ascii_strdown (uri_scheme, -1);
4089 content_type = g_strdup_printf ("x-scheme-handler/%s", scheme_down);
4090 g_free (scheme_down);
4091 app_info = g_app_info_get_default_for_type (content_type, FALSE);
4092 g_free (content_type);
4093  
4094 return app_info;
4095 }
4096  
4097 /* "Get all" API {{{2 */
4098  
4099 /**
4100 * g_desktop_app_info_get_implementations:
4101 * @interface: the name of the interface
4102 *
4103 * Gets all applications that implement @interface.
4104 *
4105 * An application implements an interface if that interface is listed in
4106 * the Implements= line of the desktop file of the application.
4107 *
4108 * Returns: (element-type GDesktopAppInfo) (transfer full): a list of #GDesktopAppInfo
4109 * objects.
4110 *
4111 * Since: 2.42
4112 **/
4113 GList *
4114 g_desktop_app_info_get_implementations (const gchar *interface)
4115 {
4116 GList *result = NULL;
4117 GList **ptr;
4118 gint i;
4119  
4120 desktop_file_dirs_lock ();
4121  
4122 for (i = 0; i < n_desktop_file_dirs; i++)
4123 desktop_file_dir_get_implementations (&desktop_file_dirs[i], &result, interface);
4124  
4125 desktop_file_dirs_unlock ();
4126  
4127 ptr = &result;
4128 while (*ptr)
4129 {
4130 gchar *name = (*ptr)->data;
4131 GDesktopAppInfo *app;
4132  
4133 app = g_desktop_app_info_new (name);
4134 g_free (name);
4135  
4136 if (app)
4137 {
4138 (*ptr)->data = app;
4139 ptr = &(*ptr)->next;
4140 }
4141 else
4142 *ptr = g_list_delete_link (*ptr, *ptr);
4143 }
4144  
4145 return result;
4146 }
4147  
4148 /**
4149 * g_desktop_app_info_search:
4150 * @search_string: the search string to use
4151 *
4152 * Searches desktop files for ones that match @search_string.
4153 *
4154 * The return value is an array of strvs. Each strv contains a list of
4155 * applications that matched @search_string with an equal score. The
4156 * outer list is sorted by score so that the first strv contains the
4157 * best-matching applications, and so on.
4158 * The algorithm for determining matches is undefined and may change at
4159 * any time.
4160 *
4161 * Returns: (array zero-terminated=1) (element-type GStrv) (transfer full): a
4162 * list of strvs. Free each item with g_strfreev() and free the outer
4163 * list with g_free().
4164 */
4165 gchar ***
4166 g_desktop_app_info_search (const gchar *search_string)
4167 {
4168 gchar **search_tokens;
4169 gint last_category = -1;
4170 gchar ***results;
4171 gint n_categories = 0;
4172 gint start_of_category;
4173 gint i, j;
4174  
4175 search_tokens = g_str_tokenize_and_fold (search_string, NULL, NULL);
4176  
4177 desktop_file_dirs_lock ();
4178  
4179 reset_total_search_results ();
4180  
4181 for (i = 0; i < n_desktop_file_dirs; i++)
4182 {
4183 for (j = 0; search_tokens[j]; j++)
4184 {
4185 desktop_file_dir_search (&desktop_file_dirs[i], search_tokens[j]);
4186 merge_token_results (j == 0);
4187 }
4188 merge_directory_results ();
4189 }
4190  
4191 sort_total_search_results ();
4192  
4193 /* Count the total number of unique categories */
4194 for (i = 0; i < static_total_results_size; i++)
4195 if (static_total_results[i].category != last_category)
4196 {
4197 last_category = static_total_results[i].category;
4198 n_categories++;
4199 }
4200  
4201 results = g_new (gchar **, n_categories + 1);
4202  
4203 /* Start loading into the results list */
4204 start_of_category = 0;
4205 for (i = 0; i < n_categories; i++)
4206 {
4207 gint n_items_in_category = 0;
4208 gint this_category;
4209 gint j;
4210  
4211 this_category = static_total_results[start_of_category].category;
4212  
4213 while (start_of_category + n_items_in_category < static_total_results_size &&
4214 static_total_results[start_of_category + n_items_in_category].category == this_category)
4215 n_items_in_category++;
4216  
4217 results[i] = g_new (gchar *, n_items_in_category + 1);
4218 for (j = 0; j < n_items_in_category; j++)
4219 results[i][j] = g_strdup (static_total_results[start_of_category + j].app_name);
4220 results[i][j] = NULL;
4221  
4222 start_of_category += n_items_in_category;
4223 }
4224 results[i] = NULL;
4225  
4226 desktop_file_dirs_unlock ();
4227  
4228 g_strfreev (search_tokens);
4229  
4230 return results;
4231 }
4232  
4233 /**
4234 * g_app_info_get_all:
4235 *
4236 * Gets a list of all of the applications currently registered
4237 * on this system.
4238 *
4239 * For desktop files, this includes applications that have
4240 * `NoDisplay=true` set or are excluded from display by means
4241 * of `OnlyShowIn` or `NotShowIn`. See g_app_info_should_show().
4242 * The returned list does not include applications which have
4243 * the `Hidden` key set.
4244 *
4245 * Returns: (element-type GAppInfo) (transfer full): a newly allocated #GList of references to #GAppInfos.
4246 **/
4247 GList *
4248 g_app_info_get_all (void)
4249 {
4250 GHashTable *apps;
4251 GHashTableIter iter;
4252 gpointer value;
4253 int i;
4254 GList *infos;
4255  
4256 apps = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
4257  
4258 desktop_file_dirs_lock ();
4259  
4260 for (i = 0; i < n_desktop_file_dirs; i++)
4261 desktop_file_dir_get_all (&desktop_file_dirs[i], apps);
4262  
4263 desktop_file_dirs_unlock ();
4264  
4265 infos = NULL;
4266 g_hash_table_iter_init (&iter, apps);
4267 while (g_hash_table_iter_next (&iter, NULL, &value))
4268 {
4269 if (value)
4270 infos = g_list_prepend (infos, value);
4271 }
4272  
4273 g_hash_table_destroy (apps);
4274  
4275 return infos;
4276 }
4277  
4278 /* GDesktopAppInfoLookup interface {{{2 */
4279  
4280 /**
4281 * GDesktopAppInfoLookup:
4282 *
4283 * #GDesktopAppInfoLookup is an opaque data structure and can only be accessed
4284 * using the following functions.
4285 **/
4286  
4287 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
4288  
4289 typedef GDesktopAppInfoLookupIface GDesktopAppInfoLookupInterface;
4290 G_DEFINE_INTERFACE (GDesktopAppInfoLookup, g_desktop_app_info_lookup, G_TYPE_OBJECT)
4291  
4292 static void
4293 g_desktop_app_info_lookup_default_init (GDesktopAppInfoLookupInterface *iface)
4294 {
4295 }
4296  
4297 /* "Get for mime type" APIs {{{2 */
4298  
4299 /**
4300 * g_desktop_app_info_lookup_get_default_for_uri_scheme:
4301 * @lookup: a #GDesktopAppInfoLookup
4302 * @uri_scheme: a string containing a URI scheme.
4303 *
4304 * Gets the default application for launching applications
4305 * using this URI scheme for a particular GDesktopAppInfoLookup
4306 * implementation.
4307 *
4308 * The GDesktopAppInfoLookup interface and this function is used
4309 * to implement g_app_info_get_default_for_uri_scheme() backends
4310 * in a GIO module. There is no reason for applications to use it
4311 * directly. Applications should use g_app_info_get_default_for_uri_scheme().
4312 *
4313 * Returns: (transfer full): #GAppInfo for given @uri_scheme or %NULL on error.
4314 *
4315 * Deprecated: The #GDesktopAppInfoLookup interface is deprecated and unused by gio.
4316 */
4317 GAppInfo *
4318 g_desktop_app_info_lookup_get_default_for_uri_scheme (GDesktopAppInfoLookup *lookup,
4319 const char *uri_scheme)
4320 {
4321 GDesktopAppInfoLookupIface *iface;
4322  
4323 g_return_val_if_fail (G_IS_DESKTOP_APP_INFO_LOOKUP (lookup), NULL);
4324  
4325 iface = G_DESKTOP_APP_INFO_LOOKUP_GET_IFACE (lookup);
4326  
4327 return (* iface->get_default_for_uri_scheme) (lookup, uri_scheme);
4328 }
4329  
4330 G_GNUC_END_IGNORE_DEPRECATIONS
4331  
4332 /* Misc getter APIs {{{2 */
4333  
4334 /**
4335 * g_desktop_app_info_get_startup_wm_class:
4336 * @info: a #GDesktopAppInfo that supports startup notify
4337 *
4338 * Retrieves the StartupWMClass field from @info. This represents the
4339 * WM_CLASS property of the main window of the application, if launched
4340 * through @info.
4341 *
4342 * Returns: (transfer none): the startup WM class, or %NULL if none is set
4343 * in the desktop file.
4344 *
4345 * Since: 2.34
4346 */
4347 const char *
4348 g_desktop_app_info_get_startup_wm_class (GDesktopAppInfo *info)
4349 {
4350 g_return_val_if_fail (G_IS_DESKTOP_APP_INFO (info), NULL);
4351  
4352 return info->startup_wm_class;
4353 }
4354  
4355 /**
4356 * g_desktop_app_info_get_string:
4357 * @info: a #GDesktopAppInfo
4358 * @key: the key to look up
4359 *
4360 * Looks up a string value in the keyfile backing @info.
4361 *
4362 * The @key is looked up in the "Desktop Entry" group.
4363 *
4364 * Returns: a newly allocated string, or %NULL if the key
4365 * is not found
4366 *
4367 * Since: 2.36
4368 */
4369 char *
4370 g_desktop_app_info_get_string (GDesktopAppInfo *info,
4371 const char *key)
4372 {
4373 g_return_val_if_fail (G_IS_DESKTOP_APP_INFO (info), NULL);
4374  
4375 return g_key_file_get_string (info->keyfile,
4376 G_KEY_FILE_DESKTOP_GROUP, key, NULL);
4377 }
4378  
4379 /**
4380 * g_desktop_app_info_get_boolean:
4381 * @info: a #GDesktopAppInfo
4382 * @key: the key to look up
4383 *
4384 * Looks up a boolean value in the keyfile backing @info.
4385 *
4386 * The @key is looked up in the "Desktop Entry" group.
4387 *
4388 * Returns: the boolean value, or %FALSE if the key
4389 * is not found
4390 *
4391 * Since: 2.36
4392 */
4393 gboolean
4394 g_desktop_app_info_get_boolean (GDesktopAppInfo *info,
4395 const char *key)
4396 {
4397 g_return_val_if_fail (G_IS_DESKTOP_APP_INFO (info), FALSE);
4398  
4399 return g_key_file_get_boolean (info->keyfile,
4400 G_KEY_FILE_DESKTOP_GROUP, key, NULL);
4401 }
4402  
4403 /**
4404 * g_desktop_app_info_has_key:
4405 * @info: a #GDesktopAppInfo
4406 * @key: the key to look up
4407 *
4408 * Returns whether @key exists in the "Desktop Entry" group
4409 * of the keyfile backing @info.
4410 *
4411 * Returns: %TRUE if the @key exists
4412 *
4413 * Since: 2.36
4414 */
4415 gboolean
4416 g_desktop_app_info_has_key (GDesktopAppInfo *info,
4417 const char *key)
4418 {
4419 g_return_val_if_fail (G_IS_DESKTOP_APP_INFO (info), FALSE);
4420  
4421 return g_key_file_has_key (info->keyfile,
4422 G_KEY_FILE_DESKTOP_GROUP, key, NULL);
4423 }
4424  
4425 /* Desktop actions support {{{2 */
4426  
4427 /**
4428 * g_desktop_app_info_list_actions:
4429 * @info: a #GDesktopAppInfo
4430 *
4431 * Returns the list of "additional application actions" supported on the
4432 * desktop file, as per the desktop file specification.
4433 *
4434 * As per the specification, this is the list of actions that are
4435 * explicitly listed in the "Actions" key of the [Desktop Entry] group.
4436 *
4437 * Returns: (array zero-terminated=1) (element-type utf8) (transfer none): a list of strings, always non-%NULL
4438 *
4439 * Since: 2.38
4440 **/
4441 const gchar * const *
4442 g_desktop_app_info_list_actions (GDesktopAppInfo *info)
4443 {
4444 g_return_val_if_fail (G_IS_DESKTOP_APP_INFO (info), NULL);
4445  
4446 return (const gchar **) info->actions;
4447 }
4448  
4449 static gboolean
4450 app_info_has_action (GDesktopAppInfo *info,
4451 const gchar *action_name)
4452 {
4453 gint i;
4454  
4455 for (i = 0; info->actions[i]; i++)
4456 if (g_str_equal (info->actions[i], action_name))
4457 return TRUE;
4458  
4459 return FALSE;
4460 }
4461  
4462 /**
4463 * g_desktop_app_info_get_action_name:
4464 * @info: a #GDesktopAppInfo
4465 * @action_name: the name of the action as from
4466 * g_desktop_app_info_list_actions()
4467 *
4468 * Gets the user-visible display name of the "additional application
4469 * action" specified by @action_name.
4470 *
4471 * This corresponds to the "Name" key within the keyfile group for the
4472 * action.
4473 *
4474 * Returns: (transfer full): the locale-specific action name
4475 *
4476 * Since: 2.38
4477 */
4478 gchar *
4479 g_desktop_app_info_get_action_name (GDesktopAppInfo *info,
4480 const gchar *action_name)
4481 {
4482 gchar *group_name;
4483 gchar *result;
4484  
4485 g_return_val_if_fail (G_IS_DESKTOP_APP_INFO (info), NULL);
4486 g_return_val_if_fail (action_name != NULL, NULL);
4487 g_return_val_if_fail (app_info_has_action (info, action_name), NULL);
4488  
4489 group_name = g_strdup_printf ("Desktop Action %s", action_name);
4490 result = g_key_file_get_locale_string (info->keyfile, group_name, "Name", NULL, NULL);
4491 g_free (group_name);
4492  
4493 /* The spec says that the Name field must be given.
4494 *
4495 * If it's not, let's follow the behaviour of our get_name()
4496 * implementation above and never return %NULL.
4497 */
4498 if (result == NULL)
4499 result = g_strdup (_("Unnamed"));
4500  
4501 return result;
4502 }
4503  
4504 /**
4505 * g_desktop_app_info_launch_action:
4506 * @info: a #GDesktopAppInfo
4507 * @action_name: the name of the action as from
4508 * g_desktop_app_info_list_actions()
4509 * @launch_context: (allow-none): a #GAppLaunchContext
4510 *
4511 * Activates the named application action.
4512 *
4513 * You may only call this function on action names that were
4514 * returned from g_desktop_app_info_list_actions().
4515 *
4516 * Note that if the main entry of the desktop file indicates that the
4517 * application supports startup notification, and @launch_context is
4518 * non-%NULL, then startup notification will be used when activating the
4519 * action (and as such, invocation of the action on the receiving side
4520 * must signal the end of startup notification when it is completed).
4521 * This is the expected behaviour of applications declaring additional
4522 * actions, as per the desktop file specification.
4523 *
4524 * As with g_app_info_launch() there is no way to detect failures that
4525 * occur while using this function.
4526 *
4527 * Since: 2.38
4528 */
4529 void
4530 g_desktop_app_info_launch_action (GDesktopAppInfo *info,
4531 const gchar *action_name,
4532 GAppLaunchContext *launch_context)
4533 {
4534 GDBusConnection *session_bus;
4535  
4536 g_return_if_fail (G_IS_DESKTOP_APP_INFO (info));
4537 g_return_if_fail (action_name != NULL);
4538 g_return_if_fail (app_info_has_action (info, action_name));
4539  
4540 session_bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
4541  
4542 if (session_bus && info->app_id)
4543 {
4544 gchar *object_path;
4545  
4546 object_path = object_path_from_appid (info->app_id);
4547 g_dbus_connection_call (session_bus, info->app_id, object_path,
4548 "org.freedesktop.Application", "ActivateAction",
4549 g_variant_new ("(sav@a{sv})", action_name, NULL,
4550 g_desktop_app_info_make_platform_data (info, NULL, launch_context)),
4551 NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
4552 g_free (object_path);
4553 }
4554 else
4555 {
4556 gchar *group_name;
4557 gchar *exec_line;
4558  
4559 group_name = g_strdup_printf ("Desktop Action %s", action_name);
4560 exec_line = g_key_file_get_string (info->keyfile, group_name, "Exec", NULL);
4561 g_free (group_name);
4562  
4563 if (exec_line)
4564 g_desktop_app_info_launch_uris_with_spawn (info, session_bus, exec_line, NULL, launch_context,
4565 _SPAWN_FLAGS_DEFAULT, NULL, NULL, NULL, NULL, NULL);
4566 }
4567  
4568 if (session_bus != NULL)
4569 {
4570 g_dbus_connection_flush (session_bus, NULL, NULL, NULL);
4571 g_object_unref (session_bus);
4572 }
4573 }
4574 /* Epilogue {{{1 */
4575  
4576 /* vim:set foldmethod=marker: */