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