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