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 [ + + + - : 1379 : 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 : 703 : desktop_file_dir_ref (DesktopFileDir *dir)
169 : : {
170 : 703 : g_atomic_ref_count_inc (&dir->ref_count);
171 : :
172 : 703 : return dir;
173 : : }
174 : :
175 : : static void
176 : 650 : desktop_file_dir_unref (DesktopFileDir *dir)
177 : : {
178 [ + + ]: 650 : if (g_atomic_ref_count_dec (&dir->ref_count))
179 : : {
180 : 252 : desktop_file_dir_reset (dir);
181 : 252 : g_free (dir->path);
182 : 252 : g_free (dir);
183 : : }
184 : 650 : }
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 : 915 : 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 [ + + ]: 915 : if (g_access (dir->path, R_OK | X_OK) == 0)
208 : 147 : return NULL;
209 : :
210 : : /* Otherwise, try the parent directories until we find one. */
211 : 768 : parent = g_path_get_dirname (dir->path);
212 : :
213 [ + + ]: 1586 : while (g_access (parent, R_OK | X_OK) != 0)
214 : : {
215 : 818 : gchar *tmp = parent;
216 : :
217 : 818 : parent = g_path_get_dirname (tmp);
218 : :
219 : : /* If somehow we get to '/' or '.' then just stop... */
220 [ - + ]: 818 : if (g_str_equal (parent, tmp))
221 : : {
222 : 0 : g_free (tmp);
223 : 0 : break;
224 : : }
225 : :
226 : 818 : g_free (tmp);
227 : : }
228 : :
229 : 768 : return parent;
230 : : }
231 : :
232 : : static void
233 : 477 : desktop_file_dir_changed (GFileMonitor *monitor,
234 : : GFile *file,
235 : : GFile *other_file,
236 : : GFileMonitorEvent event_type,
237 : : gpointer user_data)
238 : : {
239 : 477 : DesktopFileDir *dir = user_data;
240 : 477 : 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 : 477 : g_mutex_lock (&desktop_file_dir_lock);
254 : :
255 [ + + ]: 477 : if (dir->alternatively_watching)
256 : : {
257 : : gchar *alternative_dir;
258 : :
259 : 404 : alternative_dir = desktop_file_dir_get_alternative_dir (dir);
260 [ + - + + ]: 404 : do_nothing = alternative_dir && g_str_equal (dir->alternatively_watching, alternative_dir);
261 : 404 : g_free (alternative_dir);
262 : : }
263 : :
264 [ + + ]: 477 : if (!do_nothing)
265 : 314 : desktop_file_dir_reset (dir);
266 : :
267 : 477 : g_mutex_unlock (&desktop_file_dir_lock);
268 : :
269 : : /* Notify anyone else who may be interested */
270 [ + + ]: 477 : if (!do_nothing)
271 : 314 : g_app_info_monitor_fire ();
272 : 477 : }
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 : 511 : get_lowercase_current_desktops (void)
357 : : {
358 : : static gchar **result;
359 : :
360 [ + + + - : 511 : 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 : 511 : 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 : 131 : compare_results (gconstpointer a,
554 : : gconstpointer b)
555 : : {
556 : 131 : const struct search_result *ra = a;
557 : 131 : const struct search_result *rb = b;
558 : :
559 [ + + ]: 131 : if (ra->app_name < rb->app_name)
560 : : {
561 : 46 : return -1;
562 : : }
563 [ + + ]: 85 : else if (ra->app_name > rb->app_name)
564 : : {
565 : 68 : 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 : 3 : return ra->match_type - rb->match_type;
591 : :
592 : 6 : 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 [ + + ]: 34 : 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 [ + + ]: 26 : 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 : 285 : get_apps_from_dir (GHashTable **apps,
757 : : const char *dirname,
758 : : const char *prefix)
759 : : {
760 : : const char *basename;
761 : : GDir *dir;
762 : :
763 : 285 : dir = g_dir_open (dirname, 0, NULL);
764 : :
765 [ + + ]: 285 : if (dir == NULL)
766 : 141 : return;
767 : :
768 [ + + ]: 1510 : while ((basename = g_dir_read_name (dir)) != NULL)
769 : : {
770 : : gchar *filename;
771 : :
772 : 1366 : filename = g_build_filename (dirname, basename, NULL);
773 : :
774 [ + - - + : 1366 : if (g_str_has_suffix (basename, ".desktop"))
+ + + + ]
775 : : {
776 : : gchar *app_name;
777 : :
778 : 1261 : app_name = g_strconcat (prefix, basename, NULL);
779 : :
780 [ + + ]: 1261 : if (*apps == NULL)
781 : 96 : *apps = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
782 : :
783 : 1261 : g_hash_table_insert (*apps, app_name, g_strdup (filename));
784 : : }
785 [ + + ]: 105 : 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 : 1366 : 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 : 10007 : desktop_file_dir_unindexed_get_tweaks (DesktopFileDir *dir,
821 : : const gchar *mime_type)
822 : : {
823 : : UnindexedMimeTweaks *tweaks;
824 : : gchar *unaliased_type;
825 : :
826 : 10007 : unaliased_type = _g_unix_content_type_unalias (mime_type);
827 : 10007 : tweaks = g_hash_table_lookup (dir->mime_tweaks, unaliased_type);
828 : :
829 [ + + ]: 10007 : if (tweaks == NULL)
830 : : {
831 : 9916 : tweaks = g_slice_new0 (UnindexedMimeTweaks);
832 : 9916 : g_hash_table_insert (dir->mime_tweaks, unaliased_type, tweaks);
833 : : }
834 : : else
835 : 91 : g_free (unaliased_type);
836 : :
837 : 10007 : return tweaks;
838 : : }
839 : :
840 : : /* consumes 'to_add' */
841 : : static void
842 : 10007 : expand_strv (gchar ***strv_ptr,
843 : : gchar **to_add,
844 : : gchar * const *blocklist)
845 : 10 : {
846 : : guint strv_len, add_len;
847 : : gchar **strv;
848 : : guint i, j;
849 : :
850 [ + + ]: 10007 : if (!*strv_ptr)
851 : : {
852 : 9951 : *strv_ptr = to_add;
853 : 9951 : return;
854 : : }
855 : :
856 : 56 : strv = *strv_ptr;
857 : 56 : strv_len = g_strv_length (strv);
858 : 56 : add_len = g_strv_length (to_add);
859 : 56 : strv = g_renew (gchar *, strv, strv_len + add_len + 1);
860 : :
861 [ + + ]: 132 : for (i = 0; to_add[i]; i++)
862 : : {
863 : : /* Don't add blocklisted strings */
864 [ - + ]: 76 : 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 [ + + ]: 104 : for (j = 0; j < strv_len; j++)
871 [ + + ]: 94 : if (g_str_equal (to_add[i], strv[j]))
872 : 66 : goto no_add;
873 : :
874 : 10 : strv[strv_len++] = to_add[i];
875 : 10 : continue;
876 : :
877 : 66 : no_add:
878 : 66 : g_free (to_add[i]);
879 : : }
880 : :
881 : 56 : strv[strv_len] = NULL;
882 : 56 : *strv_ptr = strv;
883 : :
884 : 56 : g_free (to_add);
885 : : }
886 : :
887 : : static void
888 : 1114 : 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 : 1114 : key_file = g_key_file_new ();
900 [ + + ]: 1114 : if (!g_key_file_load_from_file (key_file, filename, G_KEY_FILE_NONE, NULL))
901 : : {
902 : 1007 : g_key_file_free (key_file);
903 : 1007 : 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 [ + + ]: 9705 : 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 : 95 : 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 [ + + ]: 433 : for (i = 0; mime_types[i] != NULL; i++)
963 : : {
964 : 394 : desktop_file_ids = g_key_file_get_string_list (key_file, DEFAULT_APPLICATIONS_GROUP, mime_types[i], NULL, NULL);
965 : :
966 [ + - ]: 394 : if (desktop_file_ids)
967 : : {
968 : 394 : tweaks = desktop_file_dir_unindexed_get_tweaks (dir, mime_types[i]);
969 : 394 : expand_strv (&tweaks->defaults, desktop_file_ids, NULL);
970 : : }
971 : : }
972 : :
973 : 39 : g_strfreev (mime_types);
974 : : }
975 : :
976 : 107 : g_key_file_free (key_file);
977 : : }
978 : :
979 : : static void
980 : 511 : desktop_file_dir_unindexed_read_mimeapps_lists (DesktopFileDir *dir)
981 : : {
982 : : const gchar * const *desktops;
983 : : gchar *filename;
984 : : gint i;
985 : :
986 : 511 : 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 : 511 : desktops = get_lowercase_current_desktops ();
996 [ + + ]: 622 : 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 : 511 : filename = g_strdup_printf ("%s/mimeapps.list", dir->path);
1005 : 511 : desktop_file_dir_unindexed_read_mimeapps_list (dir, filename, ADDED_ASSOCIATIONS_GROUP, TRUE);
1006 : 511 : 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 [ + + ]: 511 : if (dir->is_config)
1012 : 265 : 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 : 246 : filename = g_strdup_printf ("%s/defaults.list", dir->path);
1020 : 246 : desktop_file_dir_unindexed_read_mimeapps_list (dir, filename, ADDED_ASSOCIATIONS_GROUP, FALSE);
1021 : 246 : 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 : 246 : filename = g_strdup_printf ("%s/mimeinfo.cache", dir->path);
1027 : 246 : desktop_file_dir_unindexed_read_mimeapps_list (dir, filename, MIME_CACHE_GROUP, TRUE);
1028 : 246 : g_free (filename);
1029 : : }
1030 : :
1031 : : static void
1032 : 511 : desktop_file_dir_unindexed_init (DesktopFileDir *dir)
1033 : : {
1034 [ + + ]: 511 : if (!dir->is_config)
1035 : 246 : get_apps_from_dir (&dir->app_names, dir->path, "");
1036 : :
1037 : 511 : desktop_file_dir_unindexed_read_mimeapps_lists (dir);
1038 : 511 : }
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 : 346 : array_contains (GPtrArray *array,
1283 : : const gchar *str)
1284 : : {
1285 : : guint i;
1286 : :
1287 [ + + ]: 427 : for (i = 0; i < array->len; i++)
1288 [ + + ]: 204 : if (g_str_equal (array->pdata[i], str))
1289 : 123 : return TRUE;
1290 : :
1291 : 223 : return FALSE;
1292 : : }
1293 : :
1294 : : static void
1295 : 538 : 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 : 538 : tweaks = g_hash_table_lookup (dir->mime_tweaks, mime_type);
1304 : :
1305 [ + + ]: 538 : if (!tweaks)
1306 : 443 : 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 : 334 : 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 : 334 : tweaks = g_hash_table_lookup (dir->mime_tweaks, mime_type);
1342 : :
1343 [ + + + + ]: 334 : if (!tweaks || !tweaks->defaults)
1344 : 297 : 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 : 240 : desktop_file_dir_new (const gchar *data_dir)
1379 : : {
1380 : 240 : DesktopFileDir *dir = g_new0 (DesktopFileDir, 1);
1381 : :
1382 : 240 : g_atomic_ref_count_init (&dir->ref_count);
1383 : 240 : dir->path = g_build_filename (data_dir, "applications", NULL);
1384 : :
1385 : 240 : 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 : 239 : desktop_file_dir_new_for_config (const gchar *config_dir)
1399 : : {
1400 : 239 : DesktopFileDir *dir = g_new0 (DesktopFileDir, 1);
1401 : :
1402 : 239 : g_atomic_ref_count_init (&dir->ref_count);
1403 : 239 : dir->path = g_strdup (config_dir);
1404 : 239 : dir->is_config = TRUE;
1405 : :
1406 : 239 : 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 : 670 : desktop_file_dir_reset (DesktopFileDir *dir)
1417 : : {
1418 [ + + ]: 670 : if (dir->alternatively_watching)
1419 : : {
1420 : 241 : g_free (dir->alternatively_watching);
1421 : 241 : dir->alternatively_watching = NULL;
1422 : : }
1423 : :
1424 [ + + ]: 670 : if (dir->monitor)
1425 : : {
1426 : 314 : g_signal_handlers_disconnect_by_func (dir->monitor, desktop_file_dir_changed, dir);
1427 : 314 : g_file_monitor_cancel (dir->monitor);
1428 : 314 : g_object_unref (dir->monitor);
1429 : 314 : dir->monitor = NULL;
1430 : : }
1431 : :
1432 [ + + ]: 670 : if (dir->app_names)
1433 : : {
1434 : 24 : g_hash_table_unref (dir->app_names);
1435 : 24 : dir->app_names = NULL;
1436 : : }
1437 : :
1438 [ - + ]: 670 : if (dir->memory_index)
1439 : : {
1440 : 0 : g_hash_table_unref (dir->memory_index);
1441 : 0 : dir->memory_index = NULL;
1442 : : }
1443 : :
1444 [ + + ]: 670 : if (dir->mime_tweaks)
1445 : : {
1446 : 314 : g_hash_table_unref (dir->mime_tweaks);
1447 : 314 : dir->mime_tweaks = NULL;
1448 : : }
1449 : :
1450 [ - + ]: 670 : if (dir->memory_implementations)
1451 : : {
1452 : 0 : g_hash_table_unref (dir->memory_implementations);
1453 : 0 : dir->memory_implementations = NULL;
1454 : : }
1455 : :
1456 : 670 : dir->is_setup = FALSE;
1457 : 670 : }
1458 : :
1459 : : static void
1460 : 314 : closure_notify_cb (gpointer data,
1461 : : GClosure *closure)
1462 : : {
1463 : 314 : DesktopFileDir *dir = data;
1464 : 314 : desktop_file_dir_unref (dir);
1465 : 314 : }
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 : 511 : desktop_file_dir_init (DesktopFileDir *dir)
1477 : : {
1478 : : const gchar *watch_dir;
1479 : :
1480 : 511 : g_assert (!dir->is_setup);
1481 : :
1482 : 511 : g_assert (!dir->alternatively_watching);
1483 : 511 : g_assert (!dir->monitor);
1484 : :
1485 : 511 : dir->alternatively_watching = desktop_file_dir_get_alternative_dir (dir);
1486 [ + + ]: 511 : 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 : 511 : dir->monitor = g_local_file_monitor_new_in_worker (watch_dir, TRUE, G_FILE_MONITOR_NONE,
1495 : : desktop_file_dir_changed,
1496 : 511 : desktop_file_dir_ref (dir),
1497 : : closure_notify_cb, NULL);
1498 : :
1499 : 511 : desktop_file_dir_unindexed_init (dir);
1500 : :
1501 : 511 : dir->is_setup = TRUE;
1502 : 511 : }
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 : 538 : desktop_file_dir_mime_lookup (DesktopFileDir *dir,
1559 : : const gchar *mime_type,
1560 : : GPtrArray *hits,
1561 : : GPtrArray *blocklist)
1562 : : {
1563 : 538 : desktop_file_dir_unindexed_mime_lookup (dir, mime_type, hits, blocklist);
1564 : 538 : }
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 : 334 : desktop_file_dir_default_lookup (DesktopFileDir *dir,
1576 : : const gchar *mime_type,
1577 : : GPtrArray *results)
1578 : : {
1579 : 334 : desktop_file_dir_unindexed_default_lookup (dir, mime_type, results);
1580 : 334 : }
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 : 226 : desktop_file_dirs_lock (void)
1608 : : {
1609 : : guint i;
1610 : 226 : const gchar *user_config_dir = g_get_user_config_dir ();
1611 : :
1612 : 226 : 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 [ + + + + ]: 398 : if (desktop_file_dirs_config_dir != NULL &&
1617 : 172 : g_strcmp0 (desktop_file_dirs_config_dir, user_config_dir) != 0)
1618 : : {
1619 : 42 : 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 : 42 : g_ptr_array_set_size (desktop_file_dirs, 0);
1623 : 42 : g_clear_pointer (&desktop_file_dir_user_config, desktop_file_dir_unref);
1624 : 42 : g_clear_pointer (&desktop_file_dir_user_data, desktop_file_dir_unref);
1625 : : }
1626 : :
1627 [ + + + + ]: 226 : if (desktop_file_dirs == NULL || desktop_file_dirs->len == 0)
1628 : : {
1629 : : const char * const *dirs;
1630 : : gint i;
1631 : :
1632 [ + + ]: 96 : 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 : 96 : desktop_file_dir_user_config = desktop_file_dir_new_for_config (user_config_dir);
1637 : 96 : 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 : 96 : dirs = g_get_system_config_dirs ();
1641 [ + + ]: 239 : for (i = 0; dirs[i]; i++)
1642 : 143 : 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 : 96 : desktop_file_dir_user_data = desktop_file_dir_new (g_get_user_data_dir ());
1646 : 96 : 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 : 96 : dirs = g_get_system_data_dirs ();
1650 [ + + ]: 240 : for (i = 0; dirs[i]; i++)
1651 : 144 : 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 : 96 : desktop_file_dirs_config_dir = user_config_dir;
1656 : : }
1657 : :
1658 [ + + ]: 1471 : for (i = 0; i < desktop_file_dirs->len; i++)
1659 [ + + ]: 1245 : if (!((DesktopFileDir *) g_ptr_array_index (desktop_file_dirs, i))->is_setup)
1660 : 511 : desktop_file_dir_init (g_ptr_array_index (desktop_file_dirs, i));
1661 : 226 : }
1662 : :
1663 : : static void
1664 : 226 : desktop_file_dirs_unlock (void)
1665 : : {
1666 : 226 : g_mutex_unlock (&desktop_file_dir_lock);
1667 : 226 : }
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 #GDesktopAppInfo
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 [ + + ]: 801 : for (i = 0; i < desktop_file_dirs->len; i++)
1837 : : {
1838 : 781 : DesktopFileDir *dir = g_ptr_array_index (desktop_file_dirs, i);
1839 : : GHashTable *app_names;
1840 : : GHashTableIter iter;
1841 : : gpointer key, value;
1842 : :
1843 : 781 : app_names = dir->app_names;
1844 : :
1845 [ + + ]: 781 : if (!app_names)
1846 : 569 : continue;
1847 : :
1848 : 212 : g_hash_table_iter_init (&iter, app_names);
1849 [ + + ]: 1448 : while (g_hash_table_iter_next (&iter, &key, &value))
1850 : : {
1851 [ + + ]: 1403 : if (!strcmp (value, self->filename))
1852 : : {
1853 : 167 : desktop_id = g_strdup (key);
1854 : 167 : break;
1855 : : }
1856 : : }
1857 : :
1858 [ + + ]: 212 : 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 #GKeyFile
2068 : : *
2069 : : * Creates a new #GDesktopAppInfo.
2070 : : *
2071 : : * Returns: (nullable): a new #GDesktopAppInfo 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 #GDesktopAppInfo.
2099 : : *
2100 : : * Returns: (nullable): a new #GDesktopAppInfo 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 #GDesktopAppInfo 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 #GDesktopAppInfo, or %NULL if no desktop
2133 : : * 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 #GDesktopAppInfo.
2252 : : *
2253 : : * A desktop file is hidden if the Hidden key in it is
2254 : : * set to True.
2255 : : *
2256 : : * Returns: %TRUE if hidden, %FALSE otherwise.
2257 : : **/
2258 : : gboolean
2259 : 67 : g_desktop_app_info_get_is_hidden (GDesktopAppInfo *info)
2260 : : {
2261 : 67 : return info->hidden;
2262 : : }
2263 : :
2264 : : /**
2265 : : * g_desktop_app_info_get_filename:
2266 : : * @info: a #GDesktopAppInfo
2267 : : *
2268 : : * When @info was created from a known filename, return it. In some
2269 : : * situations such as the #GDesktopAppInfo returned from
2270 : : * g_desktop_app_info_new_from_keyfile(), this function will return %NULL.
2271 : : *
2272 : : * Returns: (nullable) (type filename): The full path to the file for @info,
2273 : : * or %NULL if not known.
2274 : : * Since: 2.24
2275 : : */
2276 : : const char *
2277 : 2 : g_desktop_app_info_get_filename (GDesktopAppInfo *info)
2278 : : {
2279 : 2 : return info->filename;
2280 : : }
2281 : :
2282 : : static const char *
2283 : 6 : g_desktop_app_info_get_description (GAppInfo *appinfo)
2284 : : {
2285 : 6 : GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
2286 : :
2287 : 6 : return info->comment;
2288 : : }
2289 : :
2290 : : static const char *
2291 : 1 : g_desktop_app_info_get_executable (GAppInfo *appinfo)
2292 : : {
2293 : 1 : GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
2294 : :
2295 : 1 : return info->binary;
2296 : : }
2297 : :
2298 : : static const char *
2299 : 10 : g_desktop_app_info_get_commandline (GAppInfo *appinfo)
2300 : : {
2301 : 10 : GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
2302 : :
2303 : 10 : return info->exec;
2304 : : }
2305 : :
2306 : : static GIcon *
2307 : 1 : g_desktop_app_info_get_icon (GAppInfo *appinfo)
2308 : : {
2309 : 1 : GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
2310 : :
2311 : 1 : return info->icon;
2312 : : }
2313 : :
2314 : : /**
2315 : : * g_desktop_app_info_get_categories:
2316 : : * @info: a #GDesktopAppInfo
2317 : : *
2318 : : * Gets the categories from the desktop file.
2319 : : *
2320 : : * Returns: (nullable): The unparsed Categories key from the desktop file;
2321 : : * i.e. no attempt is made to split it by ';' or validate it.
2322 : : */
2323 : : const char *
2324 : 1 : g_desktop_app_info_get_categories (GDesktopAppInfo *info)
2325 : : {
2326 : 1 : return info->categories;
2327 : : }
2328 : :
2329 : : /**
2330 : : * g_desktop_app_info_get_keywords:
2331 : : * @info: a #GDesktopAppInfo
2332 : : *
2333 : : * Gets the keywords from the desktop file.
2334 : : *
2335 : : * Returns: (transfer none): The value of the Keywords key
2336 : : *
2337 : : * Since: 2.32
2338 : : */
2339 : : const char * const *
2340 : 1 : g_desktop_app_info_get_keywords (GDesktopAppInfo *info)
2341 : : {
2342 : 1 : return (const char * const *)info->keywords;
2343 : : }
2344 : :
2345 : : /**
2346 : : * g_desktop_app_info_get_generic_name:
2347 : : * @info: a #GDesktopAppInfo
2348 : : *
2349 : : * Gets the generic name from the desktop file.
2350 : : *
2351 : : * Returns: (nullable): The value of the GenericName key
2352 : : */
2353 : : const char *
2354 : 1 : g_desktop_app_info_get_generic_name (GDesktopAppInfo *info)
2355 : : {
2356 : 1 : return info->generic_name;
2357 : : }
2358 : :
2359 : : /**
2360 : : * g_desktop_app_info_get_nodisplay:
2361 : : * @info: a #GDesktopAppInfo
2362 : : *
2363 : : * Gets the value of the NoDisplay key, which helps determine if the
2364 : : * application info should be shown in menus. See
2365 : : * %G_KEY_FILE_DESKTOP_KEY_NO_DISPLAY and g_app_info_should_show().
2366 : : *
2367 : : * Returns: The value of the NoDisplay key
2368 : : *
2369 : : * Since: 2.30
2370 : : */
2371 : : gboolean
2372 : 1 : g_desktop_app_info_get_nodisplay (GDesktopAppInfo *info)
2373 : : {
2374 : 1 : return info->nodisplay;
2375 : : }
2376 : :
2377 : : /**
2378 : : * g_desktop_app_info_get_show_in:
2379 : : * @info: a #GDesktopAppInfo
2380 : : * @desktop_env: (nullable): a string specifying a desktop name
2381 : : *
2382 : : * Checks if the application info should be shown in menus that list available
2383 : : * applications for a specific name of the desktop, based on the
2384 : : * `OnlyShowIn` and `NotShowIn` keys.
2385 : : *
2386 : : * @desktop_env should typically be given as %NULL, in which case the
2387 : : * `XDG_CURRENT_DESKTOP` environment variable is consulted. If you want
2388 : : * to override the default mechanism then you may specify @desktop_env,
2389 : : * but this is not recommended.
2390 : : *
2391 : : * Note that g_app_info_should_show() for @info will include this check (with
2392 : : * %NULL for @desktop_env) as well as additional checks.
2393 : : *
2394 : : * Returns: %TRUE if the @info should be shown in @desktop_env according to the
2395 : : * `OnlyShowIn` and `NotShowIn` keys, %FALSE
2396 : : * otherwise.
2397 : : *
2398 : : * Since: 2.30
2399 : : */
2400 : : gboolean
2401 : 14 : g_desktop_app_info_get_show_in (GDesktopAppInfo *info,
2402 : : const gchar *desktop_env)
2403 : : {
2404 : 14 : const gchar *specified_envs[] = { desktop_env, NULL };
2405 : : const gchar * const *envs;
2406 : : gint i;
2407 : :
2408 : 14 : g_return_val_if_fail (G_IS_DESKTOP_APP_INFO (info), FALSE);
2409 : :
2410 [ - + ]: 14 : if (desktop_env)
2411 : 0 : envs = specified_envs;
2412 : : else
2413 : 14 : envs = get_current_desktops (NULL);
2414 : :
2415 [ + + ]: 17 : for (i = 0; envs[i]; i++)
2416 : : {
2417 : : gint j;
2418 : :
2419 [ + + ]: 12 : if (info->only_show_in)
2420 [ + + ]: 17 : for (j = 0; info->only_show_in[j]; j++)
2421 [ + + ]: 12 : if (g_str_equal (info->only_show_in[j], envs[i]))
2422 : 6 : return TRUE;
2423 : :
2424 [ + + ]: 6 : if (info->not_show_in)
2425 [ + + ]: 7 : for (j = 0; info->not_show_in[j]; j++)
2426 [ + + ]: 5 : if (g_str_equal (info->not_show_in[j], envs[i]))
2427 : 3 : return FALSE;
2428 : : }
2429 : :
2430 : 5 : return info->only_show_in == NULL;
2431 : : }
2432 : :
2433 : : /* Launching... {{{2 */
2434 : :
2435 : : static char *
2436 : 84 : expand_macro_single (char macro, const char *uri)
2437 : : {
2438 : : GFile *file;
2439 : 84 : char *result = NULL;
2440 : 84 : char *path = NULL;
2441 : : char *name;
2442 : :
2443 : 84 : file = g_file_new_for_uri (uri);
2444 : :
2445 [ + + - - : 84 : switch (macro)
- ]
2446 : : {
2447 : 3 : case 'u':
2448 : : case 'U':
2449 : 3 : result = g_shell_quote (uri);
2450 : 3 : break;
2451 : 81 : case 'f':
2452 : : case 'F':
2453 : 81 : path = g_file_get_path (file);
2454 [ + + ]: 81 : if (path)
2455 : 78 : result = g_shell_quote (path);
2456 : 81 : break;
2457 : 0 : case 'd':
2458 : : case 'D':
2459 : 0 : path = g_file_get_path (file);
2460 [ # # ]: 0 : if (path)
2461 : : {
2462 : 0 : name = g_path_get_dirname (path);
2463 : 0 : result = g_shell_quote (name);
2464 : 0 : g_free (name);
2465 : : }
2466 : 0 : break;
2467 : 0 : case 'n':
2468 : : case 'N':
2469 : 0 : path = g_file_get_path (file);
2470 [ # # ]: 0 : if (path)
2471 : : {
2472 : 0 : name = g_path_get_basename (path);
2473 : 0 : result = g_shell_quote (name);
2474 : 0 : g_free (name);
2475 : : }
2476 : 0 : break;
2477 : : }
2478 : :
2479 : 84 : g_object_unref (file);
2480 : 84 : g_free (path);
2481 : :
2482 : 84 : return result;
2483 : : }
2484 : :
2485 : : static char *
2486 : 84 : expand_macro_uri (char macro, const char *uri, gboolean force_file_uri, char force_file_uri_macro)
2487 : : {
2488 : 84 : char *expanded = NULL;
2489 : :
2490 : 84 : g_return_val_if_fail (uri != NULL, NULL);
2491 : :
2492 [ + + ]: 84 : if (!force_file_uri ||
2493 : : /* Pass URI if it contains an anchor */
2494 [ + + ]: 57 : strchr (uri, '#') != NULL)
2495 : : {
2496 : 30 : expanded = expand_macro_single (macro, uri);
2497 : : }
2498 : : else
2499 : : {
2500 : 54 : expanded = expand_macro_single (force_file_uri_macro, uri);
2501 [ - + ]: 54 : if (expanded == NULL)
2502 : 0 : expanded = expand_macro_single (macro, uri);
2503 : : }
2504 : :
2505 : 84 : return expanded;
2506 : : }
2507 : :
2508 : : static void
2509 : 176 : expand_macro (char macro,
2510 : : GString *exec,
2511 : : GDesktopAppInfo *info,
2512 : : GList **uri_list)
2513 : : {
2514 : 176 : GList *uris = *uri_list;
2515 : 176 : char *expanded = NULL;
2516 : : gboolean force_file_uri;
2517 : : char force_file_uri_macro;
2518 : : const char *uri;
2519 : :
2520 : 176 : g_return_if_fail (exec != NULL);
2521 : :
2522 : : /* On %u and %U, pass POSIX file path pointing to the URI via
2523 : : * the FUSE mount in ~/.gvfs. Note that if the FUSE daemon isn't
2524 : : * running or the URI doesn't have a POSIX file path via FUSE
2525 : : * we'll just pass the URI.
2526 : : */
2527 : 176 : force_file_uri_macro = macro;
2528 : 176 : force_file_uri = FALSE;
2529 [ + - ]: 176 : if (!info->no_fuse)
2530 : : {
2531 [ + + + ]: 176 : switch (macro)
2532 : : {
2533 : 55 : case 'u':
2534 : 55 : force_file_uri_macro = 'f';
2535 : 55 : force_file_uri = TRUE;
2536 : 55 : break;
2537 : 11 : case 'U':
2538 : 11 : force_file_uri_macro = 'F';
2539 : 11 : force_file_uri = TRUE;
2540 : 11 : break;
2541 : 110 : default:
2542 : 110 : break;
2543 : : }
2544 : : }
2545 : :
2546 [ + + + + : 176 : switch (macro)
+ + + - ]
2547 : : {
2548 : 85 : case 'u':
2549 : : case 'f':
2550 : : case 'd':
2551 : : case 'n':
2552 [ + + ]: 85 : if (uris)
2553 : : {
2554 : 78 : uri = uris->data;
2555 : 78 : expanded = expand_macro_uri (macro, uri,
2556 : : force_file_uri, force_file_uri_macro);
2557 [ + + ]: 78 : if (expanded)
2558 : : {
2559 : : g_string_append (exec, expanded);
2560 : 75 : g_free (expanded);
2561 : : }
2562 : 78 : uris = uris->next;
2563 : : }
2564 : :
2565 : 85 : break;
2566 : :
2567 : 11 : case 'U':
2568 : : case 'F':
2569 : : case 'D':
2570 : : case 'N':
2571 [ + + ]: 17 : while (uris)
2572 : : {
2573 : 6 : uri = uris->data;
2574 : 6 : expanded = expand_macro_uri (macro, uri,
2575 : : force_file_uri, force_file_uri_macro);
2576 [ + - ]: 6 : if (expanded)
2577 : : {
2578 : : g_string_append (exec, expanded);
2579 : 6 : g_free (expanded);
2580 : : }
2581 : :
2582 : 6 : uris = uris->next;
2583 : :
2584 [ + + + - ]: 6 : if (uris != NULL && expanded)
2585 : : g_string_append_c (exec, ' ');
2586 : : }
2587 : :
2588 : 11 : break;
2589 : :
2590 : 16 : case 'i':
2591 [ + - ]: 16 : if (info->icon_name)
2592 : : {
2593 [ + - ]: 16 : g_string_append (exec, "--icon ");
2594 : 16 : expanded = g_shell_quote (info->icon_name);
2595 : : g_string_append (exec, expanded);
2596 : 16 : g_free (expanded);
2597 : : }
2598 : 16 : break;
2599 : :
2600 : 16 : case 'c':
2601 [ + - ]: 16 : if (info->name)
2602 : : {
2603 : 16 : expanded = g_shell_quote (info->name);
2604 : : g_string_append (exec, expanded);
2605 : 16 : g_free (expanded);
2606 : : }
2607 : 16 : break;
2608 : :
2609 : 16 : case 'k':
2610 [ + + ]: 16 : if (info->filename)
2611 : : {
2612 : 7 : expanded = g_shell_quote (info->filename);
2613 : : g_string_append (exec, expanded);
2614 : 7 : g_free (expanded);
2615 : : }
2616 : 16 : break;
2617 : :
2618 : 16 : case 'm': /* deprecated */
2619 : 16 : break;
2620 : :
2621 [ + - ]: 16 : case '%':
2622 : : g_string_append_c (exec, '%');
2623 : 16 : break;
2624 : : }
2625 : :
2626 : 176 : *uri_list = uris;
2627 : : }
2628 : :
2629 : : static gboolean
2630 : 99 : expand_application_parameters (GDesktopAppInfo *info,
2631 : : const gchar *exec_line,
2632 : : GList **uris,
2633 : : int *argc,
2634 : : char ***argv,
2635 : : GError **error)
2636 : : {
2637 : 99 : GList *uri_list = *uris;
2638 : 99 : const char *p = exec_line;
2639 : : GString *expanded_exec;
2640 : : gboolean res;
2641 : :
2642 [ - + ]: 99 : if (exec_line == NULL)
2643 : : {
2644 : 0 : g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
2645 : : _("Desktop file didn’t specify Exec field"));
2646 : 0 : return FALSE;
2647 : : }
2648 : :
2649 : 99 : expanded_exec = g_string_new (NULL);
2650 : :
2651 [ + + ]: 3751 : while (*p)
2652 : : {
2653 [ + + + - ]: 3652 : if (p[0] == '%' && p[1] != '\0')
2654 : : {
2655 : 152 : expand_macro (p[1], expanded_exec, info, uris);
2656 : 152 : p++;
2657 : : }
2658 : : else
2659 [ + - ]: 3500 : g_string_append_c (expanded_exec, *p);
2660 : :
2661 : 3652 : p++;
2662 : : }
2663 : :
2664 : : /* No file substitutions */
2665 [ + + + + ]: 99 : if (uri_list == *uris && uri_list != NULL)
2666 : : {
2667 : : /* If there is no macro default to %f. This is also what KDE does */
2668 : : g_string_append_c (expanded_exec, ' ');
2669 : 24 : expand_macro ('f', expanded_exec, info, uris);
2670 : : }
2671 : :
2672 : 99 : res = g_shell_parse_argv (expanded_exec->str, argc, argv, error);
2673 : 99 : g_string_free (expanded_exec, TRUE);
2674 : 99 : return res;
2675 : : }
2676 : :
2677 : : static gboolean
2678 : 73 : prepend_terminal_to_vector (int *argc,
2679 : : char ***argv,
2680 : : const char *path,
2681 : : const char *working_dir)
2682 : : {
2683 : : #ifndef G_OS_WIN32
2684 : : char **real_argv;
2685 : : size_t real_argc;
2686 : : size_t i;
2687 : : size_t term_argc;
2688 : : char *found_terminal;
2689 : : char **the_argv;
2690 : : const char *term_arg;
2691 : : static const struct {
2692 : : const char *exec;
2693 : : const char *exec_arg;
2694 : : } known_terminals[] = {
2695 : : { "xdg-terminal-exec", NULL },
2696 : : { "kgx", "-e" },
2697 : : { "gnome-terminal", "--" },
2698 : : { "mate-terminal", "-x" },
2699 : : { "xfce4-terminal", "-x" },
2700 : : { "tilix", "-e" },
2701 : : { "konsole", "-e" },
2702 : : { "nxterm", "-e" },
2703 : : { "color-xterm", "-e" },
2704 : : { "rxvt", "-e" },
2705 : : { "dtterm", "-e" },
2706 : : { "xterm", "-e" }
2707 : : };
2708 : :
2709 : 73 : g_return_val_if_fail (argc != NULL, FALSE);
2710 : 73 : g_return_val_if_fail (argv != NULL, FALSE);
2711 : :
2712 : : /* sanity */
2713 [ - + ]: 73 : if(*argv == NULL)
2714 : 0 : *argc = 0;
2715 : :
2716 : 73 : the_argv = *argv;
2717 : :
2718 : : /* compute size if not given */
2719 [ - + ]: 73 : if (*argc < 0)
2720 : : {
2721 [ # # ]: 0 : for ((*argc) = 0; the_argv[*argc] != NULL; (*argc)++)
2722 : : ;
2723 : : }
2724 : :
2725 [ + + ]: 481 : for (i = 0, found_terminal = NULL; i < G_N_ELEMENTS (known_terminals); i++)
2726 : : {
2727 : 480 : found_terminal = GLIB_PRIVATE_CALL (g_find_program_for_path) (known_terminals[i].exec,
2728 : : path, working_dir);
2729 [ + + ]: 480 : if (found_terminal != NULL)
2730 : : {
2731 : 72 : term_arg = known_terminals[i].exec_arg;
2732 : 72 : break;
2733 : : }
2734 : : }
2735 : :
2736 [ + + ]: 73 : if (found_terminal == NULL)
2737 : : {
2738 : 1 : g_debug ("Couldn’t find a known terminal");
2739 : 1 : return FALSE;
2740 : : }
2741 : :
2742 : : /* check if the terminal require an option */
2743 [ + + ]: 72 : term_argc = term_arg ? 2 : 1;
2744 : :
2745 : 72 : real_argc = term_argc + *argc;
2746 : 72 : real_argv = g_new (char *, real_argc + 1);
2747 : :
2748 : 72 : i = 0;
2749 : 72 : real_argv[i++] = found_terminal;
2750 : :
2751 [ + + ]: 72 : if (term_arg)
2752 : 132 : real_argv[i++] = g_strdup (term_arg);
2753 : :
2754 : 72 : g_assert (i == term_argc);
2755 [ + + ]: 288 : for (int j = 0; j < *argc; j++)
2756 : 216 : real_argv[i++] = the_argv[j];
2757 : :
2758 : 72 : real_argv[i] = NULL;
2759 : :
2760 : 72 : g_free (*argv);
2761 : 72 : *argv = real_argv;
2762 : 72 : *argc = real_argc;
2763 : :
2764 : 72 : return TRUE;
2765 : : #else
2766 : : return FALSE;
2767 : : #endif /* G_OS_WIN32 */
2768 : : }
2769 : :
2770 : : static GList *
2771 : 80 : create_files_for_uris (GList *uris)
2772 : : {
2773 : : GList *res;
2774 : : GList *iter;
2775 : :
2776 : 80 : res = NULL;
2777 : :
2778 [ + + ]: 153 : for (iter = uris; iter; iter = iter->next)
2779 : : {
2780 : 73 : GFile *file = g_file_new_for_uri ((char *)iter->data);
2781 : 73 : res = g_list_prepend (res, file);
2782 : : }
2783 : :
2784 : 80 : return g_list_reverse (res);
2785 : : }
2786 : :
2787 : : static void
2788 : 93 : notify_desktop_launch (GDBusConnection *session_bus,
2789 : : GDesktopAppInfo *info,
2790 : : long pid,
2791 : : const char *display,
2792 : : const char *sn_id,
2793 : : GList *uris)
2794 : : {
2795 : : GDBusMessage *msg;
2796 : : GVariantBuilder uri_variant;
2797 : : GVariantBuilder extras_variant;
2798 : : GList *iter;
2799 : : const char *desktop_file_id;
2800 : : const char *gio_desktop_file;
2801 : :
2802 [ + + ]: 93 : if (session_bus == NULL)
2803 : 76 : return;
2804 : :
2805 : 17 : g_variant_builder_init (&uri_variant, G_VARIANT_TYPE ("as"));
2806 [ + + ]: 26 : for (iter = uris; iter; iter = iter->next)
2807 : 9 : g_variant_builder_add (&uri_variant, "s", iter->data);
2808 : :
2809 : 17 : g_variant_builder_init (&extras_variant, G_VARIANT_TYPE ("a{sv}"));
2810 [ - + - - ]: 17 : if (sn_id != NULL && g_utf8_validate (sn_id, -1, NULL))
2811 : 0 : g_variant_builder_add (&extras_variant, "{sv}",
2812 : : "startup-id",
2813 : : g_variant_new ("s",
2814 : : sn_id));
2815 : 17 : gio_desktop_file = g_getenv ("GIO_LAUNCHED_DESKTOP_FILE");
2816 [ - + ]: 17 : if (gio_desktop_file != NULL)
2817 : 0 : g_variant_builder_add (&extras_variant, "{sv}",
2818 : : "origin-desktop-file",
2819 : : g_variant_new_bytestring (gio_desktop_file));
2820 [ + - ]: 17 : if (g_get_prgname () != NULL)
2821 : 17 : g_variant_builder_add (&extras_variant, "{sv}",
2822 : : "origin-prgname",
2823 : : g_variant_new_bytestring (g_get_prgname ()));
2824 : 17 : g_variant_builder_add (&extras_variant, "{sv}",
2825 : : "origin-pid",
2826 : : g_variant_new ("x",
2827 : 17 : (gint64)getpid ()));
2828 : :
2829 [ + + ]: 17 : if (info->filename)
2830 : 7 : desktop_file_id = info->filename;
2831 [ - + ]: 10 : else if (info->desktop_id)
2832 : 0 : desktop_file_id = info->desktop_id;
2833 : : else
2834 : 10 : desktop_file_id = "";
2835 : :
2836 : 17 : msg = g_dbus_message_new_signal ("/org/gtk/gio/DesktopAppInfo",
2837 : : "org.gtk.gio.DesktopAppInfo",
2838 : : "Launched");
2839 [ - + ]: 17 : g_dbus_message_set_body (msg, g_variant_new ("(@aysxasa{sv})",
2840 : : g_variant_new_bytestring (desktop_file_id),
2841 : : display ? display : "",
2842 : : (gint64)pid,
2843 : : &uri_variant,
2844 : : &extras_variant));
2845 : 17 : g_dbus_connection_send_message (session_bus,
2846 : : msg, 0,
2847 : : NULL,
2848 : : NULL);
2849 : 17 : g_object_unref (msg);
2850 : : }
2851 : :
2852 : : static void
2853 : 79 : emit_launch_started (GAppLaunchContext *context,
2854 : : GDesktopAppInfo *info,
2855 : : const gchar *startup_id)
2856 : : {
2857 : : GVariantBuilder builder;
2858 : 79 : GVariant *platform_data = NULL;
2859 : :
2860 [ + + ]: 79 : if (startup_id)
2861 : : {
2862 : 5 : g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY);
2863 : 5 : g_variant_builder_add (&builder, "{sv}",
2864 : : "startup-notification-id",
2865 : : g_variant_new_string (startup_id));
2866 : 5 : platform_data = g_variant_ref_sink (g_variant_builder_end (&builder));
2867 : : }
2868 : 79 : g_signal_emit_by_name (context, "launch-started", info, platform_data);
2869 : 79 : g_clear_pointer (&platform_data, g_variant_unref);
2870 : 79 : }
2871 : :
2872 : : #define _SPAWN_FLAGS_DEFAULT (G_SPAWN_SEARCH_PATH)
2873 : :
2874 : : static gboolean
2875 : 62 : g_desktop_app_info_launch_uris_with_spawn (GDesktopAppInfo *info,
2876 : : GDBusConnection *session_bus,
2877 : : const gchar *exec_line,
2878 : : GList *uris,
2879 : : GAppLaunchContext *launch_context,
2880 : : GSpawnFlags spawn_flags,
2881 : : GSpawnChildSetupFunc user_setup,
2882 : : gpointer user_setup_data,
2883 : : GDesktopAppLaunchCallback pid_callback,
2884 : : gpointer pid_callback_data,
2885 : : gint stdin_fd,
2886 : : gint stdout_fd,
2887 : : gint stderr_fd,
2888 : : GError **error)
2889 : : {
2890 : 62 : gboolean completed = FALSE;
2891 : : GList *old_uris;
2892 : : GList *dup_uris;
2893 : :
2894 : : char **argv, **envp;
2895 : : int argc;
2896 : :
2897 : 62 : g_return_val_if_fail (info != NULL, FALSE);
2898 : :
2899 : 62 : argv = NULL;
2900 : :
2901 [ + + ]: 62 : if (launch_context)
2902 : 41 : envp = g_app_launch_context_get_environment (launch_context);
2903 : : else
2904 : 21 : envp = g_get_environ ();
2905 : :
2906 : : /* The GList* passed to expand_application_parameters() will be modified
2907 : : * internally by expand_macro(), so we need to pass a copy of it instead,
2908 : : * and also use that copy to control the exit condition of the loop below.
2909 : : */
2910 : 62 : dup_uris = uris;
2911 : : do
2912 : : {
2913 : : GPid pid;
2914 : : GList *launched_uris;
2915 : : GList *iter;
2916 : 99 : char *sn_id = NULL;
2917 : : char **wrapped_argv;
2918 : : int i;
2919 : :
2920 : 99 : old_uris = dup_uris;
2921 [ - + ]: 99 : if (!expand_application_parameters (info, exec_line, &dup_uris, &argc, &argv, error))
2922 : 6 : goto out;
2923 : :
2924 : : /* Get the subset of URIs we're launching with this process */
2925 : 99 : launched_uris = NULL;
2926 [ + + + + ]: 183 : for (iter = old_uris; iter != NULL && iter != dup_uris; iter = iter->next)
2927 : 84 : launched_uris = g_list_prepend (launched_uris, iter->data);
2928 : 99 : launched_uris = g_list_reverse (launched_uris);
2929 : :
2930 [ + + + + ]: 172 : if (info->terminal && !prepend_terminal_to_vector (&argc, &argv,
2931 : 73 : g_environ_getenv (envp, "PATH"),
2932 : 73 : info->path))
2933 : : {
2934 : 1 : g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
2935 : : _("Unable to find terminal required for application"));
2936 : 1 : goto out;
2937 : : }
2938 : :
2939 [ + + ]: 98 : if (info->filename)
2940 : 12 : envp = g_environ_setenv (envp,
2941 : : "GIO_LAUNCHED_DESKTOP_FILE",
2942 : 12 : info->filename,
2943 : : TRUE);
2944 : :
2945 : 98 : sn_id = NULL;
2946 [ + + ]: 98 : if (launch_context)
2947 : : {
2948 : 77 : GList *launched_files = create_files_for_uris (launched_uris);
2949 : :
2950 [ + + ]: 77 : if (info->startup_notify)
2951 : : {
2952 : 4 : sn_id = g_app_launch_context_get_startup_notify_id (launch_context,
2953 : 4 : G_APP_INFO (info),
2954 : : launched_files);
2955 [ + - ]: 4 : if (sn_id)
2956 : : {
2957 : 4 : envp = g_environ_setenv (envp, "DESKTOP_STARTUP_ID", sn_id, TRUE);
2958 : 4 : envp = g_environ_setenv (envp, "XDG_ACTIVATION_TOKEN", sn_id, TRUE);
2959 : : }
2960 : : }
2961 : :
2962 : 77 : g_list_free_full (launched_files, g_object_unref);
2963 : :
2964 : 77 : emit_launch_started (launch_context, info, sn_id);
2965 : : }
2966 : :
2967 : 98 : g_assert (argc > 0);
2968 : :
2969 [ + + + + ]: 189 : if (!g_path_is_absolute (argv[0]) ||
2970 [ + + ]: 181 : !g_file_test (argv[0], G_FILE_TEST_IS_EXECUTABLE) ||
2971 : 90 : g_file_test (argv[0], G_FILE_TEST_IS_DIR))
2972 : : {
2973 : 9 : char *program = g_steal_pointer (&argv[0]);
2974 : 9 : char *program_path = NULL;
2975 : :
2976 [ + + ]: 9 : if (!g_path_is_absolute (program))
2977 : : {
2978 : 7 : const char *env_path = g_environ_getenv (envp, "PATH");
2979 : :
2980 : 14 : program_path = GLIB_PRIVATE_CALL (g_find_program_for_path) (program,
2981 : : env_path,
2982 : 7 : info->path);
2983 : : }
2984 : :
2985 [ + + ]: 9 : if (program_path)
2986 : : {
2987 : 5 : argv[0] = g_steal_pointer (&program_path);
2988 : : }
2989 : : else
2990 : : {
2991 [ + + ]: 4 : if (sn_id)
2992 : 1 : g_app_launch_context_launch_failed (launch_context, sn_id);
2993 : :
2994 : 4 : g_set_error (error, G_SPAWN_ERROR, G_SPAWN_ERROR_NOENT,
2995 : : _("Program ‘%s’ not found in $PATH"),
2996 : : program);
2997 : :
2998 : 4 : g_free (program);
2999 : 4 : g_clear_pointer (&sn_id, g_free);
3000 : 4 : g_clear_list (&launched_uris, NULL);
3001 : 4 : goto out;
3002 : : }
3003 : :
3004 : 5 : g_free (program);
3005 : : }
3006 : :
3007 [ + + + - : 94 : if (g_once_init_enter_pointer (&gio_launch_desktop_path))
+ + ]
3008 : : {
3009 : 2 : const gchar *tmp = NULL;
3010 : 2 : gboolean is_setuid = GLIB_PRIVATE_CALL (g_check_setuid) ();
3011 : :
3012 : : /* Allow test suite to specify path to gio-launch-desktop */
3013 [ + - ]: 2 : if (!is_setuid)
3014 : 2 : tmp = g_getenv ("GIO_LAUNCH_DESKTOP");
3015 : :
3016 : : /* Allow build system to specify path to gio-launch-desktop */
3017 [ - + - - ]: 2 : if (tmp == NULL && g_file_test (GIO_LAUNCH_DESKTOP, G_FILE_TEST_IS_EXECUTABLE))
3018 : 0 : tmp = GIO_LAUNCH_DESKTOP;
3019 : :
3020 : : /* Fall back on usual searching in $PATH */
3021 [ - + ]: 2 : if (tmp == NULL)
3022 : 0 : tmp = "gio-launch-desktop";
3023 : 2 : g_once_init_leave_pointer (&gio_launch_desktop_path, tmp);
3024 : : }
3025 : :
3026 : 94 : wrapped_argv = g_new (char *, argc + 2);
3027 : 94 : wrapped_argv[0] = g_strdup (gio_launch_desktop_path);
3028 : :
3029 [ + + ]: 607 : for (i = 0; i < argc; i++)
3030 : 513 : wrapped_argv[i + 1] = g_steal_pointer (&argv[i]);
3031 : :
3032 : 94 : wrapped_argv[i + 1] = NULL;
3033 : 94 : g_free (argv);
3034 : 94 : argv = NULL;
3035 : :
3036 [ + + ]: 94 : if (!g_spawn_async_with_fds (info->path,
3037 : : wrapped_argv,
3038 : : envp,
3039 : : spawn_flags,
3040 : : user_setup,
3041 : : user_setup_data,
3042 : : &pid,
3043 : : stdin_fd,
3044 : : stdout_fd,
3045 : : stderr_fd,
3046 : : error))
3047 : : {
3048 [ + - ]: 1 : if (sn_id)
3049 : 1 : g_app_launch_context_launch_failed (launch_context, sn_id);
3050 : :
3051 : 1 : g_free (sn_id);
3052 : 1 : g_list_free (launched_uris);
3053 : 1 : g_clear_pointer (&wrapped_argv, g_strfreev);
3054 : :
3055 : 1 : goto out;
3056 : : }
3057 : :
3058 [ - + ]: 93 : if (pid_callback != NULL)
3059 : 0 : pid_callback (info, pid, pid_callback_data);
3060 : :
3061 [ + + ]: 93 : if (launch_context != NULL)
3062 : : {
3063 : : GVariantBuilder builder;
3064 : : GVariant *platform_data;
3065 : :
3066 : 75 : g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY);
3067 : 75 : g_variant_builder_add (&builder, "{sv}", "pid", g_variant_new_int32 (pid));
3068 [ + + ]: 75 : if (sn_id)
3069 : 2 : g_variant_builder_add (&builder, "{sv}", "startup-notification-id", g_variant_new_string (sn_id));
3070 : 75 : platform_data = g_variant_ref_sink (g_variant_builder_end (&builder));
3071 : 75 : g_signal_emit_by_name (launch_context, "launched", info, platform_data);
3072 : 75 : g_variant_unref (platform_data);
3073 : : }
3074 : :
3075 : 93 : notify_desktop_launch (session_bus,
3076 : : info,
3077 : : pid,
3078 : : NULL,
3079 : : sn_id,
3080 : : launched_uris);
3081 : :
3082 : 93 : g_free (sn_id);
3083 : 93 : g_list_free (launched_uris);
3084 : :
3085 : 93 : g_strfreev (wrapped_argv);
3086 : 93 : wrapped_argv = NULL;
3087 : : }
3088 [ + + ]: 93 : while (dup_uris != NULL);
3089 : :
3090 : 56 : completed = TRUE;
3091 : :
3092 : 62 : out:
3093 : 62 : g_strfreev (argv);
3094 : 62 : g_strfreev (envp);
3095 : :
3096 : 62 : return completed;
3097 : : }
3098 : :
3099 : : static gchar *
3100 : 11 : object_path_from_appid (const gchar *appid)
3101 : : {
3102 : : gchar *appid_path, *iter;
3103 : :
3104 : 11 : appid_path = g_strconcat ("/", appid, NULL);
3105 [ + + ]: 302 : for (iter = appid_path; *iter; iter++)
3106 : : {
3107 [ + + ]: 291 : if (*iter == '.')
3108 : 35 : *iter = '/';
3109 : :
3110 [ - + ]: 291 : if (*iter == '-')
3111 : 0 : *iter = '_';
3112 : : }
3113 : :
3114 : 11 : return appid_path;
3115 : : }
3116 : :
3117 : : static GVariant *
3118 : 11 : g_desktop_app_info_make_platform_data (GDesktopAppInfo *info,
3119 : : GList *uris,
3120 : : GAppLaunchContext *launch_context)
3121 : : {
3122 : : GVariantBuilder builder;
3123 : :
3124 : 11 : g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
3125 : :
3126 [ + + ]: 11 : if (launch_context)
3127 : : {
3128 : 3 : GList *launched_files = create_files_for_uris (uris);
3129 : :
3130 [ + - ]: 3 : if (info->startup_notify)
3131 : : {
3132 : : gchar *sn_id;
3133 : :
3134 : 3 : sn_id = g_app_launch_context_get_startup_notify_id (launch_context, G_APP_INFO (info), launched_files);
3135 [ + + ]: 3 : if (sn_id)
3136 : : {
3137 : 2 : g_variant_builder_add (&builder, "{sv}", "desktop-startup-id", g_variant_new_string (sn_id));
3138 : 2 : g_variant_builder_add (&builder, "{sv}", "activation-token", g_variant_new_take_string (g_steal_pointer (&sn_id)));
3139 : : }
3140 : : }
3141 : :
3142 : 3 : g_list_free_full (launched_files, g_object_unref);
3143 : : }
3144 : :
3145 : 11 : return g_variant_builder_end (&builder);
3146 : : }
3147 : :
3148 : : typedef struct
3149 : : {
3150 : : GDesktopAppInfo *info; /* (owned) */
3151 : : GAppLaunchContext *launch_context; /* (owned) (nullable) */
3152 : : GAsyncReadyCallback callback;
3153 : : gchar *startup_id; /* (owned) (nullable) */
3154 : : gpointer user_data;
3155 : : } LaunchUrisWithDBusData;
3156 : :
3157 : : static void
3158 : 6 : launch_uris_with_dbus_data_free (LaunchUrisWithDBusData *data)
3159 : : {
3160 : 6 : g_clear_object (&data->info);
3161 : 6 : g_clear_object (&data->launch_context);
3162 : 6 : g_free (data->startup_id);
3163 : :
3164 : 6 : g_free (data);
3165 : 6 : }
3166 : :
3167 : : static void
3168 : 6 : launch_uris_with_dbus_signal_cb (GObject *object,
3169 : : GAsyncResult *result,
3170 : : gpointer user_data)
3171 : : {
3172 : 6 : LaunchUrisWithDBusData *data = user_data;
3173 : : GVariantBuilder builder;
3174 : :
3175 [ + + ]: 6 : if (data->launch_context)
3176 : : {
3177 [ + + ]: 2 : if (g_task_had_error (G_TASK (result)))
3178 : : {
3179 [ - + ]: 1 : if (data->startup_id != NULL)
3180 : 0 : g_app_launch_context_launch_failed (data->launch_context, data->startup_id);
3181 : : }
3182 : : else
3183 : : {
3184 : : GVariant *platform_data;
3185 : :
3186 : 1 : g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY);
3187 : : /* the docs guarantee `pid` will be set, but we can’t
3188 : : * easily know it for a D-Bus process, so set it to zero */
3189 : 1 : g_variant_builder_add (&builder, "{sv}", "pid", g_variant_new_int32 (0));
3190 [ + - ]: 1 : if (data->startup_id)
3191 : 1 : g_variant_builder_add (&builder, "{sv}",
3192 : : "startup-notification-id",
3193 : 1 : g_variant_new_string (data->startup_id));
3194 : 1 : platform_data = g_variant_ref_sink (g_variant_builder_end (&builder));
3195 : 1 : g_signal_emit_by_name (data->launch_context,
3196 : : "launched",
3197 : : data->info,
3198 : : platform_data);
3199 : 1 : g_variant_unref (platform_data);
3200 : : }
3201 : : }
3202 : :
3203 [ + + ]: 6 : if (data->callback)
3204 : 3 : data->callback (object, result, data->user_data);
3205 [ + - ]: 3 : else if (!g_task_had_error (G_TASK (result)))
3206 : 3 : g_variant_unref (g_dbus_connection_call_finish (G_DBUS_CONNECTION (object),
3207 : : result, NULL));
3208 : :
3209 : 6 : launch_uris_with_dbus_data_free (data);
3210 : 6 : }
3211 : :
3212 : : static void
3213 : 6 : launch_uris_with_dbus (GDesktopAppInfo *info,
3214 : : GDBusConnection *session_bus,
3215 : : GList *uris,
3216 : : GAppLaunchContext *launch_context,
3217 : : GCancellable *cancellable,
3218 : : GAsyncReadyCallback callback,
3219 : : gpointer user_data)
3220 : : {
3221 : : GVariant *platform_data;
3222 : : GVariantBuilder builder;
3223 : : GVariantDict dict;
3224 : : gchar *object_path;
3225 : : LaunchUrisWithDBusData *data;
3226 : :
3227 : 6 : g_variant_builder_init (&builder, G_VARIANT_TYPE_TUPLE);
3228 : :
3229 [ + + ]: 6 : if (uris)
3230 : : {
3231 : : GList *iter;
3232 : :
3233 : 3 : g_variant_builder_open (&builder, G_VARIANT_TYPE_STRING_ARRAY);
3234 [ + + ]: 7 : for (iter = uris; iter; iter = iter->next)
3235 : 4 : g_variant_builder_add (&builder, "s", iter->data);
3236 : 3 : g_variant_builder_close (&builder);
3237 : : }
3238 : :
3239 : 6 : platform_data = g_desktop_app_info_make_platform_data (info, uris, launch_context);
3240 : :
3241 : 6 : g_variant_builder_add_value (&builder, platform_data);
3242 : 6 : object_path = object_path_from_appid (info->app_id);
3243 : :
3244 : 6 : data = g_new0 (LaunchUrisWithDBusData, 1);
3245 : 6 : data->info = g_object_ref (info);
3246 : 6 : data->callback = callback;
3247 : 6 : data->user_data = user_data;
3248 [ + + ]: 6 : data->launch_context = launch_context ? g_object_ref (launch_context) : NULL;
3249 : 6 : g_variant_dict_init (&dict, platform_data);
3250 : 6 : g_variant_dict_lookup (&dict, "desktop-startup-id", "s", &data->startup_id);
3251 : :
3252 [ + + ]: 6 : if (launch_context)
3253 : 2 : emit_launch_started (launch_context, info, data->startup_id);
3254 : :
3255 [ + + ]: 6 : g_dbus_connection_call (session_bus, info->app_id, object_path, "org.freedesktop.Application",
3256 : : uris ? "Open" : "Activate", g_variant_builder_end (&builder),
3257 : : NULL, G_DBUS_CALL_FLAGS_NONE, -1,
3258 : : cancellable, launch_uris_with_dbus_signal_cb, g_steal_pointer (&data));
3259 : 6 : g_free (object_path);
3260 : :
3261 : 6 : g_variant_dict_clear (&dict);
3262 : 6 : }
3263 : :
3264 : : static gboolean
3265 : 6 : g_desktop_app_info_launch_uris_with_dbus (GDesktopAppInfo *info,
3266 : : GDBusConnection *session_bus,
3267 : : GList *uris,
3268 : : GAppLaunchContext *launch_context,
3269 : : GCancellable *cancellable,
3270 : : GAsyncReadyCallback callback,
3271 : : gpointer user_data)
3272 : : {
3273 : 6 : GList *ruris = uris;
3274 : 6 : char *app_id = NULL;
3275 : :
3276 : 6 : g_return_val_if_fail (info != NULL, FALSE);
3277 : :
3278 : : #ifdef G_OS_UNIX
3279 : 6 : app_id = g_desktop_app_info_get_string (info, "X-Flatpak");
3280 [ + + + - ]: 6 : if (app_id && *app_id)
3281 : : {
3282 : 2 : ruris = g_document_portal_add_documents (uris, app_id, NULL);
3283 [ - + ]: 2 : if (ruris == NULL)
3284 : 0 : ruris = uris;
3285 : : }
3286 : : #endif
3287 : :
3288 : 6 : launch_uris_with_dbus (info, session_bus, ruris, launch_context,
3289 : : cancellable, callback, user_data);
3290 : :
3291 [ + + ]: 6 : if (ruris != uris)
3292 : 2 : g_list_free_full (ruris, g_free);
3293 : :
3294 : 6 : g_free (app_id);
3295 : :
3296 : 6 : return TRUE;
3297 : : }
3298 : :
3299 : : static gboolean
3300 : 61 : g_desktop_app_info_launch_uris_internal (GAppInfo *appinfo,
3301 : : GList *uris,
3302 : : GAppLaunchContext *launch_context,
3303 : : GSpawnFlags spawn_flags,
3304 : : GSpawnChildSetupFunc user_setup,
3305 : : gpointer user_setup_data,
3306 : : GDesktopAppLaunchCallback pid_callback,
3307 : : gpointer pid_callback_data,
3308 : : gint stdin_fd,
3309 : : gint stdout_fd,
3310 : : gint stderr_fd,
3311 : : GError **error)
3312 : : {
3313 : 61 : GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
3314 : : GDBusConnection *session_bus;
3315 : 61 : gboolean success = TRUE;
3316 : :
3317 : 61 : session_bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
3318 : :
3319 [ + + + + ]: 61 : if (session_bus && info->app_id)
3320 : : /* This is non-blocking API. Similar to launching via fork()/exec()
3321 : : * we don't wait around to see if the program crashed during startup.
3322 : : * This is what startup-notification's job is...
3323 : : */
3324 : 3 : g_desktop_app_info_launch_uris_with_dbus (info, session_bus, uris, launch_context,
3325 : : NULL, NULL, NULL);
3326 : : else
3327 : 58 : success = g_desktop_app_info_launch_uris_with_spawn (info, session_bus, info->exec, uris, launch_context,
3328 : : spawn_flags, user_setup, user_setup_data,
3329 : : pid_callback, pid_callback_data,
3330 : : stdin_fd, stdout_fd, stderr_fd, error);
3331 : :
3332 [ + + ]: 61 : if (session_bus != NULL)
3333 : : {
3334 : : /* This asynchronous flush holds a reference until it completes,
3335 : : * which ensures that the following unref won't immediately kill
3336 : : * the connection if we were the initial owner.
3337 : : */
3338 : 16 : g_dbus_connection_flush (session_bus, NULL, NULL, NULL);
3339 : 16 : g_object_unref (session_bus);
3340 : : }
3341 : :
3342 : 61 : return success;
3343 : : }
3344 : :
3345 : : static gboolean
3346 : 58 : g_desktop_app_info_launch_uris (GAppInfo *appinfo,
3347 : : GList *uris,
3348 : : GAppLaunchContext *launch_context,
3349 : : GError **error)
3350 : : {
3351 : 58 : return g_desktop_app_info_launch_uris_internal (appinfo, uris,
3352 : : launch_context,
3353 : : _SPAWN_FLAGS_DEFAULT,
3354 : : NULL, NULL, NULL, NULL,
3355 : : -1, -1, -1,
3356 : : error);
3357 : : }
3358 : :
3359 : : typedef struct
3360 : : {
3361 : : GList *uris; /* (element-type utf8) (owned) (nullable) */
3362 : : GAppLaunchContext *context; /* (owned) (nullable) */
3363 : : } LaunchUrisData;
3364 : :
3365 : : static void
3366 : 4 : launch_uris_data_free (LaunchUrisData *data)
3367 : : {
3368 : 4 : g_clear_object (&data->context);
3369 : 4 : g_list_free_full (data->uris, g_free);
3370 : 4 : g_free (data);
3371 : 4 : }
3372 : :
3373 : : static void
3374 : 3 : launch_uris_with_dbus_cb (GObject *object,
3375 : : GAsyncResult *result,
3376 : : gpointer user_data)
3377 : : {
3378 : 3 : GTask *task = G_TASK (user_data);
3379 : 3 : GError *local_error = NULL;
3380 : : GVariant *ret;
3381 : :
3382 : 3 : ret = g_dbus_connection_call_finish (G_DBUS_CONNECTION (object), result, &local_error);
3383 [ + + ]: 3 : if (local_error != NULL)
3384 : : {
3385 : 1 : g_dbus_error_strip_remote_error (local_error);
3386 : 1 : g_task_return_error (task, g_steal_pointer (&local_error));
3387 : : }
3388 : : else
3389 : : {
3390 : 2 : g_task_return_boolean (task, TRUE);
3391 : 2 : g_variant_unref (ret);
3392 : : }
3393 : :
3394 : 3 : g_object_unref (task);
3395 : 3 : }
3396 : :
3397 : : static void
3398 : 0 : launch_uris_flush_cb (GObject *object,
3399 : : GAsyncResult *result,
3400 : : gpointer user_data)
3401 : : {
3402 : 0 : GTask *task = G_TASK (user_data);
3403 : :
3404 : 0 : g_dbus_connection_flush_finish (G_DBUS_CONNECTION (object), result, NULL);
3405 : 0 : g_task_return_boolean (task, TRUE);
3406 : 0 : g_object_unref (task);
3407 : 0 : }
3408 : :
3409 : : static void
3410 : 4 : launch_uris_bus_get_cb (GObject *object,
3411 : : GAsyncResult *result,
3412 : : gpointer user_data)
3413 : : {
3414 : 4 : GTask *task = G_TASK (user_data);
3415 : 4 : GDesktopAppInfo *info = G_DESKTOP_APP_INFO (g_task_get_source_object (task));
3416 : 4 : LaunchUrisData *data = g_task_get_task_data (task);
3417 : 4 : GCancellable *cancellable = g_task_get_cancellable (task);
3418 : : GDBusConnection *session_bus;
3419 : 4 : GError *local_error = NULL;
3420 : :
3421 : 4 : session_bus = g_bus_get_finish (result, NULL);
3422 : :
3423 [ + + + - ]: 4 : if (session_bus && info->app_id)
3424 : : {
3425 : : /* FIXME: The g_document_portal_add_documents() function, which is called
3426 : : * from the g_desktop_app_info_launch_uris_with_dbus() function, still
3427 : : * uses blocking calls.
3428 : : */
3429 : 3 : g_desktop_app_info_launch_uris_with_dbus (info, session_bus,
3430 : : data->uris, data->context,
3431 : : cancellable,
3432 : : launch_uris_with_dbus_cb,
3433 : : g_steal_pointer (&task));
3434 : : }
3435 : : else
3436 : : {
3437 : : /* FIXME: The D-Bus message from the notify_desktop_launch() function
3438 : : * can be still lost even if flush is called later. See:
3439 : : * https://gitlab.freedesktop.org/dbus/dbus/issues/72
3440 : : */
3441 : 1 : g_desktop_app_info_launch_uris_with_spawn (info, session_bus, info->exec,
3442 : : data->uris, data->context,
3443 : : _SPAWN_FLAGS_DEFAULT, NULL,
3444 : : NULL, NULL, NULL, -1, -1, -1,
3445 : : &local_error);
3446 [ - + ]: 1 : if (local_error != NULL)
3447 : : {
3448 : 0 : g_task_return_error (task, g_steal_pointer (&local_error));
3449 : 0 : g_object_unref (task);
3450 : : }
3451 [ - + ]: 1 : else if (session_bus)
3452 : 0 : g_dbus_connection_flush (session_bus,
3453 : : cancellable,
3454 : : launch_uris_flush_cb,
3455 : : g_steal_pointer (&task));
3456 : : else
3457 : : {
3458 : 1 : g_task_return_boolean (task, TRUE);
3459 : 1 : g_clear_object (&task);
3460 : : }
3461 : : }
3462 : :
3463 : 4 : g_clear_object (&session_bus);
3464 : 4 : }
3465 : :
3466 : : static void
3467 : 4 : g_desktop_app_info_launch_uris_async (GAppInfo *appinfo,
3468 : : GList *uris,
3469 : : GAppLaunchContext *context,
3470 : : GCancellable *cancellable,
3471 : : GAsyncReadyCallback callback,
3472 : : gpointer user_data)
3473 : : {
3474 : : GTask *task;
3475 : : LaunchUrisData *data;
3476 : :
3477 : 4 : task = g_task_new (appinfo, cancellable, callback, user_data);
3478 [ + - ]: 4 : g_task_set_source_tag (task, g_desktop_app_info_launch_uris_async);
3479 : :
3480 : 4 : data = g_new0 (LaunchUrisData, 1);
3481 : 4 : data->uris = g_list_copy_deep (uris, (GCopyFunc) g_strdup, NULL);
3482 : 4 : g_set_object (&data->context, context);
3483 : 4 : g_task_set_task_data (task, g_steal_pointer (&data), (GDestroyNotify) launch_uris_data_free);
3484 : :
3485 : 4 : g_bus_get (G_BUS_TYPE_SESSION, cancellable, launch_uris_bus_get_cb, task);
3486 : 4 : }
3487 : :
3488 : : static gboolean
3489 : 4 : g_desktop_app_info_launch_uris_finish (GAppInfo *appinfo,
3490 : : GAsyncResult *result,
3491 : : GError **error)
3492 : : {
3493 : 4 : g_return_val_if_fail (g_task_is_valid (result, appinfo), FALSE);
3494 : :
3495 : 4 : return g_task_propagate_boolean (G_TASK (result), error);
3496 : : }
3497 : :
3498 : : static gboolean
3499 : 2 : g_desktop_app_info_supports_uris (GAppInfo *appinfo)
3500 : : {
3501 : 2 : GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
3502 : :
3503 [ + - ]: 4 : return info->exec &&
3504 [ + + ]: 2 : ((strstr (info->exec, "%u") != NULL) ||
3505 [ - + ]: 1 : (strstr (info->exec, "%U") != NULL));
3506 : : }
3507 : :
3508 : : static gboolean
3509 : 2 : g_desktop_app_info_supports_files (GAppInfo *appinfo)
3510 : : {
3511 : 2 : GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
3512 : :
3513 [ + - ]: 4 : return info->exec &&
3514 [ + + ]: 2 : ((strstr (info->exec, "%f") != NULL) ||
3515 [ - + ]: 1 : (strstr (info->exec, "%F") != NULL));
3516 : : }
3517 : :
3518 : : static gboolean
3519 : 14 : g_desktop_app_info_launch (GAppInfo *appinfo,
3520 : : GList *files,
3521 : : GAppLaunchContext *launch_context,
3522 : : GError **error)
3523 : : {
3524 : : GList *uris;
3525 : : char *uri;
3526 : : gboolean res;
3527 : :
3528 : 14 : uris = NULL;
3529 [ + + ]: 21 : while (files)
3530 : : {
3531 : 7 : uri = g_file_get_uri (files->data);
3532 : 7 : uris = g_list_prepend (uris, uri);
3533 : 7 : files = files->next;
3534 : : }
3535 : :
3536 : 14 : uris = g_list_reverse (uris);
3537 : :
3538 : 14 : res = g_desktop_app_info_launch_uris (appinfo, uris, launch_context, error);
3539 : :
3540 : 14 : g_list_free_full (uris, g_free);
3541 : :
3542 : 14 : return res;
3543 : : }
3544 : :
3545 : : /**
3546 : : * g_desktop_app_info_launch_uris_as_manager_with_fds:
3547 : : * @appinfo: a #GDesktopAppInfo
3548 : : * @uris: (element-type utf8): List of URIs
3549 : : * @launch_context: (nullable): a #GAppLaunchContext
3550 : : * @spawn_flags: #GSpawnFlags, used for each process
3551 : : * @user_setup: (scope async) (nullable) (closure user_setup_data): a #GSpawnChildSetupFunc, used once
3552 : : * for each process.
3553 : : * @user_setup_data: User data for @user_setup
3554 : : * @pid_callback: (scope call) (nullable) (closure pid_callback_data): Callback for child processes
3555 : : * @pid_callback_data: User data for @callback
3556 : : * @stdin_fd: file descriptor to use for child's stdin, or -1
3557 : : * @stdout_fd: file descriptor to use for child's stdout, or -1
3558 : : * @stderr_fd: file descriptor to use for child's stderr, or -1
3559 : : * @error: return location for a #GError, or %NULL
3560 : : *
3561 : : * Equivalent to g_desktop_app_info_launch_uris_as_manager() but allows
3562 : : * you to pass in file descriptors for the stdin, stdout and stderr streams
3563 : : * of the launched process.
3564 : : *
3565 : : * If application launching occurs via some non-spawn mechanism (e.g. D-Bus
3566 : : * activation) then @stdin_fd, @stdout_fd and @stderr_fd are ignored.
3567 : : *
3568 : : * Returns: %TRUE on successful launch, %FALSE otherwise.
3569 : : *
3570 : : * Since: 2.58
3571 : : */
3572 : : gboolean
3573 : 3 : g_desktop_app_info_launch_uris_as_manager_with_fds (GDesktopAppInfo *appinfo,
3574 : : GList *uris,
3575 : : GAppLaunchContext *launch_context,
3576 : : GSpawnFlags spawn_flags,
3577 : : GSpawnChildSetupFunc user_setup,
3578 : : gpointer user_setup_data,
3579 : : GDesktopAppLaunchCallback pid_callback,
3580 : : gpointer pid_callback_data,
3581 : : gint stdin_fd,
3582 : : gint stdout_fd,
3583 : : gint stderr_fd,
3584 : : GError **error)
3585 : : {
3586 : 3 : return g_desktop_app_info_launch_uris_internal ((GAppInfo*)appinfo,
3587 : : uris,
3588 : : launch_context,
3589 : : spawn_flags,
3590 : : user_setup,
3591 : : user_setup_data,
3592 : : pid_callback,
3593 : : pid_callback_data,
3594 : : stdin_fd,
3595 : : stdout_fd,
3596 : : stderr_fd,
3597 : : error);
3598 : : }
3599 : :
3600 : : /**
3601 : : * g_desktop_app_info_launch_uris_as_manager:
3602 : : * @appinfo: a #GDesktopAppInfo
3603 : : * @uris: (element-type utf8): List of URIs
3604 : : * @launch_context: (nullable): a #GAppLaunchContext
3605 : : * @spawn_flags: #GSpawnFlags, used for each process
3606 : : * @user_setup: (scope async) (nullable): a #GSpawnChildSetupFunc, used once
3607 : : * for each process.
3608 : : * @user_setup_data: (closure user_setup) (nullable): User data for @user_setup
3609 : : * @pid_callback: (scope call) (nullable): Callback for child processes
3610 : : * @pid_callback_data: (closure pid_callback) (nullable): User data for @callback
3611 : : * @error: return location for a #GError, or %NULL
3612 : : *
3613 : : * This function performs the equivalent of g_app_info_launch_uris(),
3614 : : * but is intended primarily for operating system components that
3615 : : * launch applications. Ordinary applications should use
3616 : : * g_app_info_launch_uris().
3617 : : *
3618 : : * If the application is launched via GSpawn, then @spawn_flags, @user_setup
3619 : : * and @user_setup_data are used for the call to g_spawn_async().
3620 : : * Additionally, @pid_callback (with @pid_callback_data) will be called to
3621 : : * inform about the PID of the created process. See g_spawn_async_with_pipes()
3622 : : * for information on certain parameter conditions that can enable an
3623 : : * optimized posix_spawn() codepath to be used.
3624 : : *
3625 : : * If application launching occurs via some other mechanism (eg: D-Bus
3626 : : * activation) then @spawn_flags, @user_setup, @user_setup_data,
3627 : : * @pid_callback and @pid_callback_data are ignored.
3628 : : *
3629 : : * Returns: %TRUE on successful launch, %FALSE otherwise.
3630 : : */
3631 : : gboolean
3632 : 1 : g_desktop_app_info_launch_uris_as_manager (GDesktopAppInfo *appinfo,
3633 : : GList *uris,
3634 : : GAppLaunchContext *launch_context,
3635 : : GSpawnFlags spawn_flags,
3636 : : GSpawnChildSetupFunc user_setup,
3637 : : gpointer user_setup_data,
3638 : : GDesktopAppLaunchCallback pid_callback,
3639 : : gpointer pid_callback_data,
3640 : : GError **error)
3641 : : {
3642 : 1 : return g_desktop_app_info_launch_uris_as_manager_with_fds (appinfo,
3643 : : uris,
3644 : : launch_context,
3645 : : spawn_flags,
3646 : : user_setup,
3647 : : user_setup_data,
3648 : : pid_callback,
3649 : : pid_callback_data,
3650 : : -1, -1, -1,
3651 : : error);
3652 : : }
3653 : :
3654 : : /* OnlyShowIn API support {{{2 */
3655 : :
3656 : : /**
3657 : : * g_desktop_app_info_set_desktop_env:
3658 : : * @desktop_env: a string specifying what desktop this is
3659 : : *
3660 : : * Sets the name of the desktop that the application is running in.
3661 : : * This is used by g_app_info_should_show() and
3662 : : * g_desktop_app_info_get_show_in() to evaluate the
3663 : : * `OnlyShowIn` and `NotShowIn`
3664 : : * desktop entry fields.
3665 : : *
3666 : : * Should be called only once; subsequent calls are ignored.
3667 : : *
3668 : : * Deprecated:2.42:do not use this API. Since 2.42 the value of the
3669 : : * `XDG_CURRENT_DESKTOP` environment variable will be used.
3670 : : */
3671 : : void
3672 : 0 : g_desktop_app_info_set_desktop_env (const gchar *desktop_env)
3673 : : {
3674 : 0 : get_current_desktops (desktop_env);
3675 : 0 : }
3676 : :
3677 : : static gboolean
3678 : 14 : g_desktop_app_info_should_show (GAppInfo *appinfo)
3679 : : {
3680 : 14 : GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
3681 : :
3682 [ - + ]: 14 : if (info->nodisplay)
3683 : 0 : return FALSE;
3684 : :
3685 : 14 : return g_desktop_app_info_get_show_in (info, NULL);
3686 : : }
3687 : :
3688 : : /* mime types/default apps support {{{2 */
3689 : :
3690 : : typedef enum {
3691 : : CONF_DIR,
3692 : : APP_DIR,
3693 : : MIMETYPE_DIR
3694 : : } DirType;
3695 : :
3696 : : static char *
3697 : 126 : ensure_dir (DirType type,
3698 : : GError **error)
3699 : : {
3700 : : char *path, *display_name;
3701 : : int errsv;
3702 : :
3703 [ + + + - ]: 126 : switch (type)
3704 : : {
3705 : 107 : case CONF_DIR:
3706 : 107 : path = g_build_filename (g_get_user_config_dir (), NULL);
3707 : 107 : break;
3708 : :
3709 : 18 : case APP_DIR:
3710 : 18 : path = g_build_filename (g_get_user_data_dir (), "applications", NULL);
3711 : 18 : break;
3712 : :
3713 : 1 : case MIMETYPE_DIR:
3714 : 1 : path = g_build_filename (g_get_user_data_dir (), "mime", "packages", NULL);
3715 : 1 : break;
3716 : :
3717 : 0 : default:
3718 : : g_assert_not_reached ();
3719 : : }
3720 : :
3721 : 126 : g_debug ("%s: Ensuring %s", G_STRFUNC, path);
3722 : :
3723 : 126 : errno = 0;
3724 [ + - ]: 126 : if (g_mkdir_with_parents (path, 0700) == 0)
3725 : 126 : return path;
3726 : :
3727 : 0 : errsv = errno;
3728 : 0 : display_name = g_filename_display_name (path);
3729 [ # # ]: 0 : if (type == APP_DIR)
3730 : 0 : g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv),
3731 : : _("Can’t create user application configuration folder %s: %s"),
3732 : : display_name, g_strerror (errsv));
3733 : : else
3734 : 0 : g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv),
3735 : : _("Can’t create user MIME configuration folder %s: %s"),
3736 : : display_name, g_strerror (errsv));
3737 : :
3738 : 0 : g_free (display_name);
3739 : 0 : g_free (path);
3740 : :
3741 : 0 : return NULL;
3742 : : }
3743 : :
3744 : : static gboolean
3745 : 107 : update_mimeapps_list (const char *desktop_id,
3746 : : const char *content_type,
3747 : : UpdateMimeFlags flags,
3748 : : GError **error)
3749 : : {
3750 : : char *dirname, *filename, *string;
3751 : : GKeyFile *key_file;
3752 : : gboolean load_succeeded, res;
3753 : : char **old_list, **list;
3754 : : gsize length, data_size;
3755 : : char *data;
3756 : : int i, j, k;
3757 : : char **content_types;
3758 : :
3759 : : /* Don't add both at start and end */
3760 : 107 : g_assert (!((flags & UPDATE_MIME_SET_DEFAULT) &&
3761 : : (flags & UPDATE_MIME_SET_NON_DEFAULT)));
3762 : :
3763 : 107 : dirname = ensure_dir (CONF_DIR, error);
3764 [ - + ]: 107 : if (!dirname)
3765 : 0 : return FALSE;
3766 : :
3767 : 107 : filename = g_build_filename (dirname, "mimeapps.list", NULL);
3768 : 107 : g_free (dirname);
3769 : :
3770 : 107 : key_file = g_key_file_new ();
3771 : 107 : load_succeeded = g_key_file_load_from_file (key_file, filename, G_KEY_FILE_NONE, NULL);
3772 [ + + + + ]: 198 : if (!load_succeeded ||
3773 [ + - ]: 95 : (!g_key_file_has_group (key_file, ADDED_ASSOCIATIONS_GROUP) &&
3774 [ + - ]: 8 : !g_key_file_has_group (key_file, REMOVED_ASSOCIATIONS_GROUP) &&
3775 : 4 : !g_key_file_has_group (key_file, DEFAULT_APPLICATIONS_GROUP)))
3776 : : {
3777 : 20 : g_key_file_free (key_file);
3778 : 20 : key_file = g_key_file_new ();
3779 : : }
3780 : :
3781 [ + + ]: 107 : if (content_type)
3782 : : {
3783 : 95 : content_types = g_new (char *, 2);
3784 : 95 : content_types[0] = g_strdup (content_type);
3785 : 95 : content_types[1] = NULL;
3786 : : }
3787 : : else
3788 : : {
3789 : 12 : content_types = g_key_file_get_keys (key_file, DEFAULT_APPLICATIONS_GROUP, NULL, NULL);
3790 : : }
3791 : :
3792 [ + - + + ]: 204 : for (k = 0; content_types && content_types[k]; k++)
3793 : : {
3794 : : /* set as default, if requested so */
3795 : 97 : string = g_key_file_get_string (key_file,
3796 : : DEFAULT_APPLICATIONS_GROUP,
3797 : 97 : content_types[k],
3798 : : NULL);
3799 : :
3800 [ + + ]: 97 : if (g_strcmp0 (string, desktop_id) != 0 &&
3801 [ + + ]: 73 : (flags & UPDATE_MIME_SET_DEFAULT))
3802 : : {
3803 : 30 : g_free (string);
3804 : 30 : string = g_strdup (desktop_id);
3805 : :
3806 : : /* add in the non-default list too, if it's not already there */
3807 : 30 : flags |= UPDATE_MIME_SET_NON_DEFAULT;
3808 : : }
3809 : :
3810 [ + + + + ]: 97 : if (string == NULL || desktop_id == NULL)
3811 : 40 : g_key_file_remove_key (key_file,
3812 : : DEFAULT_APPLICATIONS_GROUP,
3813 : 40 : content_types[k],
3814 : : NULL);
3815 : : else
3816 : 57 : g_key_file_set_string (key_file,
3817 : : DEFAULT_APPLICATIONS_GROUP,
3818 : 57 : content_types[k],
3819 : : string);
3820 : :
3821 : 97 : g_free (string);
3822 : : }
3823 : :
3824 [ + + ]: 107 : if (content_type)
3825 : : {
3826 : : /* reuse the list from above */
3827 : : }
3828 : : else
3829 : : {
3830 : 12 : g_strfreev (content_types);
3831 : 12 : content_types = g_key_file_get_keys (key_file, ADDED_ASSOCIATIONS_GROUP, NULL, NULL);
3832 : : }
3833 : :
3834 [ + - + + ]: 204 : for (k = 0; content_types && content_types[k]; k++)
3835 : : {
3836 : : /* Add to the right place in the list */
3837 : :
3838 : 97 : length = 0;
3839 : 97 : old_list = g_key_file_get_string_list (key_file, ADDED_ASSOCIATIONS_GROUP,
3840 : 97 : content_types[k], &length, NULL);
3841 : :
3842 : 97 : list = g_new (char *, 1 + length + 1);
3843 : :
3844 : 97 : i = 0;
3845 : :
3846 : : /* if we're adding a last-used hint, just put the application in front of the list */
3847 [ + + ]: 97 : if (flags & UPDATE_MIME_SET_LAST_USED)
3848 : : {
3849 : : /* avoid adding this again as non-default later */
3850 [ + - ]: 7 : if (flags & UPDATE_MIME_SET_NON_DEFAULT)
3851 : 7 : flags ^= UPDATE_MIME_SET_NON_DEFAULT;
3852 : :
3853 : 14 : list[i++] = g_strdup (desktop_id);
3854 : : }
3855 : :
3856 [ + + ]: 97 : if (old_list)
3857 : : {
3858 [ + + ]: 114 : for (j = 0; old_list[j] != NULL; j++)
3859 : : {
3860 [ + + ]: 67 : if (g_strcmp0 (old_list[j], desktop_id) != 0)
3861 : : {
3862 : : /* rewrite other entries if they're different from the new one */
3863 : 76 : list[i++] = g_strdup (old_list[j]);
3864 : : }
3865 [ + + ]: 29 : else if (flags & UPDATE_MIME_SET_NON_DEFAULT)
3866 : : {
3867 : : /* we encountered an old entry which is equal to the one we're adding as non-default,
3868 : : * don't change its position in the list.
3869 : : */
3870 : 4 : flags ^= UPDATE_MIME_SET_NON_DEFAULT;
3871 : 8 : list[i++] = g_strdup (old_list[j]);
3872 : : }
3873 : : }
3874 : : }
3875 : :
3876 : : /* add it at the end of the list */
3877 [ + + ]: 97 : if (flags & UPDATE_MIME_SET_NON_DEFAULT)
3878 : 76 : list[i++] = g_strdup (desktop_id);
3879 : :
3880 : 97 : list[i] = NULL;
3881 : :
3882 : 97 : g_strfreev (old_list);
3883 : :
3884 [ + + + + ]: 97 : if (list[0] == NULL || desktop_id == NULL)
3885 : 46 : g_key_file_remove_key (key_file,
3886 : : ADDED_ASSOCIATIONS_GROUP,
3887 : 46 : content_types[k],
3888 : : NULL);
3889 : : else
3890 : 51 : g_key_file_set_string_list (key_file,
3891 : : ADDED_ASSOCIATIONS_GROUP,
3892 : 51 : content_types[k],
3893 : : (const char * const *)list, i);
3894 : :
3895 : 97 : g_strfreev (list);
3896 : : }
3897 : :
3898 [ + + ]: 107 : if (content_type)
3899 : : {
3900 : : /* reuse the list from above */
3901 : : }
3902 : : else
3903 : : {
3904 : 12 : g_strfreev (content_types);
3905 : 12 : content_types = g_key_file_get_keys (key_file, REMOVED_ASSOCIATIONS_GROUP, NULL, NULL);
3906 : : }
3907 : :
3908 [ + - + + ]: 203 : for (k = 0; content_types && content_types[k]; k++)
3909 : : {
3910 : : /* Remove from removed associations group (unless remove) */
3911 : :
3912 : 96 : length = 0;
3913 : 96 : old_list = g_key_file_get_string_list (key_file, REMOVED_ASSOCIATIONS_GROUP,
3914 : 96 : content_types[k], &length, NULL);
3915 : :
3916 : 96 : list = g_new (char *, 1 + length + 1);
3917 : :
3918 : 96 : i = 0;
3919 [ + + ]: 96 : if (flags & UPDATE_MIME_REMOVE)
3920 : 36 : list[i++] = g_strdup (desktop_id);
3921 [ + + ]: 96 : if (old_list)
3922 : : {
3923 [ + + ]: 36 : for (j = 0; old_list[j] != NULL; j++)
3924 : : {
3925 [ + + ]: 18 : if (g_strcmp0 (old_list[j], desktop_id) != 0)
3926 : 34 : list[i++] = g_strdup (old_list[j]);
3927 : : }
3928 : : }
3929 : 96 : list[i] = NULL;
3930 : :
3931 : 96 : g_strfreev (old_list);
3932 : :
3933 [ + + + + ]: 96 : if (list[0] == NULL || desktop_id == NULL)
3934 : 78 : g_key_file_remove_key (key_file,
3935 : : REMOVED_ASSOCIATIONS_GROUP,
3936 : 78 : content_types[k],
3937 : : NULL);
3938 : : else
3939 : 18 : g_key_file_set_string_list (key_file,
3940 : : REMOVED_ASSOCIATIONS_GROUP,
3941 : 18 : content_types[k],
3942 : : (const char * const *)list, i);
3943 : :
3944 : 96 : g_strfreev (list);
3945 : : }
3946 : :
3947 : 107 : g_strfreev (content_types);
3948 : :
3949 : 107 : data = g_key_file_to_data (key_file, &data_size, error);
3950 : 107 : g_key_file_free (key_file);
3951 : :
3952 : 107 : res = g_file_set_contents_full (filename, data, data_size,
3953 : : G_FILE_SET_CONTENTS_CONSISTENT | G_FILE_SET_CONTENTS_ONLY_EXISTING,
3954 : : 0600, error);
3955 : :
3956 : 107 : desktop_file_dirs_invalidate_user_config ();
3957 : :
3958 : 107 : g_free (filename);
3959 : 107 : g_free (data);
3960 : :
3961 : 107 : return res;
3962 : : }
3963 : :
3964 : : static gboolean
3965 : 7 : g_desktop_app_info_set_as_last_used_for_type (GAppInfo *appinfo,
3966 : : const char *content_type,
3967 : : GError **error)
3968 : : {
3969 : 7 : GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
3970 : :
3971 [ - + ]: 7 : if (!g_desktop_app_info_ensure_saved (info, error))
3972 : 0 : return FALSE;
3973 : :
3974 [ - + ]: 7 : if (!info->desktop_id)
3975 : : {
3976 : 0 : g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
3977 : : _("Application information lacks an identifier"));
3978 : 0 : return FALSE;
3979 : : }
3980 : :
3981 : : /* both add support for the content type and set as last used */
3982 : 7 : return update_mimeapps_list (info->desktop_id, content_type,
3983 : : UPDATE_MIME_SET_NON_DEFAULT |
3984 : : UPDATE_MIME_SET_LAST_USED,
3985 : : error);
3986 : : }
3987 : :
3988 : : static gboolean
3989 : 30 : g_desktop_app_info_set_as_default_for_type (GAppInfo *appinfo,
3990 : : const char *content_type,
3991 : : GError **error)
3992 : : {
3993 : 30 : GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
3994 : :
3995 [ - + ]: 30 : if (!g_desktop_app_info_ensure_saved (info, error))
3996 : 0 : return FALSE;
3997 : :
3998 [ - + ]: 30 : if (!info->desktop_id)
3999 : : {
4000 : 0 : g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
4001 : : _("Application information lacks an identifier"));
4002 : 0 : return FALSE;
4003 : : }
4004 : :
4005 : 30 : return update_mimeapps_list (info->desktop_id, content_type,
4006 : : UPDATE_MIME_SET_DEFAULT,
4007 : : error);
4008 : : }
4009 : :
4010 : : static void
4011 : 19 : update_program_done (GPid pid,
4012 : : gint status,
4013 : : gpointer data)
4014 : : {
4015 : : /* Did the application exit correctly */
4016 : 19 : if (g_spawn_check_wait_status (status, NULL))
4017 : : {
4018 : : /* Here we could clean out any caches in use */
4019 : : }
4020 : 19 : }
4021 : :
4022 : : static void
4023 : 19 : run_update_command (char *command,
4024 : : char *subdir)
4025 : : {
4026 : 19 : char *argv[3] = {
4027 : : NULL,
4028 : : NULL,
4029 : : NULL,
4030 : : };
4031 : 19 : GPid pid = 0;
4032 : 19 : GError *local_error = NULL;
4033 : :
4034 : 19 : argv[0] = command;
4035 : 19 : argv[1] = g_build_filename (g_get_user_data_dir (), subdir, NULL);
4036 : :
4037 [ + - ]: 19 : if (g_spawn_async ("/", argv,
4038 : : NULL, /* envp */
4039 : : G_SPAWN_SEARCH_PATH |
4040 : : G_SPAWN_STDOUT_TO_DEV_NULL |
4041 : : G_SPAWN_STDERR_TO_DEV_NULL |
4042 : : G_SPAWN_DO_NOT_REAP_CHILD,
4043 : : NULL, NULL, /* No setup function */
4044 : : &pid,
4045 : : &local_error))
4046 : 19 : g_child_watch_add (pid, update_program_done, NULL);
4047 : : else
4048 : : {
4049 : : /* If we get an error at this point, it's quite likely the user doesn't
4050 : : * have an installed copy of either 'update-mime-database' or
4051 : : * 'update-desktop-database'. I don't think we want to popup an error
4052 : : * dialog at this point, so we just do a g_warning to give the user a
4053 : : * chance of debugging it.
4054 : : */
4055 : 0 : g_warning ("%s", local_error->message);
4056 : 0 : g_error_free (local_error);
4057 : : }
4058 : :
4059 : 19 : g_free (argv[1]);
4060 : 19 : }
4061 : :
4062 : : static gboolean
4063 : 1 : g_desktop_app_info_set_as_default_for_extension (GAppInfo *appinfo,
4064 : : const char *extension,
4065 : : GError **error)
4066 : : {
4067 : : char *filename, *basename, *mimetype;
4068 : : char *dirname;
4069 : : gboolean res;
4070 : :
4071 [ - + ]: 1 : if (!g_desktop_app_info_ensure_saved (G_DESKTOP_APP_INFO (appinfo), error))
4072 : 0 : return FALSE;
4073 : :
4074 : 1 : dirname = ensure_dir (MIMETYPE_DIR, error);
4075 [ - + ]: 1 : if (!dirname)
4076 : 0 : return FALSE;
4077 : :
4078 : 1 : basename = g_strdup_printf ("user-extension-%s.xml", extension);
4079 : 1 : filename = g_build_filename (dirname, basename, NULL);
4080 : 1 : g_free (basename);
4081 : 1 : g_free (dirname);
4082 : :
4083 : 1 : mimetype = g_strdup_printf ("application/x-extension-%s", extension);
4084 : :
4085 [ + - ]: 1 : if (!g_file_test (filename, G_FILE_TEST_EXISTS))
4086 : : {
4087 : : char *contents;
4088 : :
4089 : : contents =
4090 : 1 : g_strdup_printf ("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
4091 : : "<mime-info xmlns=\"http://www.freedesktop.org/standards/shared-mime-info\">\n"
4092 : : " <mime-type type=\"%s\">\n"
4093 : : " <comment>%s document</comment>\n"
4094 : : " <glob pattern=\"*.%s\"/>\n"
4095 : : " </mime-type>\n"
4096 : : "</mime-info>\n", mimetype, extension, extension);
4097 : :
4098 : 1 : g_file_set_contents_full (filename, contents, -1,
4099 : : G_FILE_SET_CONTENTS_CONSISTENT | G_FILE_SET_CONTENTS_ONLY_EXISTING,
4100 : : 0600, NULL);
4101 : 1 : g_free (contents);
4102 : :
4103 : 1 : run_update_command ("update-mime-database", "mime");
4104 : : }
4105 : 1 : g_free (filename);
4106 : :
4107 : 1 : res = g_desktop_app_info_set_as_default_for_type (appinfo,
4108 : : mimetype,
4109 : : error);
4110 : :
4111 : 1 : g_free (mimetype);
4112 : :
4113 : 1 : return res;
4114 : : }
4115 : :
4116 : : static gboolean
4117 : 12 : g_desktop_app_info_add_supports_type (GAppInfo *appinfo,
4118 : : const char *content_type,
4119 : : GError **error)
4120 : : {
4121 : 12 : GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
4122 : :
4123 [ - + ]: 12 : if (!g_desktop_app_info_ensure_saved (G_DESKTOP_APP_INFO (info), error))
4124 : 0 : return FALSE;
4125 : :
4126 : 12 : return update_mimeapps_list (info->desktop_id, content_type,
4127 : : UPDATE_MIME_SET_NON_DEFAULT,
4128 : : error);
4129 : : }
4130 : :
4131 : : static gboolean
4132 : 1 : g_desktop_app_info_can_remove_supports_type (GAppInfo *appinfo)
4133 : : {
4134 : 1 : return TRUE;
4135 : : }
4136 : :
4137 : : static gboolean
4138 : 18 : g_desktop_app_info_remove_supports_type (GAppInfo *appinfo,
4139 : : const char *content_type,
4140 : : GError **error)
4141 : : {
4142 : 18 : GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
4143 : :
4144 [ - + ]: 18 : if (!g_desktop_app_info_ensure_saved (G_DESKTOP_APP_INFO (info), error))
4145 : 0 : return FALSE;
4146 : :
4147 : 18 : return update_mimeapps_list (info->desktop_id, content_type,
4148 : : UPDATE_MIME_REMOVE,
4149 : : error);
4150 : : }
4151 : :
4152 : : static const char **
4153 : 1 : g_desktop_app_info_get_supported_types (GAppInfo *appinfo)
4154 : : {
4155 : 1 : GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
4156 : :
4157 : 1 : return (const char**) info->mime_types;
4158 : : }
4159 : :
4160 : : /* Saving and deleting {{{2 */
4161 : :
4162 : : static gboolean
4163 : 68 : g_desktop_app_info_ensure_saved (GDesktopAppInfo *info,
4164 : : GError **error)
4165 : : {
4166 : : GKeyFile *key_file;
4167 : : char *dirname;
4168 : : char *filename;
4169 : : char *data, *desktop_id;
4170 : : gsize data_size;
4171 : : int fd;
4172 : : gboolean res;
4173 : :
4174 [ + + ]: 68 : if (info->filename != NULL)
4175 : 50 : return TRUE;
4176 : :
4177 : : /* This is only used for object created with
4178 : : * g_app_info_create_from_commandline. All other
4179 : : * object should have a filename
4180 : : */
4181 : :
4182 : 18 : dirname = ensure_dir (APP_DIR, error);
4183 [ - + ]: 18 : if (!dirname)
4184 : 0 : return FALSE;
4185 : :
4186 : 18 : key_file = g_key_file_new ();
4187 : :
4188 : 18 : g_key_file_set_string (key_file, G_KEY_FILE_DESKTOP_GROUP,
4189 : : "Encoding", "UTF-8");
4190 : 18 : g_key_file_set_string (key_file, G_KEY_FILE_DESKTOP_GROUP,
4191 : : G_KEY_FILE_DESKTOP_KEY_VERSION, "1.0");
4192 : 18 : g_key_file_set_string (key_file, G_KEY_FILE_DESKTOP_GROUP,
4193 : : G_KEY_FILE_DESKTOP_KEY_TYPE,
4194 : : G_KEY_FILE_DESKTOP_TYPE_APPLICATION);
4195 [ - + ]: 18 : if (info->terminal)
4196 : 0 : g_key_file_set_boolean (key_file, G_KEY_FILE_DESKTOP_GROUP,
4197 : : G_KEY_FILE_DESKTOP_KEY_TERMINAL, TRUE);
4198 [ + - ]: 18 : if (info->nodisplay)
4199 : 18 : g_key_file_set_boolean (key_file, G_KEY_FILE_DESKTOP_GROUP,
4200 : : G_KEY_FILE_DESKTOP_KEY_NO_DISPLAY, TRUE);
4201 : :
4202 : 18 : g_key_file_set_string (key_file, G_KEY_FILE_DESKTOP_GROUP,
4203 : 18 : G_KEY_FILE_DESKTOP_KEY_EXEC, info->exec);
4204 : :
4205 : 18 : g_key_file_set_string (key_file, G_KEY_FILE_DESKTOP_GROUP,
4206 : 18 : G_KEY_FILE_DESKTOP_KEY_NAME, info->name);
4207 : :
4208 [ - + ]: 18 : if (info->generic_name != NULL)
4209 : 0 : g_key_file_set_string (key_file, G_KEY_FILE_DESKTOP_GROUP,
4210 : 0 : GENERIC_NAME_KEY, info->generic_name);
4211 : :
4212 [ - + ]: 18 : if (info->fullname != NULL)
4213 : 0 : g_key_file_set_string (key_file, G_KEY_FILE_DESKTOP_GROUP,
4214 : 0 : FULL_NAME_KEY, info->fullname);
4215 : :
4216 : 18 : g_key_file_set_string (key_file, G_KEY_FILE_DESKTOP_GROUP,
4217 : 18 : G_KEY_FILE_DESKTOP_KEY_COMMENT, info->comment);
4218 : :
4219 : 18 : g_key_file_set_boolean (key_file, G_KEY_FILE_DESKTOP_GROUP,
4220 : : G_KEY_FILE_DESKTOP_KEY_NO_DISPLAY, TRUE);
4221 : :
4222 : 18 : data = g_key_file_to_data (key_file, &data_size, NULL);
4223 : 18 : g_key_file_free (key_file);
4224 : :
4225 : 18 : desktop_id = g_strdup_printf ("userapp-%s-XXXXXX.desktop", info->name);
4226 : 18 : filename = g_build_filename (dirname, desktop_id, NULL);
4227 : 18 : g_free (desktop_id);
4228 : 18 : g_free (dirname);
4229 : :
4230 : 18 : fd = g_mkstemp (filename);
4231 [ - + ]: 18 : if (fd == -1)
4232 : : {
4233 : : char *display_name;
4234 : :
4235 : 0 : display_name = g_filename_display_name (filename);
4236 : 0 : g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
4237 : : _("Can’t create user desktop file %s"), display_name);
4238 : 0 : g_free (display_name);
4239 : 0 : g_free (filename);
4240 : 0 : g_free (data);
4241 : 0 : return FALSE;
4242 : : }
4243 : :
4244 : 18 : desktop_id = g_path_get_basename (filename);
4245 : :
4246 : : /* FIXME - actually handle error */
4247 : 18 : (void) g_close (fd, NULL);
4248 : :
4249 : 18 : res = g_file_set_contents_full (filename, data, data_size,
4250 : : G_FILE_SET_CONTENTS_CONSISTENT | G_FILE_SET_CONTENTS_ONLY_EXISTING,
4251 : : 0600, error);
4252 : 18 : g_free (data);
4253 [ - + ]: 18 : if (!res)
4254 : : {
4255 : 0 : g_free (desktop_id);
4256 : 0 : g_free (filename);
4257 : 0 : return FALSE;
4258 : : }
4259 : :
4260 : 18 : info->filename = filename;
4261 : 18 : info->desktop_id = desktop_id;
4262 : :
4263 : 18 : run_update_command ("update-desktop-database", "applications");
4264 : :
4265 : : /* We just dropped a file in the user's desktop file directory. Save
4266 : : * the monitor the bother of having to notice it and invalidate
4267 : : * immediately.
4268 : : *
4269 : : * This means that calls directly following this will be able to see
4270 : : * the results immediately.
4271 : : */
4272 : 18 : desktop_file_dirs_invalidate_user_data ();
4273 : :
4274 : 18 : return TRUE;
4275 : : }
4276 : :
4277 : : static gboolean
4278 : 2 : g_desktop_app_info_can_delete (GAppInfo *appinfo)
4279 : : {
4280 : 2 : GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
4281 : :
4282 [ + - ]: 2 : if (info->filename)
4283 : : {
4284 [ + - ]: 2 : if (strstr (info->filename, "/userapp-"))
4285 : 2 : return g_access (info->filename, W_OK) == 0;
4286 : : }
4287 : :
4288 : 0 : return FALSE;
4289 : : }
4290 : :
4291 : : static gboolean
4292 : 12 : g_desktop_app_info_delete (GAppInfo *appinfo)
4293 : : {
4294 : 12 : GDesktopAppInfo *info = G_DESKTOP_APP_INFO (appinfo);
4295 : :
4296 [ + - ]: 12 : if (info->filename)
4297 : : {
4298 [ + - ]: 12 : if (g_remove (info->filename) == 0)
4299 : : {
4300 : 12 : update_mimeapps_list (info->desktop_id, NULL,
4301 : : UPDATE_MIME_NONE,
4302 : : NULL);
4303 : :
4304 : 12 : g_free (info->filename);
4305 : 12 : info->filename = NULL;
4306 : 12 : g_free (info->desktop_id);
4307 : 12 : info->desktop_id = NULL;
4308 : :
4309 : 12 : return TRUE;
4310 : : }
4311 : : }
4312 : :
4313 : 0 : return FALSE;
4314 : : }
4315 : :
4316 : : /* Create for commandline {{{2 */
4317 : : /**
4318 : : * g_app_info_create_from_commandline:
4319 : : * @commandline: (type filename): the commandline to use
4320 : : * @application_name: (nullable): the application name, or %NULL to use @commandline
4321 : : * @flags: flags that can specify details of the created #GAppInfo
4322 : : * @error: a #GError location to store the error occurring, %NULL to ignore.
4323 : : *
4324 : : * Creates a new #GAppInfo from the given information.
4325 : : *
4326 : : * Note that for @commandline, the quoting rules of the Exec key of the
4327 : : * [freedesktop.org Desktop Entry Specification](http://freedesktop.org/Standards/desktop-entry-spec)
4328 : : * are applied. For example, if the @commandline contains
4329 : : * percent-encoded URIs, the percent-character must be doubled in order to prevent it from
4330 : : * being swallowed by Exec key unquoting. See the specification for exact quoting rules.
4331 : : *
4332 : : * Returns: (transfer full): new #GAppInfo for given command.
4333 : : **/
4334 : : GAppInfo *
4335 : 51 : g_app_info_create_from_commandline (const char *commandline,
4336 : : const char *application_name,
4337 : : GAppInfoCreateFlags flags,
4338 : : GError **error)
4339 : : {
4340 : : char **split;
4341 : : char *basename;
4342 : : GDesktopAppInfo *info;
4343 : :
4344 : 51 : g_return_val_if_fail (commandline, NULL);
4345 : :
4346 : 51 : info = g_object_new (G_TYPE_DESKTOP_APP_INFO, NULL);
4347 : :
4348 : 51 : info->filename = NULL;
4349 : 51 : info->desktop_id = NULL;
4350 : :
4351 : 51 : info->terminal = (flags & G_APP_INFO_CREATE_NEEDS_TERMINAL) != 0;
4352 : 51 : info->startup_notify = (flags & G_APP_INFO_CREATE_SUPPORTS_STARTUP_NOTIFICATION) != 0;
4353 : 51 : info->hidden = FALSE;
4354 [ + + ]: 51 : if ((flags & G_APP_INFO_CREATE_SUPPORTS_URIS) != 0)
4355 : 29 : info->exec = g_strconcat (commandline, " %u", NULL);
4356 : : else
4357 : 22 : info->exec = g_strconcat (commandline, " %f", NULL);
4358 : 51 : info->nodisplay = TRUE;
4359 : 51 : info->binary = binary_from_exec (info->exec);
4360 : :
4361 [ + + ]: 51 : if (application_name)
4362 : 49 : info->name = g_strdup (application_name);
4363 : : else
4364 : : {
4365 : : /* FIXME: this should be more robust. Maybe g_shell_parse_argv and use argv[0] */
4366 : 2 : split = g_strsplit (commandline, " ", 2);
4367 [ + - ]: 2 : basename = split[0] ? g_path_get_basename (split[0]) : NULL;
4368 : 2 : g_strfreev (split);
4369 : 2 : info->name = basename;
4370 [ - + ]: 2 : if (info->name == NULL)
4371 : 0 : info->name = g_strdup ("custom");
4372 : : }
4373 : 51 : info->comment = g_strdup_printf (_("Custom definition for %s"), info->name);
4374 : :
4375 : 51 : return G_APP_INFO (info);
4376 : : }
4377 : :
4378 : : /* GAppInfo interface init */
4379 : :
4380 : : static void
4381 : 28 : g_desktop_app_info_iface_init (GAppInfoIface *iface)
4382 : : {
4383 : 28 : iface->dup = g_desktop_app_info_dup;
4384 : 28 : iface->equal = g_desktop_app_info_equal;
4385 : 28 : iface->get_id = g_desktop_app_info_get_id;
4386 : 28 : iface->get_name = g_desktop_app_info_get_name;
4387 : 28 : iface->get_description = g_desktop_app_info_get_description;
4388 : 28 : iface->get_executable = g_desktop_app_info_get_executable;
4389 : 28 : iface->get_icon = g_desktop_app_info_get_icon;
4390 : 28 : iface->launch = g_desktop_app_info_launch;
4391 : 28 : iface->supports_uris = g_desktop_app_info_supports_uris;
4392 : 28 : iface->supports_files = g_desktop_app_info_supports_files;
4393 : 28 : iface->launch_uris = g_desktop_app_info_launch_uris;
4394 : 28 : iface->launch_uris_async = g_desktop_app_info_launch_uris_async;
4395 : 28 : iface->launch_uris_finish = g_desktop_app_info_launch_uris_finish;
4396 : 28 : iface->should_show = g_desktop_app_info_should_show;
4397 : 28 : iface->set_as_default_for_type = g_desktop_app_info_set_as_default_for_type;
4398 : 28 : iface->set_as_default_for_extension = g_desktop_app_info_set_as_default_for_extension;
4399 : 28 : iface->add_supports_type = g_desktop_app_info_add_supports_type;
4400 : 28 : iface->can_remove_supports_type = g_desktop_app_info_can_remove_supports_type;
4401 : 28 : iface->remove_supports_type = g_desktop_app_info_remove_supports_type;
4402 : 28 : iface->can_delete = g_desktop_app_info_can_delete;
4403 : 28 : iface->do_delete = g_desktop_app_info_delete;
4404 : 28 : iface->get_commandline = g_desktop_app_info_get_commandline;
4405 : 28 : iface->get_display_name = g_desktop_app_info_get_display_name;
4406 : 28 : iface->set_as_last_used_for_type = g_desktop_app_info_set_as_last_used_for_type;
4407 : 28 : iface->get_supported_types = g_desktop_app_info_get_supported_types;
4408 : 28 : }
4409 : :
4410 : : /* Recommended applications {{{2 */
4411 : :
4412 : : /* Converts content_type into a list of itself with all of its parent
4413 : : * types (if include_fallback is enabled) or just returns a single-item
4414 : : * list with the unaliased content type.
4415 : : */
4416 : : static gchar **
4417 : 84 : get_list_of_mimetypes (const gchar *content_type,
4418 : : gboolean include_fallback)
4419 : : {
4420 : : gchar *unaliased;
4421 : : GPtrArray *array;
4422 : :
4423 : 84 : array = g_ptr_array_new ();
4424 : 84 : unaliased = _g_unix_content_type_unalias (content_type);
4425 : 84 : g_ptr_array_add (array, unaliased);
4426 : :
4427 [ + + ]: 84 : if (include_fallback)
4428 : : {
4429 : : guint i;
4430 : :
4431 : : /* Iterate the array as we grow it, until we have nothing more to add */
4432 [ + + ]: 132 : for (i = 0; i < array->len; i++)
4433 : : {
4434 : 69 : gchar **parents = _g_unix_content_type_get_parents (g_ptr_array_index (array, i));
4435 : : gint j;
4436 : :
4437 [ + + ]: 144 : for (j = 0; parents[j]; j++)
4438 : : /* Don't add duplicates */
4439 [ + + ]: 75 : if (!array_contains (array, parents[j]))
4440 : 6 : g_ptr_array_add (array, parents[j]);
4441 : : else
4442 : 69 : g_free (parents[j]);
4443 : :
4444 : : /* We already stole or freed each element. Free the container. */
4445 : 69 : g_free (parents);
4446 : : }
4447 : : }
4448 : :
4449 : 84 : g_ptr_array_add (array, NULL);
4450 : :
4451 : 84 : return (gchar **) g_ptr_array_free (array, FALSE);
4452 : : }
4453 : :
4454 : : static gchar **
4455 : 28 : g_desktop_app_info_get_desktop_ids_for_content_type (const gchar *content_type,
4456 : : gboolean include_fallback)
4457 : : {
4458 : : GPtrArray *hits, *blocklist;
4459 : : gchar **types;
4460 : : guint i, j;
4461 : :
4462 : 28 : hits = g_ptr_array_new ();
4463 : 28 : blocklist = g_ptr_array_new ();
4464 : :
4465 : 28 : types = get_list_of_mimetypes (content_type, include_fallback);
4466 : :
4467 : 28 : desktop_file_dirs_lock ();
4468 : :
4469 [ + + ]: 62 : for (i = 0; types[i]; i++)
4470 [ + + ]: 238 : for (j = 0; j < desktop_file_dirs->len; j++)
4471 : 204 : desktop_file_dir_mime_lookup (g_ptr_array_index (desktop_file_dirs, j), types[i], hits, blocklist);
4472 : :
4473 : : /* We will keep the hits past unlocking, so we must dup them */
4474 [ + + ]: 67 : for (i = 0; i < hits->len; i++)
4475 : 78 : hits->pdata[i] = g_strdup (hits->pdata[i]);
4476 : :
4477 : 28 : desktop_file_dirs_unlock ();
4478 : :
4479 : 28 : g_ptr_array_add (hits, NULL);
4480 : :
4481 : 28 : g_ptr_array_free (blocklist, TRUE);
4482 : 28 : g_strfreev (types);
4483 : :
4484 : 28 : return (gchar **) g_ptr_array_free (hits, FALSE);
4485 : : }
4486 : :
4487 : : /**
4488 : : * g_app_info_get_recommended_for_type:
4489 : : * @content_type: the content type to find a #GAppInfo for
4490 : : *
4491 : : * Gets a list of recommended #GAppInfos for a given content type, i.e.
4492 : : * those applications which claim to support the given content type exactly,
4493 : : * and not by MIME type subclassing.
4494 : : * Note that the first application of the list is the last used one, i.e.
4495 : : * the last one for which g_app_info_set_as_last_used_for_type() has been
4496 : : * called.
4497 : : *
4498 : : * Returns: (element-type GAppInfo) (transfer full): #GList of #GAppInfos
4499 : : * for given @content_type or %NULL on error.
4500 : : *
4501 : : * Since: 2.28
4502 : : **/
4503 : : GList *
4504 : 20 : g_app_info_get_recommended_for_type (const gchar *content_type)
4505 : : {
4506 : : gchar **desktop_ids;
4507 : : GList *infos;
4508 : : gint i;
4509 : :
4510 : 20 : g_return_val_if_fail (content_type != NULL, NULL);
4511 : :
4512 : 20 : desktop_ids = g_desktop_app_info_get_desktop_ids_for_content_type (content_type, FALSE);
4513 : :
4514 : 20 : infos = NULL;
4515 [ + + ]: 53 : for (i = 0; desktop_ids[i]; i++)
4516 : : {
4517 : : GDesktopAppInfo *info;
4518 : :
4519 : 33 : info = g_desktop_app_info_new (desktop_ids[i]);
4520 [ + - ]: 33 : if (info)
4521 : 33 : infos = g_list_prepend (infos, info);
4522 : : }
4523 : :
4524 : 20 : g_strfreev (desktop_ids);
4525 : :
4526 : 20 : return g_list_reverse (infos);
4527 : : }
4528 : :
4529 : : /**
4530 : : * g_app_info_get_fallback_for_type:
4531 : : * @content_type: the content type to find a #GAppInfo for
4532 : : *
4533 : : * Gets a list of fallback #GAppInfos for a given content type, i.e.
4534 : : * those applications which claim to support the given content type
4535 : : * by MIME type subclassing and not directly.
4536 : : *
4537 : : * Returns: (element-type GAppInfo) (transfer full): #GList of #GAppInfos
4538 : : * for given @content_type or %NULL on error.
4539 : : *
4540 : : * Since: 2.28
4541 : : **/
4542 : : GList *
4543 : 1 : g_app_info_get_fallback_for_type (const gchar *content_type)
4544 : : {
4545 : : gchar **recommended_ids;
4546 : : gchar **all_ids;
4547 : : GList *infos;
4548 : : gint i;
4549 : :
4550 : 1 : g_return_val_if_fail (content_type != NULL, NULL);
4551 : :
4552 : 1 : recommended_ids = g_desktop_app_info_get_desktop_ids_for_content_type (content_type, FALSE);
4553 : 1 : all_ids = g_desktop_app_info_get_desktop_ids_for_content_type (content_type, TRUE);
4554 : :
4555 : 1 : infos = NULL;
4556 [ + + ]: 3 : for (i = 0; all_ids[i]; i++)
4557 : : {
4558 : : GDesktopAppInfo *info;
4559 : : gint j;
4560 : :
4561 : : /* Don't return the ones on the recommended list */
4562 [ + + ]: 3 : for (j = 0; recommended_ids[j]; j++)
4563 [ + + ]: 2 : if (g_str_equal (all_ids[i], recommended_ids[j]))
4564 : 1 : break;
4565 : :
4566 [ + + ]: 2 : if (recommended_ids[j])
4567 : 1 : continue;
4568 : :
4569 : 1 : info = g_desktop_app_info_new (all_ids[i]);
4570 : :
4571 [ + - ]: 1 : if (info)
4572 : 1 : infos = g_list_prepend (infos, info);
4573 : : }
4574 : :
4575 : 1 : g_strfreev (recommended_ids);
4576 : 1 : g_strfreev (all_ids);
4577 : :
4578 : 1 : return g_list_reverse (infos);
4579 : : }
4580 : :
4581 : : /**
4582 : : * g_app_info_get_all_for_type:
4583 : : * @content_type: the content type to find a #GAppInfo for
4584 : : *
4585 : : * Gets a list of all #GAppInfos for a given content type,
4586 : : * including the recommended and fallback #GAppInfos. See
4587 : : * g_app_info_get_recommended_for_type() and
4588 : : * g_app_info_get_fallback_for_type().
4589 : : *
4590 : : * Returns: (element-type GAppInfo) (transfer full): #GList of #GAppInfos
4591 : : * for given @content_type or %NULL on error.
4592 : : **/
4593 : : GList *
4594 : 6 : g_app_info_get_all_for_type (const char *content_type)
4595 : : {
4596 : : gchar **desktop_ids;
4597 : : GList *infos;
4598 : : gint i;
4599 : :
4600 : 6 : g_return_val_if_fail (content_type != NULL, NULL);
4601 : :
4602 : 6 : desktop_ids = g_desktop_app_info_get_desktop_ids_for_content_type (content_type, TRUE);
4603 : :
4604 : 6 : infos = NULL;
4605 [ + + ]: 9 : for (i = 0; desktop_ids[i]; i++)
4606 : : {
4607 : : GDesktopAppInfo *info;
4608 : :
4609 : 3 : info = g_desktop_app_info_new (desktop_ids[i]);
4610 [ + - ]: 3 : if (info)
4611 : 3 : infos = g_list_prepend (infos, info);
4612 : : }
4613 : :
4614 : 6 : g_strfreev (desktop_ids);
4615 : :
4616 : 6 : return g_list_reverse (infos);
4617 : : }
4618 : :
4619 : : /**
4620 : : * g_app_info_reset_type_associations:
4621 : : * @content_type: a content type
4622 : : *
4623 : : * Removes all changes to the type associations done by
4624 : : * g_app_info_set_as_default_for_type(),
4625 : : * g_app_info_set_as_default_for_extension(),
4626 : : * g_app_info_add_supports_type() or
4627 : : * g_app_info_remove_supports_type().
4628 : : *
4629 : : * Since: 2.20
4630 : : */
4631 : : void
4632 : 28 : g_app_info_reset_type_associations (const char *content_type)
4633 : : {
4634 : 28 : update_mimeapps_list (NULL, content_type,
4635 : : UPDATE_MIME_NONE,
4636 : : NULL);
4637 : 28 : }
4638 : :
4639 : : /**
4640 : : * g_app_info_get_default_for_type:
4641 : : * @content_type: the content type to find a #GAppInfo for
4642 : : * @must_support_uris: if %TRUE, the #GAppInfo is expected to
4643 : : * support URIs
4644 : : *
4645 : : * Gets the default #GAppInfo for a given content type.
4646 : : *
4647 : : * Returns: (transfer full) (nullable): #GAppInfo for given @content_type or
4648 : : * %NULL on error.
4649 : : */
4650 : : GAppInfo *
4651 : 56 : g_app_info_get_default_for_type (const char *content_type,
4652 : : gboolean must_support_uris)
4653 : : {
4654 : : GPtrArray *blocklist;
4655 : : GPtrArray *results;
4656 : : GAppInfo *info;
4657 : : gchar **types;
4658 : : guint i, j, k;
4659 : :
4660 : 56 : g_return_val_if_fail (content_type != NULL, NULL);
4661 : :
4662 : 56 : types = get_list_of_mimetypes (content_type, TRUE);
4663 : :
4664 : 56 : blocklist = g_ptr_array_new ();
4665 : 56 : results = g_ptr_array_new ();
4666 : 56 : info = NULL;
4667 : :
4668 : 56 : desktop_file_dirs_lock ();
4669 : :
4670 [ + + ]: 76 : for (i = 0; types[i]; i++)
4671 : : {
4672 : : /* Collect all the default apps for this type */
4673 [ + + ]: 390 : for (j = 0; j < desktop_file_dirs->len; j++)
4674 : 334 : desktop_file_dir_default_lookup (g_ptr_array_index (desktop_file_dirs, j), types[i], results);
4675 : :
4676 : : /* Consider the associations as well... */
4677 [ + + ]: 390 : for (j = 0; j < desktop_file_dirs->len; j++)
4678 : 334 : desktop_file_dir_mime_lookup (g_ptr_array_index (desktop_file_dirs, j), types[i], results, blocklist);
4679 : :
4680 : : /* (If any), see if one of those apps is installed... */
4681 [ + + ]: 56 : for (j = 0; j < results->len; j++)
4682 : : {
4683 : 36 : const gchar *desktop_id = g_ptr_array_index (results, j);
4684 : :
4685 [ + - ]: 144 : for (k = 0; k < desktop_file_dirs->len; k++)
4686 : : {
4687 : 144 : info = (GAppInfo *) desktop_file_dir_get_app (g_ptr_array_index (desktop_file_dirs, k), desktop_id);
4688 : :
4689 [ + + ]: 144 : if (info)
4690 : : {
4691 [ - + - - ]: 36 : if (!must_support_uris || g_app_info_supports_uris (info))
4692 : 36 : goto out;
4693 : :
4694 : 0 : g_clear_object (&info);
4695 : : }
4696 : : }
4697 : : }
4698 : :
4699 : : /* Reset the list, ready to try again with the next (parent)
4700 : : * mimetype, but keep the blocklist in place.
4701 : : */
4702 : 20 : g_ptr_array_set_size (results, 0);
4703 : : }
4704 : :
4705 : 20 : out:
4706 : 56 : desktop_file_dirs_unlock ();
4707 : :
4708 : 56 : g_ptr_array_unref (blocklist);
4709 : 56 : g_ptr_array_unref (results);
4710 : 56 : g_strfreev (types);
4711 : :
4712 : 56 : return info;
4713 : : }
4714 : :
4715 : : /**
4716 : : * g_app_info_get_default_for_uri_scheme:
4717 : : * @uri_scheme: a string containing a URI scheme.
4718 : : *
4719 : : * Gets the default application for handling URIs with
4720 : : * the given URI scheme. A URI scheme is the initial part
4721 : : * of the URI, up to but not including the ':', e.g. "http",
4722 : : * "ftp" or "sip".
4723 : : *
4724 : : * Returns: (transfer full) (nullable): #GAppInfo for given @uri_scheme or
4725 : : * %NULL on error.
4726 : : */
4727 : : GAppInfo *
4728 : 28 : g_app_info_get_default_for_uri_scheme (const char *uri_scheme)
4729 : : {
4730 : : GAppInfo *app_info;
4731 : : char *content_type, *scheme_down;
4732 : :
4733 : 28 : g_return_val_if_fail (uri_scheme != NULL && *uri_scheme != '\0', NULL);
4734 : :
4735 : 24 : scheme_down = g_ascii_strdown (uri_scheme, -1);
4736 : 24 : content_type = g_strdup_printf ("x-scheme-handler/%s", scheme_down);
4737 : 24 : g_free (scheme_down);
4738 : 24 : app_info = g_app_info_get_default_for_type (content_type, FALSE);
4739 : 24 : g_free (content_type);
4740 : :
4741 : 24 : return app_info;
4742 : : }
4743 : :
4744 : : /* "Get all" API {{{2 */
4745 : :
4746 : : /**
4747 : : * g_desktop_app_info_get_implementations:
4748 : : * @interface: the name of the interface
4749 : : *
4750 : : * Gets all applications that implement @interface.
4751 : : *
4752 : : * An application implements an interface if that interface is listed in
4753 : : * the Implements= line of the desktop file of the application.
4754 : : *
4755 : : * Returns: (element-type GDesktopAppInfo) (transfer full): a list of #GDesktopAppInfo
4756 : : * objects.
4757 : : *
4758 : : * Since: 2.42
4759 : : **/
4760 : : GList *
4761 : 4 : g_desktop_app_info_get_implementations (const gchar *interface)
4762 : : {
4763 : 4 : GList *result = NULL;
4764 : : GList **ptr;
4765 : : guint i;
4766 : :
4767 : 4 : desktop_file_dirs_lock ();
4768 : :
4769 [ + + ]: 20 : for (i = 0; i < desktop_file_dirs->len; i++)
4770 : 16 : desktop_file_dir_get_implementations (g_ptr_array_index (desktop_file_dirs, i), &result, interface);
4771 : :
4772 : 4 : desktop_file_dirs_unlock ();
4773 : :
4774 : 4 : ptr = &result;
4775 [ + + ]: 10 : while (*ptr)
4776 : : {
4777 : 6 : gchar *name = (*ptr)->data;
4778 : : GDesktopAppInfo *app;
4779 : :
4780 : 6 : app = g_desktop_app_info_new (name);
4781 : 6 : g_free (name);
4782 : :
4783 [ + - ]: 6 : if (app)
4784 : : {
4785 : 6 : (*ptr)->data = app;
4786 : 6 : ptr = &(*ptr)->next;
4787 : : }
4788 : : else
4789 : 0 : *ptr = g_list_delete_link (*ptr, *ptr);
4790 : : }
4791 : :
4792 : 4 : return result;
4793 : : }
4794 : :
4795 : : /**
4796 : : * g_desktop_app_info_search:
4797 : : * @search_string: the search string to use
4798 : : *
4799 : : * Searches desktop files for ones that match @search_string.
4800 : : *
4801 : : * The return value is an array of strvs. Each strv contains a list of
4802 : : * applications that matched @search_string with an equal score. The
4803 : : * outer list is sorted by score so that the first strv contains the
4804 : : * best-matching applications, and so on.
4805 : : * The algorithm for determining matches is undefined and may change at
4806 : : * any time.
4807 : : *
4808 : : * None of the search results are subjected to the normal validation
4809 : : * checks performed by g_desktop_app_info_new() (for example, checking that
4810 : : * the executable referenced by a result exists), and so it is possible for
4811 : : * g_desktop_app_info_new() to return %NULL when passed an app ID returned by
4812 : : * this function. It is expected that calling code will do this when
4813 : : * subsequently creating a #GDesktopAppInfo for each result.
4814 : : *
4815 : : * Returns: (array zero-terminated=1) (element-type GStrv) (transfer full): a
4816 : : * list of strvs. Free each item with g_strfreev() and free the outer
4817 : : * list with g_free().
4818 : : */
4819 : : gchar ***
4820 : 26 : g_desktop_app_info_search (const gchar *search_string)
4821 : : {
4822 : : gchar **search_tokens;
4823 : 26 : gint last_category = -1;
4824 : 26 : gint last_match_type = -1;
4825 : : gchar ***results;
4826 : 26 : gint n_groups = 0;
4827 : : gint start_of_group;
4828 : : gint i, j;
4829 : : guint k;
4830 : :
4831 : 26 : search_tokens = g_str_tokenize_and_fold (search_string, NULL, NULL);
4832 : :
4833 : 26 : desktop_file_dirs_lock ();
4834 : :
4835 : 26 : reset_total_search_results ();
4836 : :
4837 [ + + ]: 130 : for (k = 0; k < desktop_file_dirs->len; k++)
4838 : : {
4839 [ + + ]: 256 : for (j = 0; search_tokens[j]; j++)
4840 : : {
4841 : 152 : desktop_file_dir_search (g_ptr_array_index (desktop_file_dirs, k), search_tokens[j]);
4842 : 152 : merge_token_results (j == 0);
4843 : : }
4844 : 104 : merge_directory_results ();
4845 : : }
4846 : :
4847 : 26 : sort_total_search_results ();
4848 : :
4849 : : /* Count the total number of unique categories and match types */
4850 [ + + ]: 51 : for (i = 0; i < static_total_results_size; i++)
4851 [ + + ]: 25 : if (static_total_results[i].category != last_category ||
4852 [ + + ]: 2 : static_total_results[i].match_type != last_match_type)
4853 : : {
4854 : 24 : last_category = static_total_results[i].category;
4855 : 24 : last_match_type = static_total_results[i].match_type;
4856 : 24 : n_groups++;
4857 : : }
4858 : :
4859 : 26 : results = g_new (gchar **, n_groups + 1);
4860 : :
4861 : : /* Start loading into the results list */
4862 : 26 : start_of_group = 0;
4863 [ + + ]: 50 : for (i = 0; i < n_groups; i++)
4864 : : {
4865 : 24 : gint n_items_in_group = 0;
4866 : : gint this_category;
4867 : : gint this_match_type;
4868 : : gint j;
4869 : :
4870 : 24 : this_category = static_total_results[start_of_group].category;
4871 : 24 : this_match_type = static_total_results[start_of_group].match_type;
4872 : :
4873 : 24 : while (start_of_group + n_items_in_group < static_total_results_size &&
4874 [ + + + + ]: 49 : static_total_results[start_of_group + n_items_in_group].category == this_category &&
4875 [ + + ]: 26 : static_total_results[start_of_group + n_items_in_group].match_type == this_match_type)
4876 : 25 : n_items_in_group++;
4877 : :
4878 : 24 : results[i] = g_new (gchar *, n_items_in_group + 1);
4879 [ + + ]: 49 : for (j = 0; j < n_items_in_group; j++)
4880 : 50 : results[i][j] = g_strdup (static_total_results[start_of_group + j].app_name);
4881 : 24 : results[i][j] = NULL;
4882 : :
4883 : 24 : start_of_group += n_items_in_group;
4884 : : }
4885 : 26 : results[i] = NULL;
4886 : :
4887 : 26 : desktop_file_dirs_unlock ();
4888 : :
4889 : 26 : g_strfreev (search_tokens);
4890 : :
4891 : 26 : return results;
4892 : : }
4893 : :
4894 : : /**
4895 : : * g_app_info_get_all:
4896 : : *
4897 : : * Gets a list of all of the applications currently registered
4898 : : * on this system.
4899 : : *
4900 : : * For desktop files, this includes applications that have
4901 : : * `NoDisplay=true` set or are excluded from display by means
4902 : : * of `OnlyShowIn` or `NotShowIn`. See g_app_info_should_show().
4903 : : * The returned list does not include applications which have
4904 : : * the `Hidden` key set.
4905 : : *
4906 : : * Returns: (element-type GAppInfo) (transfer full): a newly allocated #GList of references to #GAppInfos.
4907 : : **/
4908 : : GList *
4909 : 6 : g_app_info_get_all (void)
4910 : : {
4911 : : GHashTable *apps;
4912 : : GHashTableIter iter;
4913 : : gpointer value;
4914 : : guint i;
4915 : : GList *infos;
4916 : :
4917 : 6 : apps = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
4918 : :
4919 : 6 : desktop_file_dirs_lock ();
4920 : :
4921 [ + + ]: 34 : for (i = 0; i < desktop_file_dirs->len; i++)
4922 : 28 : desktop_file_dir_get_all (g_ptr_array_index (desktop_file_dirs, i), apps);
4923 : :
4924 : 6 : desktop_file_dirs_unlock ();
4925 : :
4926 : 6 : infos = NULL;
4927 : 6 : g_hash_table_iter_init (&iter, apps);
4928 [ + + ]: 76 : while (g_hash_table_iter_next (&iter, NULL, &value))
4929 : : {
4930 [ - + ]: 64 : if (value)
4931 : 64 : infos = g_list_prepend (infos, value);
4932 : : }
4933 : :
4934 : 6 : g_hash_table_destroy (apps);
4935 : :
4936 : 6 : return infos;
4937 : : }
4938 : :
4939 : : /* GDesktopAppInfoLookup interface {{{2 */
4940 : :
4941 : : /**
4942 : : * GDesktopAppInfoLookup:
4943 : : *
4944 : : * #GDesktopAppInfoLookup is an opaque data structure and can only be accessed
4945 : : * using the following functions.
4946 : : *
4947 : : * Deprecated: 2.28: The #GDesktopAppInfoLookup interface is deprecated and
4948 : : * unused by GIO.
4949 : : **/
4950 : :
4951 : : G_GNUC_BEGIN_IGNORE_DEPRECATIONS
4952 : :
4953 : : typedef GDesktopAppInfoLookupIface GDesktopAppInfoLookupInterface;
4954 [ + + + - : 119 : G_DEFINE_INTERFACE (GDesktopAppInfoLookup, g_desktop_app_info_lookup, G_TYPE_OBJECT)
+ + ]
4955 : :
4956 : : static void
4957 : 2 : g_desktop_app_info_lookup_default_init (GDesktopAppInfoLookupInterface *iface)
4958 : : {
4959 : 2 : }
4960 : :
4961 : : /* "Get for mime type" APIs {{{2 */
4962 : :
4963 : : /**
4964 : : * g_desktop_app_info_lookup_get_default_for_uri_scheme:
4965 : : * @lookup: a #GDesktopAppInfoLookup
4966 : : * @uri_scheme: a string containing a URI scheme.
4967 : : *
4968 : : * Gets the default application for launching applications
4969 : : * using this URI scheme for a particular #GDesktopAppInfoLookup
4970 : : * implementation.
4971 : : *
4972 : : * The #GDesktopAppInfoLookup interface and this function is used
4973 : : * to implement g_app_info_get_default_for_uri_scheme() backends
4974 : : * in a GIO module. There is no reason for applications to use it
4975 : : * directly. Applications should use g_app_info_get_default_for_uri_scheme().
4976 : : *
4977 : : * Returns: (transfer full) (nullable): #GAppInfo for given @uri_scheme or
4978 : : * %NULL on error.
4979 : : *
4980 : : * Deprecated: 2.28: The #GDesktopAppInfoLookup interface is deprecated and
4981 : : * unused by GIO.
4982 : : */
4983 : : GAppInfo *
4984 : 0 : g_desktop_app_info_lookup_get_default_for_uri_scheme (GDesktopAppInfoLookup *lookup,
4985 : : const char *uri_scheme)
4986 : : {
4987 : : GDesktopAppInfoLookupIface *iface;
4988 : :
4989 : 0 : g_return_val_if_fail (G_IS_DESKTOP_APP_INFO_LOOKUP (lookup), NULL);
4990 : :
4991 : 0 : iface = G_DESKTOP_APP_INFO_LOOKUP_GET_IFACE (lookup);
4992 : :
4993 : 0 : return (* iface->get_default_for_uri_scheme) (lookup, uri_scheme);
4994 : : }
4995 : :
4996 : : G_GNUC_END_IGNORE_DEPRECATIONS
4997 : :
4998 : : /* Misc getter APIs {{{2 */
4999 : :
5000 : : /**
5001 : : * g_desktop_app_info_get_startup_wm_class:
5002 : : * @info: a #GDesktopAppInfo that supports startup notify
5003 : : *
5004 : : * Retrieves the StartupWMClass field from @info. This represents the
5005 : : * WM_CLASS property of the main window of the application, if launched
5006 : : * through @info.
5007 : : *
5008 : : * Returns: (nullable) (transfer none): the startup WM class, or %NULL if none is set
5009 : : * in the desktop file.
5010 : : *
5011 : : * Since: 2.34
5012 : : */
5013 : : const char *
5014 : 1 : g_desktop_app_info_get_startup_wm_class (GDesktopAppInfo *info)
5015 : : {
5016 : 1 : g_return_val_if_fail (G_IS_DESKTOP_APP_INFO (info), NULL);
5017 : :
5018 : 1 : return info->startup_wm_class;
5019 : : }
5020 : :
5021 : : /**
5022 : : * g_desktop_app_info_get_string:
5023 : : * @info: a #GDesktopAppInfo
5024 : : * @key: the key to look up
5025 : : *
5026 : : * Looks up a string value in the keyfile backing @info.
5027 : : *
5028 : : * The @key is looked up in the "Desktop Entry" group.
5029 : : *
5030 : : * Returns: (nullable): a newly allocated string, or %NULL if the key
5031 : : * is not found
5032 : : *
5033 : : * Since: 2.36
5034 : : */
5035 : : char *
5036 : 7 : g_desktop_app_info_get_string (GDesktopAppInfo *info,
5037 : : const char *key)
5038 : : {
5039 : 7 : g_return_val_if_fail (G_IS_DESKTOP_APP_INFO (info), NULL);
5040 : :
5041 : 7 : return g_key_file_get_string (info->keyfile,
5042 : : G_KEY_FILE_DESKTOP_GROUP, key, NULL);
5043 : : }
5044 : :
5045 : : /**
5046 : : * g_desktop_app_info_get_locale_string:
5047 : : * @info: a #GDesktopAppInfo
5048 : : * @key: the key to look up
5049 : : *
5050 : : * Looks up a localized string value in the keyfile backing @info
5051 : : * translated to the current locale.
5052 : : *
5053 : : * The @key is looked up in the "Desktop Entry" group.
5054 : : *
5055 : : * Returns: (nullable): a newly allocated string, or %NULL if the key
5056 : : * is not found
5057 : : *
5058 : : * Since: 2.56
5059 : : */
5060 : : char *
5061 : 2 : g_desktop_app_info_get_locale_string (GDesktopAppInfo *info,
5062 : : const char *key)
5063 : : {
5064 : 2 : g_return_val_if_fail (G_IS_DESKTOP_APP_INFO (info), NULL);
5065 : 2 : g_return_val_if_fail (key != NULL && *key != '\0', NULL);
5066 : :
5067 : 2 : return g_key_file_get_locale_string (info->keyfile,
5068 : : G_KEY_FILE_DESKTOP_GROUP,
5069 : : key, NULL, NULL);
5070 : : }
5071 : :
5072 : : /**
5073 : : * g_desktop_app_info_get_boolean:
5074 : : * @info: a #GDesktopAppInfo
5075 : : * @key: the key to look up
5076 : : *
5077 : : * Looks up a boolean value in the keyfile backing @info.
5078 : : *
5079 : : * The @key is looked up in the "Desktop Entry" group.
5080 : : *
5081 : : * Returns: the boolean value, or %FALSE if the key
5082 : : * is not found
5083 : : *
5084 : : * Since: 2.36
5085 : : */
5086 : : gboolean
5087 : 13 : g_desktop_app_info_get_boolean (GDesktopAppInfo *info,
5088 : : const char *key)
5089 : : {
5090 : 13 : g_return_val_if_fail (G_IS_DESKTOP_APP_INFO (info), FALSE);
5091 : :
5092 : 13 : return g_key_file_get_boolean (info->keyfile,
5093 : : G_KEY_FILE_DESKTOP_GROUP, key, NULL);
5094 : : }
5095 : :
5096 : : /**
5097 : : * g_desktop_app_info_get_string_list:
5098 : : * @info: a #GDesktopAppInfo
5099 : : * @key: the key to look up
5100 : : * @length: (out) (optional): return location for the number of returned strings, or %NULL
5101 : : *
5102 : : * Looks up a string list value in the keyfile backing @info.
5103 : : *
5104 : : * The @key is looked up in the "Desktop Entry" group.
5105 : : *
5106 : : * Returns: (array zero-terminated=1 length=length) (element-type utf8) (transfer full):
5107 : : * a %NULL-terminated string array or %NULL if the specified
5108 : : * key cannot be found. The array should be freed with g_strfreev().
5109 : : *
5110 : : * Since: 2.60
5111 : : */
5112 : : gchar **
5113 : 1 : g_desktop_app_info_get_string_list (GDesktopAppInfo *info,
5114 : : const char *key,
5115 : : gsize *length)
5116 : : {
5117 : 1 : g_return_val_if_fail (G_IS_DESKTOP_APP_INFO (info), NULL);
5118 : :
5119 : 1 : return g_key_file_get_string_list (info->keyfile,
5120 : : G_KEY_FILE_DESKTOP_GROUP, key, length, NULL);
5121 : : }
5122 : :
5123 : : /**
5124 : : * g_desktop_app_info_has_key:
5125 : : * @info: a #GDesktopAppInfo
5126 : : * @key: the key to look up
5127 : : *
5128 : : * Returns whether @key exists in the "Desktop Entry" group
5129 : : * of the keyfile backing @info.
5130 : : *
5131 : : * Returns: %TRUE if the @key exists
5132 : : *
5133 : : * Since: 2.36
5134 : : */
5135 : : gboolean
5136 : 3 : g_desktop_app_info_has_key (GDesktopAppInfo *info,
5137 : : const char *key)
5138 : : {
5139 : 3 : g_return_val_if_fail (G_IS_DESKTOP_APP_INFO (info), FALSE);
5140 : :
5141 : 3 : return g_key_file_has_key (info->keyfile,
5142 : : G_KEY_FILE_DESKTOP_GROUP, key, NULL);
5143 : : }
5144 : :
5145 : : /* Desktop actions support {{{2 */
5146 : :
5147 : : /**
5148 : : * g_desktop_app_info_list_actions:
5149 : : * @info: a #GDesktopAppInfo
5150 : : *
5151 : : * Returns the list of "additional application actions" supported on the
5152 : : * desktop file, as per the desktop file specification.
5153 : : *
5154 : : * As per the specification, this is the list of actions that are
5155 : : * explicitly listed in the "Actions" key of the [Desktop Entry] group.
5156 : : *
5157 : : * Returns: (array zero-terminated=1) (element-type utf8) (transfer none): a list of strings, always non-%NULL
5158 : : *
5159 : : * Since: 2.38
5160 : : **/
5161 : : const gchar * const *
5162 : 1 : g_desktop_app_info_list_actions (GDesktopAppInfo *info)
5163 : : {
5164 : 1 : g_return_val_if_fail (G_IS_DESKTOP_APP_INFO (info), NULL);
5165 : :
5166 : 1 : return (const gchar **) info->actions;
5167 : : }
5168 : :
5169 : : static gboolean
5170 : 12 : app_info_has_action (GDesktopAppInfo *info,
5171 : : const gchar *action_name)
5172 : : {
5173 : : gint i;
5174 : :
5175 [ + - ]: 27 : for (i = 0; info->actions[i]; i++)
5176 [ + + ]: 27 : if (g_str_equal (info->actions[i], action_name))
5177 : 12 : return TRUE;
5178 : :
5179 : 0 : return FALSE;
5180 : : }
5181 : :
5182 : : /**
5183 : : * g_desktop_app_info_get_action_name:
5184 : : * @info: a #GDesktopAppInfo
5185 : : * @action_name: the name of the action as from
5186 : : * g_desktop_app_info_list_actions()
5187 : : *
5188 : : * Gets the user-visible display name of the "additional application
5189 : : * action" specified by @action_name.
5190 : : *
5191 : : * This corresponds to the "Name" key within the keyfile group for the
5192 : : * action.
5193 : : *
5194 : : * Returns: (transfer full): the locale-specific action name
5195 : : *
5196 : : * Since: 2.38
5197 : : */
5198 : : gchar *
5199 : 4 : g_desktop_app_info_get_action_name (GDesktopAppInfo *info,
5200 : : const gchar *action_name)
5201 : : {
5202 : : gchar *group_name;
5203 : : gchar *result;
5204 : :
5205 : 4 : g_return_val_if_fail (G_IS_DESKTOP_APP_INFO (info), NULL);
5206 : 4 : g_return_val_if_fail (action_name != NULL, NULL);
5207 : 4 : g_return_val_if_fail (app_info_has_action (info, action_name), NULL);
5208 : :
5209 : 4 : group_name = g_strdup_printf ("Desktop Action %s", action_name);
5210 : 4 : result = g_key_file_get_locale_string (info->keyfile, group_name, "Name", NULL, NULL);
5211 : 4 : g_free (group_name);
5212 : :
5213 : : /* The spec says that the Name field must be given.
5214 : : *
5215 : : * If it's not, let's follow the behaviour of our get_name()
5216 : : * implementation above and never return %NULL.
5217 : : */
5218 [ + + ]: 4 : if (result == NULL)
5219 : 2 : result = g_strdup (_("Unnamed"));
5220 : :
5221 : 4 : return result;
5222 : : }
5223 : :
5224 : : /**
5225 : : * g_desktop_app_info_launch_action:
5226 : : * @info: a #GDesktopAppInfo
5227 : : * @action_name: the name of the action as from
5228 : : * g_desktop_app_info_list_actions()
5229 : : * @launch_context: (nullable): a #GAppLaunchContext
5230 : : *
5231 : : * Activates the named application action.
5232 : : *
5233 : : * You may only call this function on action names that were
5234 : : * returned from g_desktop_app_info_list_actions().
5235 : : *
5236 : : * Note that if the main entry of the desktop file indicates that the
5237 : : * application supports startup notification, and @launch_context is
5238 : : * non-%NULL, then startup notification will be used when activating the
5239 : : * action (and as such, invocation of the action on the receiving side
5240 : : * must signal the end of startup notification when it is completed).
5241 : : * This is the expected behaviour of applications declaring additional
5242 : : * actions, as per the desktop file specification.
5243 : : *
5244 : : * As with g_app_info_launch() there is no way to detect failures that
5245 : : * occur while using this function.
5246 : : *
5247 : : * Since: 2.38
5248 : : */
5249 : : void
5250 : 8 : g_desktop_app_info_launch_action (GDesktopAppInfo *info,
5251 : : const gchar *action_name,
5252 : : GAppLaunchContext *launch_context)
5253 : : {
5254 : : GDBusConnection *session_bus;
5255 : :
5256 : 8 : g_return_if_fail (G_IS_DESKTOP_APP_INFO (info));
5257 : 8 : g_return_if_fail (action_name != NULL);
5258 : 8 : g_return_if_fail (app_info_has_action (info, action_name));
5259 : :
5260 : 8 : session_bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
5261 : :
5262 [ + - + + ]: 8 : if (session_bus && info->app_id)
5263 : 5 : {
5264 : : gchar *object_path;
5265 : :
5266 : 5 : object_path = object_path_from_appid (info->app_id);
5267 : 5 : g_dbus_connection_call (session_bus, info->app_id, object_path,
5268 : : "org.freedesktop.Application", "ActivateAction",
5269 : : g_variant_new ("(sav@a{sv})", action_name, NULL,
5270 : : g_desktop_app_info_make_platform_data (info, NULL, launch_context)),
5271 : : NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
5272 : 5 : g_free (object_path);
5273 : : }
5274 : : else
5275 : : {
5276 : : gchar *group_name;
5277 : : gchar *exec_line;
5278 : :
5279 : 3 : group_name = g_strdup_printf ("Desktop Action %s", action_name);
5280 : 3 : exec_line = g_key_file_get_string (info->keyfile, group_name, "Exec", NULL);
5281 : 3 : g_free (group_name);
5282 : :
5283 [ + - ]: 3 : if (exec_line)
5284 : 3 : g_desktop_app_info_launch_uris_with_spawn (info, session_bus, exec_line, NULL, launch_context,
5285 : : _SPAWN_FLAGS_DEFAULT, NULL, NULL, NULL, NULL,
5286 : : -1, -1, -1, NULL);
5287 : :
5288 : 3 : g_free (exec_line);
5289 : : }
5290 : :
5291 [ + - ]: 8 : if (session_bus != NULL)
5292 : : {
5293 : 8 : g_dbus_connection_flush (session_bus, NULL, NULL, NULL);
5294 : 8 : g_object_unref (session_bus);
5295 : : }
5296 : : }
5297 : : /* Epilogue {{{1 */
5298 : :
5299 : : /* vim:set foldmethod=marker: */
|