1
2 #include <config.h>
3
4 #include <stdio.h>
5 #include <stdlib.h>
6 #include <string.h>
7
8 #include <sys/types.h>
9 #include <sys/stat.h>
10 #include <fcntl.h>
11 #include <unistd.h>
12 #include <utime.h>
13
14 #include <gtk/gtk.h>
15 #include <glib/gi18n-lib.h>
16 #include <gio/gdesktopappinfo.h>
17 #include <glib/gstdio.h>
18
19 #include "xapp-favorites.h"
20 #include "favorite-vfs-file.h"
21
22 #define DEBUG_FLAG XAPP_DEBUG_FAVORITES
23 #include "xapp-debug.h"
24
25 #define FAVORITES_SCHEMA "org.x.apps.favorites"
26 #define FAVORITES_KEY "list"
27 #define SETTINGS_DELIMITER "::"
28 #define MAX_DISPLAY_URI_LENGTH 20
29
30 G_DEFINE_BOXED_TYPE (XAppFavoriteInfo, xapp_favorite_info, xapp_favorite_info_copy, xapp_favorite_info_free);
31 /**
32 * SECTION:xapp-favorites
33 * @Short_description: Keeps track of favorite files.
34 * @Title: XAppFavorites
35 *
36 * The XAppFavorites class allows applications display frequently-used files and
37 * provide a safe mechanism for launching them.
38 *
39 * A list of #XAppFavoriteInfos can be retrieved in full, or only for specific mimetypes.
40 *
41 * A favorites uri scheme is also available if the xapp gtk3 module is loaded and this also makes the
42 * uri available as a shortcut in file dialogs.
43 *
44 * XAppFavorites are new for 2.0
45 */
46
47 /**
48 * xapp_favorite_info_copy:
49 * @info: The #XAppFavoriteInfo to duplicate.
50 *
51 * Makes an exact copy of an existing #XAppFavoriteInfo.
52 *
53 * Returns: (transfer full): a new #XAppFavoriteInfo. Free using #xapp_favorite_info_free.
54 *
55 * Since 2.0
56 */
57 XAppFavorites *global_favorites;
58
59 XAppFavoriteInfo *
xapp_favorite_info_copy(const XAppFavoriteInfo * info)60 xapp_favorite_info_copy (const XAppFavoriteInfo *info)
61 {
62 // DEBUG ("XAppFavoriteInfo: copy");
63 g_return_val_if_fail (info != NULL, NULL);
64
65 XAppFavoriteInfo *_info = g_slice_dup (XAppFavoriteInfo, info);
66 _info->uri = g_strdup (info->uri);
67 _info->display_name = g_strdup (info->display_name);
68 _info->cached_mimetype = g_strdup (info->cached_mimetype);
69
70 return _info;
71 }
72
73 /**
74 * xapp_favorite_info_free:
75 * @info: The #XAppFavoriteInfo to free.
76 *
77 * Destroys the #XAppFavoriteInfo.
78 *
79 * Since 2.0
80 */
81 void
xapp_favorite_info_free(XAppFavoriteInfo * info)82 xapp_favorite_info_free (XAppFavoriteInfo *info)
83 {
84 DEBUG ("XAppFavoriteInfo free (%s)", info->uri);
85 g_return_if_fail (info != NULL);
86
87 g_free (info->uri);
88 g_free (info->display_name);
89 g_free (info->cached_mimetype);
90 g_slice_free (XAppFavoriteInfo, info);
91 }
92
93 typedef struct
94 {
95 GHashTable *infos;
96 GHashTable *menus;
97
98 GSettings *settings;
99
100 gulong settings_listener_id;
101 guint changed_timer_id;
102 } XAppFavoritesPrivate;
103
104 struct _XAppFavorites
105 {
106 GObject parent_instance;
107 };
108
109 G_DEFINE_TYPE_WITH_PRIVATE (XAppFavorites, xapp_favorites, G_TYPE_OBJECT)
110
111 enum
112 {
113 CHANGED,
114 LAST_SIGNAL
115 };
116
117 static guint signals[LAST_SIGNAL] = {0, };
118
119 static void finish_add_favorite (XAppFavorites *favorites,
120 const gchar *uri,
121 const gchar *mimetype,
122 gboolean from_saved);
123
124 static gboolean
changed_callback(gpointer data)125 changed_callback (gpointer data)
126 {
127 g_return_val_if_fail (XAPP_IS_FAVORITES (data), G_SOURCE_REMOVE);
128 XAppFavorites *favorites = XAPP_FAVORITES (data);
129
130 XAppFavoritesPrivate *priv = xapp_favorites_get_instance_private (favorites);
131 DEBUG ("XAppFavorites: list updated, emitting changed signal");
132
133 priv->changed_timer_id = 0;
134 g_signal_emit (favorites, signals[CHANGED], 0);
135
136 return G_SOURCE_REMOVE;
137 }
138
139 static void
queue_changed(XAppFavorites * favorites)140 queue_changed (XAppFavorites *favorites)
141 {
142 XAppFavoritesPrivate *priv = xapp_favorites_get_instance_private (favorites);
143
144 if (priv->changed_timer_id > 0)
145 {
146 g_source_remove (priv->changed_timer_id);
147 }
148
149 priv->changed_timer_id = g_idle_add ((GSourceFunc) changed_callback, favorites);
150 }
151
152 static void
sync_metadata_callback(GObject * source,GAsyncResult * res,gpointer user_data)153 sync_metadata_callback (GObject *source,
154 GAsyncResult *res,
155 gpointer user_data)
156 {
157 // Disabled
158 return;
159
160 // GFile *file;
161 // GError *error;
162
163 // file = G_FILE (source);
164 // error = NULL;
165
166 // if (!g_file_set_attributes_finish (file,
167 // res,
168 // NULL,
169 // &error))
170 // {
171 // if (error != NULL)
172 // {
173 // if (error->code != G_IO_ERROR_NOT_FOUND)
174 // {
175 // g_warning ("Could not update file metadata for favorite file '%s': %s", g_file_get_uri (file), error->message);
176 // }
177
178 // g_error_free (error);
179 // }
180 // }
181 // else
182 // {
183 // if (g_file_is_native (file))
184 // {
185 // // I can't think of any other way to touch a file so a file monitor might notice
186 // // the attribute change. It shouldn't be too much trouble since most times add/remove
187 // // will be done in the file manager (where the update can be triggered internally).
188
189 // gchar *local_path = g_file_get_path (file);
190 // g_utime (local_path, NULL);
191 // g_free (local_path);
192 // }
193 // }
194 }
195
196 static void
sync_file_metadata(XAppFavorites * favorites,const gchar * uri,gboolean is_favorite)197 sync_file_metadata (XAppFavorites *favorites,
198 const gchar *uri,
199 gboolean is_favorite)
200 {
201 /* Disabled - this is less than optimal, and is implemented instead in
202 * nemo, currently. This could be changed later to help support other browsers.
203 * Also, this only works with local files. */
204 return;
205
206 /* borrowed from nemo-vfs-file.c */
207 GFileInfo *info;
208 GFile *file;
209
210 DEBUG ("Sync metadata: %s - Favorite? %d", uri, is_favorite);
211
212 info = g_file_info_new ();
213
214 if (is_favorite) {
215 g_file_info_set_attribute_string (info, FAVORITE_METADATA_KEY, META_TRUE);
216 } else {
217 /* Unset the key */
218 g_file_info_set_attribute (info, FAVORITE_METADATA_KEY, G_FILE_ATTRIBUTE_TYPE_INVALID, NULL);
219 }
220
221 file = g_file_new_for_uri (uri);
222
223 g_file_set_attributes_async (file,
224 info,
225 0,
226 G_PRIORITY_DEFAULT,
227 NULL,
228 sync_metadata_callback,
229 favorites);
230
231 g_object_unref (file);
232 g_object_unref (info);
233 }
234
235 static void
store_favorites(XAppFavorites * favorites)236 store_favorites (XAppFavorites *favorites)
237 {
238 XAppFavoritesPrivate *priv = xapp_favorites_get_instance_private (favorites);
239 GList *iter, *keys;
240 GPtrArray *array;
241 gchar **new_settings;
242
243 array = g_ptr_array_new ();
244
245 keys = g_hash_table_get_keys (priv->infos);
246
247 for (iter = keys; iter != NULL; iter = iter->next)
248 {
249 XAppFavoriteInfo *info = (XAppFavoriteInfo *) g_hash_table_lookup (priv->infos, iter->data);
250 gchar *entry;
251
252 entry = g_strjoin (SETTINGS_DELIMITER,
253 info->uri,
254 info->cached_mimetype,
255 NULL);
256
257 g_ptr_array_add (array, entry);
258 }
259
260 g_ptr_array_add (array, NULL);
261
262 g_list_free (keys);
263
264 new_settings = (gchar **) g_ptr_array_free (array, FALSE);
265
266 g_signal_handler_block (priv->settings, priv->settings_listener_id);
267 g_settings_set_strv (priv->settings, FAVORITES_KEY, (const gchar* const*) new_settings);
268 g_signal_handler_unblock (priv->settings, priv->settings_listener_id);
269
270 DEBUG ("XAppFavorites: store_favorites: favorites saved");
271
272 g_strfreev (new_settings);
273 }
274
275 static void
load_favorites(XAppFavorites * favorites,gboolean signal_changed)276 load_favorites (XAppFavorites *favorites,
277 gboolean signal_changed)
278 {
279 XAppFavoritesPrivate *priv = xapp_favorites_get_instance_private (favorites);
280 gchar **raw_list;
281 gint i;
282
283 if (priv->infos != NULL)
284 {
285 g_hash_table_destroy (priv->infos);
286 }
287
288 priv->infos = g_hash_table_new_full (g_str_hash, g_str_equal,
289 g_free, (GDestroyNotify) xapp_favorite_info_free);
290
291 raw_list = g_settings_get_strv (priv->settings, FAVORITES_KEY);
292
293 if (!raw_list)
294 {
295 // no favorites
296 return;
297 }
298
299 for (i = 0; i < g_strv_length (raw_list); i++)
300 {
301 gchar **entry = g_strsplit (raw_list[i], SETTINGS_DELIMITER, 2);
302
303 finish_add_favorite (favorites,
304 entry[0], // uri
305 entry[1], // cached_mimetype
306 TRUE);
307
308 g_strfreev (entry);
309 }
310
311 g_strfreev (raw_list);
312
313 DEBUG ("XAppFavorites: load_favorite: favorites loaded (%d)", i);
314
315 if (signal_changed)
316 {
317 queue_changed (favorites);
318 }
319 }
320
321 static void
rename_favorite(XAppFavorites * favorites,const gchar * old_uri,const gchar * new_uri)322 rename_favorite (XAppFavorites *favorites,
323 const gchar *old_uri,
324 const gchar *new_uri)
325 {
326 XAppFavoriteInfo *info;
327 XAppFavoritesPrivate *priv = xapp_favorites_get_instance_private (favorites);
328 gchar *final_new_uri = NULL;
329
330 if (g_str_has_prefix (old_uri, ROOT_URI))
331 {
332 // Renaming occurred inside of favorites:/// we need to identify by
333 // display name.
334
335 const gchar *old_display_name = old_uri + strlen (ROOT_URI);
336 const gchar *new_display_name = new_uri + strlen (ROOT_URI);
337
338 info = xapp_favorites_find_by_display_name (favorites, old_display_name);
339
340 if (info)
341 {
342 GFile *real_file, *parent, *renamed_file;
343
344 real_file = g_file_new_for_uri (info->uri);
345 parent = g_file_get_parent (real_file);
346
347 renamed_file = g_file_get_child_for_display_name (parent,
348 new_display_name,
349 NULL);
350
351 if (renamed_file != NULL)
352 {
353 final_new_uri = g_file_get_uri (renamed_file);
354 }
355
356 g_object_unref (real_file);
357 g_object_unref (parent);
358 g_clear_object (&renamed_file);
359 }
360 }
361 else
362 {
363 info = g_hash_table_lookup (priv->infos, old_uri);
364 final_new_uri = g_strdup (new_uri);
365 }
366
367 if (info != NULL && final_new_uri != NULL)
368 {
369 gchar *mimetype = g_strdup (info->cached_mimetype);
370
371 sync_file_metadata (favorites, info->uri, FALSE);
372
373 g_hash_table_remove (priv->infos,
374 (gconstpointer) info->uri);
375
376 finish_add_favorite (favorites,
377 final_new_uri,
378 mimetype,
379 FALSE);
380
381 sync_file_metadata (favorites, final_new_uri, TRUE);
382
383 g_free (mimetype);
384 }
385
386 g_free (final_new_uri);
387 }
388
389 static void
remove_favorite(XAppFavorites * favorites,const gchar * uri)390 remove_favorite (XAppFavorites *favorites,
391 const gchar *uri)
392 {
393 XAppFavoritesPrivate *priv = xapp_favorites_get_instance_private (favorites);
394 gchar *real_uri;
395
396 if (g_str_has_prefix (uri, "favorites"))
397 {
398 GFile *file = g_file_new_for_uri (uri);
399 real_uri = favorite_vfs_file_get_real_uri (file);
400
401 g_object_unref (file);
402 }
403 else
404 {
405 real_uri = g_strdup (uri);
406 }
407
408 g_return_if_fail (real_uri != NULL);
409
410 DEBUG ("XAppFavorites: remove favorite: %s", real_uri);
411
412 // It may be orphaned for some reason.. even if it's not in gsettings, still try
413 // to remove the favorite attribute.
414 sync_file_metadata (favorites, real_uri, FALSE);
415
416 if (!g_hash_table_remove (priv->infos, real_uri))
417 {
418 DEBUG ("XAppFavorites: remove_favorite: could not find favorite for uri '%s'", real_uri);
419 g_free (real_uri);
420 return;
421 }
422
423 g_free (real_uri);
424
425 store_favorites (favorites);
426 queue_changed (favorites);
427 }
428
429 static void
deduplicate_display_names(XAppFavorites * favorites,GHashTable * infos)430 deduplicate_display_names (XAppFavorites *favorites,
431 GHashTable *infos)
432 {
433 GList *fav_uris, *ptr;
434 GHashTable *lists_of_keys_by_basename = g_hash_table_new_full (g_str_hash, g_str_equal,
435 g_free, NULL);
436 GHashTableIter iter;
437
438 fav_uris = g_hash_table_get_keys (infos);
439
440 for (ptr = fav_uris; ptr != NULL; ptr = ptr->next)
441 {
442 GList *uris;
443 const gchar *uri = (gchar *) ptr->data;
444 gchar *original_display_name = g_path_get_basename (uri);
445
446 if (g_hash_table_contains (lists_of_keys_by_basename, original_display_name))
447 {
448 uris = g_hash_table_lookup (lists_of_keys_by_basename, original_display_name);
449
450 // this could be prepend, but then the value in the table would have to be replaced
451 uris = g_list_append ((GList *) uris, g_strdup (uri));
452 }
453 else
454 {
455 uris = g_list_prepend (NULL, g_strdup (uri));
456 g_hash_table_insert (lists_of_keys_by_basename,
457 g_strdup (original_display_name),
458 uris);
459 }
460
461 g_free (original_display_name);
462 }
463
464 g_list_free (fav_uris);
465
466 gpointer key, value;
467
468 g_hash_table_iter_init (&iter, lists_of_keys_by_basename);
469
470 while (g_hash_table_iter_next (&iter, &key, &value))
471 {
472 GList *same_names_list, *uri_ptr;
473 gchar *common_display_name = NULL;
474
475 if (((GList *) value)->next == NULL)
476 {
477 // Single member of current common name list;
478 g_list_free_full ((GList *) value, g_free);
479 continue;
480 }
481 // Now we know we have a list of uris that would have identical display names
482 // Add a part of the uri after each to distinguish them.
483 common_display_name = g_uri_unescape_string ((const gchar *) key, NULL);
484 same_names_list = (GList *) value;
485
486 for (uri_ptr = same_names_list; uri_ptr != NULL; uri_ptr = uri_ptr->next)
487 {
488 XAppFavoriteInfo *info;
489 GFile *uri_file, *home_file, *parent_file;
490 GString *new_display_string;
491 const gchar *current_uri;
492
493 current_uri = (const gchar *) uri_ptr->data;
494
495 uri_file = g_file_new_for_uri (current_uri);
496 parent_file = g_file_get_parent (uri_file);
497 home_file = g_file_new_for_path (g_get_home_dir());
498
499 new_display_string = g_string_new (common_display_name);
500 g_string_append (new_display_string, " (");
501
502 // How much effort should we put into duplicate naming? Keeping it
503 // simple like this won't work all the time.
504 gchar *parent_basename = g_file_get_basename (parent_file);
505 g_string_append (new_display_string, parent_basename);
506 g_free (parent_basename);
507
508 // TODO: ellipsized deduplication paths?
509
510 // if (g_file_has_prefix (parent_file, home_file))
511 // {
512 // gchar *home_rpath = g_file_get_relative_path (home_file, parent_file);
513 // gchar *home_basename = g_file_get_basename (home_file);
514
515 // if (strlen (home_rpath) < MAX_DISPLAY_URI_LENGTH)
516 // {
517 // g_string_append (new_display_string, home_basename);
518 // g_string_append (new_display_string, "/");
519 // g_string_append (new_display_string, home_rpath);
520 // }
521 // else
522 // {
523 // gchar *parent_basename = g_file_get_basename (parent_file);
524
525 // g_string_append (new_display_string, home_basename);
526 // g_string_append (new_display_string, "/.../");
527 // g_string_append (new_display_string, parent_basename);
528
529 // g_free (parent_basename);
530 // }
531
532 // g_free (home_rpath);
533 // g_free (home_basename);
534 // }
535 // else
536 // {
537 // GString *tmp_string = g_string_new (NULL);
538
539 // if (g_file_is_native (parent_file))
540 // {
541 // g_string_append (tmp_string, g_file_peek_path (parent_file));
542 // }
543 // else
544 // {
545 // g_string_append (tmp_string, current_uri);
546 // }
547
548 // if (tmp_string->len > MAX_DISPLAY_URI_LENGTH)
549 // {
550 // gint diff;
551 // gint replace_pos;
552
553 // diff = tmp_string->len - MAX_DISPLAY_URI_LENGTH;
554 // replace_pos = (tmp_string->len / 2) - (diff / 2) - 2;
555
556 // g_string_erase (tmp_string,
557 // replace_pos,
558 // diff);
559 // g_string_insert (tmp_string,
560 // replace_pos,
561 // "...");
562 // }
563
564 // g_string_append (new_display_string, tmp_string->str);
565 // g_string_free (tmp_string, TRUE);
566 // }
567
568 g_object_unref (uri_file);
569 g_object_unref (home_file);
570 g_object_unref (parent_file);
571
572 g_string_append (new_display_string, ")");
573
574 // Look up the info from our master table
575 info = g_hash_table_lookup (infos, current_uri);
576 g_free (info->display_name);
577
578 info->display_name = g_string_free (new_display_string, FALSE);
579 }
580
581 g_free (common_display_name);
582 g_list_free_full (same_names_list, g_free);
583 }
584
585 // We freed the individual lists just above, only the keys will need
586 // freed here.
587 g_hash_table_destroy (lists_of_keys_by_basename);
588 }
589
590 static void
finish_add_favorite(XAppFavorites * favorites,const gchar * uri,const gchar * cached_mimetype,gboolean from_saved)591 finish_add_favorite (XAppFavorites *favorites,
592 const gchar *uri,
593 const gchar *cached_mimetype,
594 gboolean from_saved)
595 {
596 XAppFavoritesPrivate *priv = xapp_favorites_get_instance_private (favorites);
597 XAppFavoriteInfo *info;
598 gchar *unescaped_uri;
599
600 // Check if it's there again, in case it was added while we were getting mimetype.
601 if (g_hash_table_contains (priv->infos, uri))
602 {
603 DEBUG ("XAppFavorites: favorite for '%s' exists, ignoring", uri);
604 return;
605 }
606
607 info = g_slice_new0 (XAppFavoriteInfo);
608 info->uri = g_strdup (uri);
609
610 unescaped_uri = g_uri_unescape_string (uri, NULL);
611 info->display_name = g_path_get_basename (unescaped_uri);
612 g_free (unescaped_uri);
613
614 info->cached_mimetype = g_strdup (cached_mimetype);
615
616 g_hash_table_insert (priv->infos, (gpointer) g_strdup (uri), (gpointer) info);
617
618 DEBUG ("XAppFavorites: added favorite: %s", uri);
619
620 deduplicate_display_names (favorites, priv->infos);
621
622 if (from_saved)
623 {
624 return;
625 }
626
627 store_favorites (favorites);
628 queue_changed (favorites);
629 }
630
631 static void
on_content_type_info_received(GObject * source,GAsyncResult * res,gpointer user_data)632 on_content_type_info_received (GObject *source,
633 GAsyncResult *res,
634 gpointer user_data)
635 {
636 XAppFavorites *favorites = XAPP_FAVORITES (user_data);
637 GFile *file;
638 GFileInfo *file_info;
639 GError *error;
640 gchar *cached_mimetype, *uri;
641
642 file = G_FILE (source);
643 uri = g_file_get_uri (file);
644 error = NULL;
645 cached_mimetype = NULL;
646
647 file_info = g_file_query_info_finish (file, res, &error);
648
649 if (error)
650 {
651 DEBUG ("XAppFavorites: problem trying to figure out content type for uri '%s': %s",
652 uri, error->message);
653 g_error_free (error);
654 }
655
656 if (file_info)
657 {
658 cached_mimetype = g_strdup (g_file_info_get_content_type (file_info));
659
660 if (cached_mimetype == NULL)
661 {
662 cached_mimetype = g_strdup ("application/unknown");
663 }
664
665 finish_add_favorite (favorites,
666 uri,
667 cached_mimetype,
668 FALSE);
669
670 sync_file_metadata (favorites, uri, TRUE);
671 }
672
673 g_free (uri);
674 g_free (cached_mimetype);
675 g_clear_object (&file_info);
676 g_object_unref (file);
677 }
678
679 static void
add_favorite(XAppFavorites * favorites,const gchar * uri)680 add_favorite (XAppFavorites *favorites,
681 const gchar *uri)
682 {
683 XAppFavoritesPrivate *priv = xapp_favorites_get_instance_private (favorites);
684 GFile *file;
685
686 if (g_hash_table_contains (priv->infos, uri))
687 {
688 DEBUG ("XAppFavorites: favorite for '%s' exists, ignoring", uri);
689 return;
690 }
691
692 file = g_file_new_for_uri (uri);
693
694 g_file_query_info_async (file,
695 G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
696 G_FILE_QUERY_INFO_NONE,
697 G_PRIORITY_LOW,
698 NULL,
699 on_content_type_info_received,
700 favorites);
701 }
702
703 static void
on_settings_list_changed(GSettings * settings,gchar * key,gpointer user_data)704 on_settings_list_changed (GSettings *settings,
705 gchar *key,
706 gpointer user_data)
707 {
708 XAppFavorites *favorites = XAPP_FAVORITES (user_data);
709
710 load_favorites (favorites, TRUE);
711 }
712
713 static void
xapp_favorites_init(XAppFavorites * favorites)714 xapp_favorites_init (XAppFavorites *favorites)
715 {
716 XAppFavoritesPrivate *priv = xapp_favorites_get_instance_private (favorites);
717
718 DEBUG ("XAppFavorites: init:");
719
720 priv->settings = g_settings_new (FAVORITES_SCHEMA);
721 priv->settings_listener_id = g_signal_connect (priv->settings,
722 "changed::" FAVORITES_KEY,
723 G_CALLBACK (on_settings_list_changed),
724 favorites);
725
726 load_favorites (favorites, FALSE);
727 }
728
729 static void
xapp_favorites_dispose(GObject * object)730 xapp_favorites_dispose (GObject *object)
731 {
732 XAppFavorites *favorites = XAPP_FAVORITES (object);
733 XAppFavoritesPrivate *priv = xapp_favorites_get_instance_private (favorites);
734
735 DEBUG ("XAppFavorites dispose (%p)", object);
736
737 g_clear_object (&priv->settings);
738 g_clear_pointer (&priv->infos, g_hash_table_destroy);
739
740 G_OBJECT_CLASS (xapp_favorites_parent_class)->dispose (object);
741 }
742
743 static void
xapp_favorites_finalize(GObject * object)744 xapp_favorites_finalize (GObject *object)
745 {
746 DEBUG ("XAppFavorites finalize (%p)", object);
747
748 G_OBJECT_CLASS (xapp_favorites_parent_class)->finalize (object);
749 }
750
751 static void
xapp_favorites_class_init(XAppFavoritesClass * klass)752 xapp_favorites_class_init (XAppFavoritesClass *klass)
753 {
754 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
755
756 gobject_class->dispose = xapp_favorites_dispose;
757 gobject_class->finalize = xapp_favorites_finalize;
758
759 /**
760 * XAppFavorites::changed:
761
762 * Notifies when the favorites list has changed.
763 */
764 signals [CHANGED] =
765 g_signal_new ("changed",
766 XAPP_TYPE_FAVORITES,
767 G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
768 0,
769 NULL, NULL, NULL,
770 G_TYPE_NONE, 0);
771 }
772
773 /**
774 * xapp_favorites_get_default:
775 *
776 * Returns the #XAppFavorites instance.
777 *
778 * Returns: (transfer none): the XAppFavorites instance for the process. Do not free.
779 *
780 * Since: 2.0
781 */
782 XAppFavorites *
xapp_favorites_get_default(void)783 xapp_favorites_get_default (void)
784 {
785 if (global_favorites == NULL)
786 {
787 init_favorite_vfs ();
788 global_favorites = g_object_new (XAPP_TYPE_FAVORITES, NULL);
789 }
790
791 return global_favorites;
792 }
793
794 typedef struct {
795 GList *items;
796 const gchar **mimetypes;
797 } MatchData;
798
799 void
match_mimetypes(gpointer key,gpointer value,gpointer user_data)800 match_mimetypes (gpointer key,
801 gpointer value,
802 gpointer user_data)
803 {
804 MatchData *data = (MatchData *) user_data;
805 const XAppFavoriteInfo *info = (XAppFavoriteInfo *) value;
806
807 if (data->mimetypes == NULL)
808 {
809 data->items = g_list_prepend (data->items, xapp_favorite_info_copy (info));
810 return;
811 }
812
813 gint i;
814
815 for (i = 0; i < g_strv_length ((gchar **) data->mimetypes); i++)
816 {
817 if (g_content_type_is_mime_type (info->cached_mimetype, data->mimetypes[i]))
818 {
819 data->items = g_list_prepend (data->items, xapp_favorite_info_copy (info));
820 return;
821 }
822 }
823 }
824
825 /**
826 * xapp_favorites_get_favorites:
827 * @favorites: The #XAppFavorites
828 * @mimetypes: (nullable) (array zero-terminated=1): The mimetypes to filter by for results
829 *
830 * Gets a list of all favorites. If mimetype is not %NULL, the list will
831 * contain only favorites with that mimetype.
832 *
833 * Returns: (element-type XAppFavoriteInfo) (transfer full): a list of #XAppFavoriteInfos.
834 Free the list with #g_list_free, free elements with #xapp_favorite_info_free.
835 *
836 * Since: 2.0
837 */
838 GList *
xapp_favorites_get_favorites(XAppFavorites * favorites,const gchar * const * mimetypes)839 xapp_favorites_get_favorites (XAppFavorites *favorites,
840 const gchar * const *mimetypes)
841 {
842 g_return_val_if_fail (XAPP_IS_FAVORITES (favorites), NULL);
843 XAppFavoritesPrivate *priv = xapp_favorites_get_instance_private (favorites);
844 GList *ret = NULL;
845 MatchData data;
846
847 data.items = NULL;
848 data.mimetypes = (const gchar **) mimetypes;
849 g_hash_table_foreach (priv->infos,
850 (GHFunc) match_mimetypes,
851 &data);
852
853 ret = g_list_reverse (data.items);
854
855 gchar *typestring = mimetypes ? g_strjoinv (", ", (gchar **) mimetypes) : NULL;
856 DEBUG ("XAppFavorites: get_favorites returning list for mimetype '%s' (%d items)",
857 typestring, g_list_length (ret));
858 g_free (typestring);
859
860 return ret;
861 }
862
863 /**
864 * xapp_favorites_get_n_favorites:
865 * @favorites: The #XAppFavorites
866 *
867 * Returns: The number of favorite files
868
869 * Since: 2.0
870 */
871 gint
xapp_favorites_get_n_favorites(XAppFavorites * favorites)872 xapp_favorites_get_n_favorites (XAppFavorites *favorites)
873 {
874 g_return_val_if_fail (XAPP_IS_FAVORITES (favorites), 0);
875 XAppFavoritesPrivate *priv = xapp_favorites_get_instance_private (favorites);
876 gint n;
877
878 n = g_hash_table_size (priv->infos);
879
880 DEBUG ("XAppFavorites: get_n_favorites returning number of items: %d.", n);
881
882 return n;
883 }
884
885 static gboolean
lookup_display_name(gpointer key,gpointer value,gpointer user_data)886 lookup_display_name (gpointer key,
887 gpointer value,
888 gpointer user_data)
889 {
890 XAppFavoriteInfo *info = (XAppFavoriteInfo *) value;
891
892 if (g_strcmp0 (info->display_name, (const gchar *) user_data) == 0)
893 {
894 return TRUE;
895 }
896
897 return FALSE;
898 }
899
900 /**
901 * xapp_favorites_find_by_display_name:
902 * @favorites: The #XAppFavorites
903 * @display_name: (not nullable): The display name to lookup info for.
904 *
905 * Looks for an XAppFavoriteInfo that corresponds to @display_name.
906 *
907 * Returns: (transfer none): an XAppFavoriteInfo or NULL if one was not found. This is owned
908 * by the favorites manager and should not be freed.
909 *
910 * Since: 2.0
911 */
912 XAppFavoriteInfo *
xapp_favorites_find_by_display_name(XAppFavorites * favorites,const gchar * display_name)913 xapp_favorites_find_by_display_name (XAppFavorites *favorites,
914 const gchar *display_name)
915 {
916 g_return_val_if_fail (XAPP_IS_FAVORITES (favorites), NULL);
917 g_return_val_if_fail (display_name != NULL, NULL);
918
919 XAppFavoriteInfo *info;
920 XAppFavoritesPrivate *priv = xapp_favorites_get_instance_private (favorites);
921
922 info = g_hash_table_find (priv->infos,
923 (GHRFunc) lookup_display_name,
924 (gpointer) display_name);
925
926 if (info != NULL)
927 {
928 return info;
929 }
930
931 return NULL;
932 }
933
934 /**
935 * xapp_favorites_find_by_uri:
936 * @favorites: The #XAppFavorites
937 * @uri: (not nullable): The uri to lookup info for.
938 *
939 * Looks for an XAppFavoriteInfo that corresponds to @uri.
940 *
941 * Returns: (transfer none): an XAppFavoriteInfo or NULL if one was not found. This is owned
942 * by the favorites manager and should not be freed.
943 *
944 * Since: 2.0
945 */
946 XAppFavoriteInfo *
xapp_favorites_find_by_uri(XAppFavorites * favorites,const gchar * uri)947 xapp_favorites_find_by_uri (XAppFavorites *favorites,
948 const gchar *uri)
949 {
950 g_return_val_if_fail (XAPP_IS_FAVORITES (favorites), NULL);
951 g_return_val_if_fail (uri != NULL, NULL);
952
953 XAppFavoriteInfo *info;
954 XAppFavoritesPrivate *priv = xapp_favorites_get_instance_private (favorites);
955
956 info = g_hash_table_lookup (priv->infos, uri);
957
958 if (info != NULL)
959 {
960 return (XAppFavoriteInfo *) info;
961 }
962
963 return NULL;
964 }
965
966 /**
967 * xapp_favorites_add:
968 * @favorites: The #XAppFavorites
969 * @uri: The uri the favorite is for
970 *
971 * Adds a new favorite. If the uri already exists, this does nothing.
972 *
973 * Since: 2.0
974 */
975 void
xapp_favorites_add(XAppFavorites * favorites,const gchar * uri)976 xapp_favorites_add (XAppFavorites *favorites,
977 const gchar *uri)
978 {
979 g_return_if_fail (XAPP_IS_FAVORITES (favorites));
980 g_return_if_fail (uri != NULL);
981
982 add_favorite (favorites, uri);
983 }
984
985 /**
986 * xapp_favorites_remove:
987 * @favorites: The #XAppFavorites
988 * @uri: The uri for the favorite being removed
989 *
990 * Removes a favorite from the list.
991 *
992 * Since: 2.0
993 */
994 void
xapp_favorites_remove(XAppFavorites * favorites,const gchar * uri)995 xapp_favorites_remove (XAppFavorites *favorites,
996 const gchar *uri)
997 {
998 g_return_if_fail (XAPP_IS_FAVORITES (favorites));
999 g_return_if_fail (uri != NULL);
1000
1001 remove_favorite (favorites, uri);
1002 }
1003
1004 static void
launch_uri_callback(GObject * source,GAsyncResult * res,gpointer user_data)1005 launch_uri_callback (GObject *source,
1006 GAsyncResult *res,
1007 gpointer user_data)
1008 {
1009 gchar *uri = (gchar *) user_data;
1010 GError *error;
1011
1012 error = NULL;
1013
1014 if (!g_app_info_launch_default_for_uri_finish (res, &error))
1015 {
1016 if (error)
1017 {
1018 DEBUG ("XAppFavorites: launch: error opening uri '%s': %s", uri, error->message);
1019 g_error_free (error);
1020 }
1021 }
1022
1023 g_free (uri);
1024 }
1025
1026 /**
1027 * xapp_favorites_launch:
1028 * @favorites: The #XAppFavorites
1029 * @uri: The uri for the favorite to launch
1030 * @timestamp: The timestamp from an event or 0
1031 *
1032 * Opens a favorite in its default app.
1033 *
1034 * Since: 2.0
1035 */
1036 void
xapp_favorites_launch(XAppFavorites * favorites,const gchar * uri,guint32 timestamp)1037 xapp_favorites_launch (XAppFavorites *favorites,
1038 const gchar *uri,
1039 guint32 timestamp)
1040 {
1041 GdkDisplay *display;
1042 GdkAppLaunchContext *launch_context;
1043
1044 display = gdk_display_get_default ();
1045 launch_context = gdk_display_get_app_launch_context (display);
1046 gdk_app_launch_context_set_timestamp (launch_context, timestamp);
1047
1048 g_app_info_launch_default_for_uri_async (uri,
1049 G_APP_LAUNCH_CONTEXT (launch_context),
1050 NULL,
1051 launch_uri_callback,
1052 g_strdup (uri));
1053
1054 g_object_unref (launch_context);
1055 }
1056
1057 /**
1058 * xapp_favorites_rename:
1059 * @old_uri: the old favorite's uri.
1060 * @new_uri: The new uri.
1061 *
1062 * Removes old_uri and adds new_uri. This is mainly for file managers to use as
1063 * a convenience instead of add/remove, and guarantees the result, without having to
1064 * worry about multiple dbus calls (gsettings).
1065 *
1066 * Since: 2.0
1067 */
1068 void
xapp_favorites_rename(XAppFavorites * favorites,const gchar * old_uri,const gchar * new_uri)1069 xapp_favorites_rename (XAppFavorites *favorites,
1070 const gchar *old_uri,
1071 const gchar *new_uri)
1072 {
1073 g_return_if_fail (XAPP_IS_FAVORITES (favorites));
1074 g_return_if_fail (old_uri != NULL && new_uri != NULL);
1075
1076 rename_favorite (favorites, old_uri, new_uri);
1077 }
1078
1079 typedef struct {
1080 XAppFavorites *favorites;
1081 guint update_id;
1082
1083 GDestroyNotify destroy_func;
1084 gpointer user_data;
1085 } DestroyData;
1086
1087 typedef struct {
1088 XAppFavorites *favorites;
1089 XAppFavoritesItemSelectedCallback callback;
1090 gchar *uri;
1091 gpointer user_data;
1092 } ItemCallbackData;
1093
1094 static void
cb_data_destroy_notify(gpointer callback_data,GObject * object)1095 cb_data_destroy_notify (gpointer callback_data,
1096 GObject *object)
1097 {
1098 DestroyData *dd = (DestroyData *) callback_data;
1099
1100 if (dd->update_id > 0)
1101 {
1102 g_signal_handler_disconnect (dd->favorites, dd->update_id);
1103 }
1104
1105 dd->destroy_func (dd->user_data);
1106
1107 g_slice_free (DestroyData, dd);
1108 }
1109
1110 static void
free_item_callback_data(gpointer callback_data,GClosure * closure)1111 free_item_callback_data (gpointer callback_data,
1112 GClosure *closure)
1113 {
1114 ItemCallbackData *data = (ItemCallbackData *) callback_data;
1115 g_free (data->uri);
1116 g_slice_free (ItemCallbackData, data);
1117 }
1118
1119 static void
item_activated(GObject * item,gpointer user_data)1120 item_activated (GObject *item,
1121 gpointer user_data)
1122 {
1123 ItemCallbackData *data = (ItemCallbackData *) user_data;
1124
1125 data->callback (data->favorites,
1126 data->uri,
1127 data->user_data);
1128 }
1129
1130 static void
remove_menu_item(GtkWidget * item,gpointer user_data)1131 remove_menu_item (GtkWidget *item,
1132 gpointer user_data)
1133 {
1134 gtk_container_remove (GTK_CONTAINER (user_data), item);
1135 }
1136
1137 static void
populate_menu(XAppFavorites * favorites,GtkMenu * menu)1138 populate_menu (XAppFavorites *favorites,
1139 GtkMenu *menu)
1140 {
1141 GList *fav_list, *ptr;
1142 GtkWidget *item;
1143 XAppFavoritesItemSelectedCallback callback;
1144 gpointer user_data;
1145 const gchar **mimetypes;
1146
1147 gtk_container_foreach (GTK_CONTAINER (menu), (GtkCallback) remove_menu_item, menu);
1148
1149 mimetypes = (const gchar **) g_object_get_data (G_OBJECT (menu), "mimetypes");
1150 callback = g_object_get_data (G_OBJECT (menu), "activate-cb");
1151 user_data = g_object_get_data (G_OBJECT (menu), "user-data");
1152
1153 fav_list = xapp_favorites_get_favorites (favorites, mimetypes);
1154
1155 if (fav_list == NULL)
1156 {
1157 return;
1158 }
1159
1160 for (ptr = fav_list; ptr != NULL; ptr = ptr->next)
1161 {
1162 XAppFavoriteInfo *info = (XAppFavoriteInfo *) ptr->data;
1163 ItemCallbackData *data;
1164
1165 if (mimetypes != NULL)
1166 {
1167 item = gtk_menu_item_new_with_label (info->display_name);
1168 }
1169 else
1170 {
1171 GtkWidget *image;
1172 GIcon *icon;
1173
1174 icon = g_content_type_get_symbolic_icon (info->cached_mimetype);
1175 image = gtk_image_new_from_gicon (icon, GTK_ICON_SIZE_MENU);
1176 g_object_unref (icon);
1177
1178 item = gtk_image_menu_item_new_with_label (info->display_name);
1179 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
1180 }
1181
1182 data = g_slice_new0 (ItemCallbackData);
1183 data->favorites = favorites;
1184 data->uri = g_strdup (info->uri);
1185 data->callback = callback;
1186 data->user_data = user_data;
1187
1188 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
1189
1190 g_signal_connect_data (item,
1191 "activate", G_CALLBACK (item_activated),
1192 data, (GClosureNotify) free_item_callback_data, 0);
1193 }
1194
1195 g_list_free_full (fav_list, (GDestroyNotify) xapp_favorite_info_free);
1196
1197 gtk_widget_show_all (GTK_WIDGET (menu));
1198 }
1199
1200 static void
refresh_menu_items(XAppFavorites * favorites,gpointer user_data)1201 refresh_menu_items (XAppFavorites *favorites,
1202 gpointer user_data)
1203 {
1204 g_return_if_fail (XAPP_IS_FAVORITES (favorites));
1205 g_return_if_fail (GTK_IS_MENU (user_data));
1206
1207 GtkMenu *menu = GTK_MENU (user_data);
1208
1209 populate_menu (favorites, menu);
1210 }
1211
1212 /**
1213 * xapp_favorites_create_menu:
1214 * @favorites: The #XAppFavorites instance.
1215 * @mimetypes: (nullable): The mimetypes to filter for, or NULL to include all favorites.
1216 * @callback: (scope notified): (closure user_data): The callback to use when a menu item has been selected.
1217 * @user_data: (closure): The data to pass to the callback
1218 * @func: Destroy function for user_data
1219 *
1220 * Generates a GtkMenu widget populated with favorites. The callback will be called when
1221 * a menu item has been activated, and will include the uri of the respective item.
1222 *
1223 * Returns: (transfer full): a new #GtkMenu populated with a list of favorites, or NULL
1224 if there are no favorites.
1225 *
1226 * Since: 2.0
1227 */
1228 GtkWidget *
xapp_favorites_create_menu(XAppFavorites * favorites,const gchar ** mimetypes,XAppFavoritesItemSelectedCallback callback,gpointer user_data,GDestroyNotify func)1229 xapp_favorites_create_menu (XAppFavorites *favorites,
1230 const gchar **mimetypes,
1231 XAppFavoritesItemSelectedCallback callback,
1232 gpointer user_data,
1233 GDestroyNotify func)
1234 {
1235 g_return_val_if_fail (XAPP_IS_FAVORITES (favorites), NULL);
1236 GtkWidget *menu;
1237
1238 menu = gtk_menu_new ();
1239
1240 g_object_set_data_full (G_OBJECT (menu),
1241 "mimetype", g_strdupv ((gchar **) mimetypes),
1242 (GDestroyNotify) g_strfreev);
1243
1244 g_object_set_data (G_OBJECT (menu),
1245 "activate-cb", callback);
1246
1247 g_object_set_data (G_OBJECT (menu),
1248 "user-data", user_data);
1249
1250 populate_menu (favorites, GTK_MENU (menu));
1251
1252 DestroyData *dd = g_slice_new0 (DestroyData);
1253 dd->destroy_func = func;
1254 dd->user_data = user_data;
1255 dd->favorites = favorites;
1256 dd->update_id = g_signal_connect (favorites,
1257 "changed",
1258 G_CALLBACK (refresh_menu_items),
1259 menu);
1260
1261 g_object_weak_ref (G_OBJECT (menu), (GWeakNotify) cb_data_destroy_notify, dd);
1262
1263 return menu;
1264 }
1265
1266 static GList *
populate_action_list(XAppFavorites * favorites,const gchar ** mimetypes)1267 populate_action_list (XAppFavorites *favorites,
1268 const gchar **mimetypes)
1269 {
1270 GList *fav_list, *ptr;
1271 GList *actions;
1272 GtkAction *action;
1273 gint i;
1274
1275 fav_list = xapp_favorites_get_favorites (favorites, mimetypes);
1276
1277 if (fav_list == NULL)
1278 {
1279 return NULL;
1280 }
1281
1282 actions = NULL;
1283
1284 for (ptr = fav_list, i = 0; ptr != NULL; ptr = ptr->next, i++)
1285 {
1286 XAppFavoriteInfo *info = (XAppFavoriteInfo *) ptr->data;
1287
1288 if (mimetypes != NULL)
1289 {
1290 action = g_object_new (GTK_TYPE_ACTION,
1291 "name", info->uri,
1292 "label", info->display_name,
1293 NULL);
1294 }
1295 else
1296 {
1297 GIcon *icon;
1298 icon = g_content_type_get_symbolic_icon (info->cached_mimetype);
1299
1300 action = g_object_new (GTK_TYPE_ACTION,
1301 "name", info->uri,
1302 "label", info->display_name,
1303 "gicon", icon,
1304 NULL);
1305
1306 g_free (icon);
1307 }
1308
1309 actions = g_list_prepend (actions, action);
1310 }
1311
1312 actions = g_list_reverse (actions);
1313
1314 return actions;
1315 }
1316
1317 /**
1318 * xapp_favorites_create_actions:
1319 * @favorites: The #XAppFavorites instance.
1320 * @mimetypes: (nullable): The mimetypes to filter for, or NULL to include all favorites.
1321 *
1322 * Generates a list of favorite GtkActions.
1323 *
1324 * Returns: (element-type Gtk.Action) (transfer full): a new #GtkActionGroup populated with a list of favorites, or NULL
1325 if there are no favorites.
1326
1327 * Since: 2.0
1328 */
1329 GList *
xapp_favorites_create_actions(XAppFavorites * favorites,const gchar ** mimetypes)1330 xapp_favorites_create_actions (XAppFavorites *favorites,
1331 const gchar **mimetypes)
1332 {
1333 g_return_val_if_fail (XAPP_IS_FAVORITES (favorites), NULL);
1334 GList *actions;
1335
1336 actions = populate_action_list (favorites,
1337 mimetypes);
1338
1339 return actions;
1340 }
1341
1342 /* Used by favorite_vfs_file */
1343 GList *
_xapp_favorites_get_display_names(XAppFavorites * favorites)1344 _xapp_favorites_get_display_names (XAppFavorites *favorites)
1345 {
1346 g_return_val_if_fail (XAPP_IS_FAVORITES (favorites), NULL);
1347 XAppFavoritesPrivate *priv = xapp_favorites_get_instance_private (favorites);
1348 GHashTableIter iter;
1349 GList *ret;
1350 gpointer key, value;
1351
1352 ret = NULL;
1353 g_hash_table_iter_init (&iter, priv->infos);
1354
1355 while (g_hash_table_iter_next (&iter, &key, &value))
1356 {
1357 XAppFavoriteInfo *info = (XAppFavoriteInfo *) value;
1358 ret = g_list_prepend (ret, info->display_name);
1359 }
1360
1361 ret = g_list_reverse (ret);
1362 return ret;
1363 }
1364
1365