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