diff --git a/.gitignore b/.gitignore index 1413453..c635c3f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ # Specify filepatterns you want git to ignore. -debian/build/ +obj-x86_64-linux-gnu debian/tmp/ debian/*.debhelper debian/gir1.2-xapp-1.0/ diff --git a/README.md b/README.md index 05d5c9e..2a632ee 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ # API Reference -https://projects.linuxmint.com/xapps/reference/index.html +https://projects.linuxmint.com/xapp/reference/index.html # xapp-common diff --git a/data/80xapp-gtk3-module b/data/80xapp-gtk3-module new file mode 100644 index 0000000..004cebb --- /dev/null +++ b/data/80xapp-gtk3-module @@ -0,0 +1,9 @@ +# This file is sourced by Xsession(5), not executed. + +if [ -z "$GTK_MODULES" ] ; then + GTK_MODULES="xapp-gtk3-module" +else + GTK_MODULES="$GTK_MODULES:xapp-gtk3-module" +fi + +export GTK_MODULES diff --git a/data/meson.build b/data/meson.build new file mode 100644 index 0000000..e3d768a --- /dev/null +++ b/data/meson.build @@ -0,0 +1,3 @@ +install_data(['80xapp-gtk3-module'], + install_dir: join_paths(get_option('sysconfdir'), 'X11', 'Xsession.d') +) diff --git a/debian/changelog b/debian/changelog index 624a5df..7d9e3df 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,18 @@ +xapp (2.0.0) ulyssa; urgency=medium + + [ Michael Webster ] + * Bump for favorites support + * .gitignore: Ignore the new debian build folder. + * favorites: expose xapp_favorites_rename (), it was mistakenly made private during some cleanup. + * xapp-gtk3-module.c: Don't initialize favorites until they're needed. + * xapp-gtk3-module.c: Register the favorites uri scheme immediately, rather than when XAppFavorites is instantiated. + * xapp-favorites: Add missing pointer to the DestroyData. + + [ Clement Lefebvre ] + * l10n: Update POT + + -- Clement Lefebvre Wed, 25 Nov 2020 11:27:06 +0000 + xapp (1.8.9) ulyana; urgency=medium [ Michael Webster ] diff --git a/debian/clean b/debian/clean deleted file mode 100644 index 9e6a17f..0000000 --- a/debian/clean +++ /dev/null @@ -1 +0,0 @@ -debian/build/ \ No newline at end of file diff --git a/debian/compat b/debian/compat deleted file mode 100644 index f599e28..0000000 --- a/debian/compat +++ /dev/null @@ -1 +0,0 @@ -10 diff --git a/debian/control b/debian/control index af87f95..de0fd45 100644 --- a/debian/control +++ b/debian/control @@ -3,7 +3,7 @@ Priority: optional Maintainer: Clement Lefebvre Build-Depends: - debhelper (>= 10), + debhelper-compat (= 12), dh-python, dpkg-dev (>= 1.15.1), gobject-introspection (>= 0.10.2-1~), diff --git a/debian/libxapp1.install b/debian/libxapp1.install index 1ceb047..b4fd5df 100644 --- a/debian/libxapp1.install +++ b/debian/libxapp1.install @@ -1,2 +1,4 @@ -usr/lib/*/libxapp.so.1* +usr/lib/*/libxapp.so.* usr/libexec/xapps/sn-watcher/* +usr/lib/*/gtk-3.0/modules +/etc/X11 diff --git a/debian/rules b/debian/rules index de2815a..c66b9c5 100755 --- a/debian/rules +++ b/debian/rules @@ -1,28 +1,15 @@ #!/usr/bin/make -f -# FIXME: much of this can be removed once we move off of xenial/mint 18 base support -# meson support is baked in +export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed -Wl,-z,now %: dh $@ --with=gir,python3 override_dh_auto_configure: - mkdir -p debian/build - meson debian/build \ - --prefix=/usr \ - --buildtype=plain \ + dh_auto_configure -- \ + --libexecdir=/usr/libexec \ -D docs=true \ -D deprecated_warnings=false override_dh_strip: dh_strip --dbg-package=libxapp-dbg - -override_dh_auto_build: - ninja -C debian/build - -override_dh_auto_test: - ninja -C debian/build test - -override_dh_auto_install: - DESTDIR=${CURDIR}/debian/tmp \ - ninja -C debian/build install diff --git a/docs/meson.build b/docs/meson.build index b43ec9a..62214bb 100644 --- a/docs/meson.build +++ b/docs/meson.build @@ -11,4 +11,5 @@ main_xml: 'xapp-docs.xml', scan_args: ['--rebuild-types'], mkdb_args: ['--xml-mode', '--output-format=xml'], -) \ No newline at end of file + ignore_headers: ['favorite-vfs-file.h', 'favorite-vfs-file-enumerator.h', 'favorite-vfs-file-monitor.h'] +) diff --git a/docs/xapp-docs.xml b/docs/xapp-docs.xml index 69df987..a3adceb 100644 --- a/docs/xapp-docs.xml +++ b/docs/xapp-docs.xml @@ -16,6 +16,7 @@ API reference + diff --git a/icons/hicolor/scalable/actions/xapp-favorite-symbolic.svg b/icons/hicolor/scalable/actions/xapp-favorite-symbolic.svg new file mode 100644 index 0000000..c052e5f --- /dev/null +++ b/icons/hicolor/scalable/actions/xapp-favorite-symbolic.svg @@ -0,0 +1,58 @@ + + + + + + + + + + image/svg+xml + + + + + + + diff --git a/icons/hicolor/scalable/actions/xapp-unfavorite-symbolic.svg b/icons/hicolor/scalable/actions/xapp-unfavorite-symbolic.svg new file mode 100644 index 0000000..84f1e3f --- /dev/null +++ b/icons/hicolor/scalable/actions/xapp-unfavorite-symbolic.svg @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + diff --git a/icons/hicolor/scalable/categories/xapp-favorites-symbolic.svg b/icons/hicolor/scalable/categories/xapp-favorites-symbolic.svg new file mode 100644 index 0000000..bc4418e --- /dev/null +++ b/icons/hicolor/scalable/categories/xapp-favorites-symbolic.svg @@ -0,0 +1,95 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + diff --git a/icons/hicolor/scalable/categories/xapp-favorites.svg b/icons/hicolor/scalable/categories/xapp-favorites.svg new file mode 100644 index 0000000..939a7e3 --- /dev/null +++ b/icons/hicolor/scalable/categories/xapp-favorites.svg @@ -0,0 +1,559 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/icons/hicolor/scalable/emblems/emblem-xapp-favorite.svg b/icons/hicolor/scalable/emblems/emblem-xapp-favorite.svg new file mode 100644 index 0000000..a1420c8 --- /dev/null +++ b/icons/hicolor/scalable/emblems/emblem-xapp-favorite.svg @@ -0,0 +1,193 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/libxapp/favorite-vfs-file-enumerator.c b/libxapp/favorite-vfs-file-enumerator.c new file mode 100644 index 0000000..0f7dc31 --- /dev/null +++ b/libxapp/favorite-vfs-file-enumerator.c @@ -0,0 +1,239 @@ +#include "xapp-favorites.h" +#include "favorite-vfs-file-enumerator.h" +#include "favorite-vfs-file.h" + +typedef struct +{ + GFile *file; + + GList *uris; + gchar *attributes; + GFileQueryInfoFlags flags; + + GList *current_pos; +} FavoriteVfsFileEnumeratorPrivate; + +struct _FavoriteVfsFileEnumerator +{ + GObject parent_instance; + FavoriteVfsFileEnumeratorPrivate *priv; +}; + +G_DEFINE_TYPE_WITH_PRIVATE(FavoriteVfsFileEnumerator, + favorite_vfs_file_enumerator, + G_TYPE_FILE_ENUMERATOR) + +static GFileInfo * +next_file (GFileEnumerator *enumerator, + GCancellable *cancellable, + GError **error) +{ + FavoriteVfsFileEnumerator *self = FAVORITE_VFS_FILE_ENUMERATOR (enumerator); + FavoriteVfsFileEnumeratorPrivate *priv = favorite_vfs_file_enumerator_get_instance_private (self); + GFileInfo *info; + + if (cancellable) + { + if (g_cancellable_is_cancelled (cancellable)) + { + *error = g_error_new (G_IO_ERROR, G_IO_ERROR_CANCELLED, "Enumerate canceled"); + return NULL; + } + } + + info = NULL; + + while (priv->current_pos != NULL && info == NULL) + { + GFile *file; + gchar *uri; + + uri = path_to_fav_uri ((const gchar *) priv->current_pos->data); + if (!xapp_favorites_find_by_display_name (xapp_favorites_get_default (), (gchar *) priv->current_pos->data)) + { + *error = g_error_new (G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "File not found"); + + g_warn_if_reached (); + } + else + { + file = g_file_new_for_uri (uri); + + info = g_file_query_info (file, + priv->attributes, + priv->flags, + cancellable, + error); + } + + g_free (uri); + g_object_unref (file); + } + + if (priv->current_pos) + { + priv->current_pos = priv->current_pos->next; + } + + return info; +} + +static void +next_async_op_free (GList *files) +{ + g_list_free_full (files, g_object_unref); +} + +static void +next_files_async_thread (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + FavoriteVfsFileEnumerator *self = FAVORITE_VFS_FILE_ENUMERATOR (source_object); + GList *ret; + GError *error = NULL; + gint i, n_requested; + + n_requested = GPOINTER_TO_INT (g_task_get_task_data (task)); + + ret = NULL; + + for (i = 0; i < n_requested; i++) + { + GFileInfo *info = NULL; + // g_clear_error (&error); + + if (g_cancellable_set_error_if_cancelled (cancellable, &error)) + { + g_task_return_error (task, error); + return; + } + else + { + info = next_file (G_FILE_ENUMERATOR (self), cancellable, &error); + } + + if (info != NULL) + { + ret = g_list_prepend (ret, info); + } + else + { + if (error) + { + g_critical ("ERROR: %s\n", error->message); + } + break; + } + } + + if (error) + { + g_task_return_error (task, error); + } + else + { + ret = g_list_reverse (ret); + g_task_return_pointer (task, ret, (GDestroyNotify) next_async_op_free); + } +} + +static void +next_files_async (GFileEnumerator *enumerator, + gint num_files, + gint io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + // FavoriteVfsFileEnumerator *self = FAVORITE_VFS_FILE_ENUMERATOR (enumerator); + + GTask *task; + task = g_task_new (enumerator, cancellable, callback, user_data); + g_task_set_priority (task, io_priority); + g_task_set_task_data (task, GINT_TO_POINTER (num_files), NULL); + + g_task_run_in_thread (task, next_files_async_thread); + g_object_unref (task); +} + +static GList * +next_files_finished (GFileEnumerator *enumerator, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (G_IS_FILE_ENUMERATOR (enumerator), NULL); + g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL); + + return (GList *) g_task_propagate_pointer (G_TASK (result), error); +} + +static gboolean +close_fn (GFileEnumerator *enumerator, + GCancellable *cancellable, + GError **error) +{ + // FavoriteVfsFileEnumerator *self = FAVORITE_VFS_FILE_ENUMERATOR (enumerator); + + return TRUE; +} + +static void +favorite_vfs_file_enumerator_init (FavoriteVfsFileEnumerator *self) +{ +} + +static void +favorite_vfs_file_enumerator_dispose (GObject *object) +{ + G_OBJECT_CLASS (favorite_vfs_file_enumerator_parent_class)->dispose (object); +} + +static void +favorite_vfs_file_enumerator_finalize (GObject *object) +{ + FavoriteVfsFileEnumerator *self = FAVORITE_VFS_FILE_ENUMERATOR(object); + FavoriteVfsFileEnumeratorPrivate *priv = favorite_vfs_file_enumerator_get_instance_private(self); + + g_list_free_full (priv->uris, (GDestroyNotify) g_free); + g_free (priv->attributes); + g_object_unref (priv->file); + + G_OBJECT_CLASS (favorite_vfs_file_enumerator_parent_class)->finalize (object); +} + +static void +favorite_vfs_file_enumerator_class_init (FavoriteVfsFileEnumeratorClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GFileEnumeratorClass *enumerator_class = G_FILE_ENUMERATOR_CLASS (klass); + + gobject_class->dispose = favorite_vfs_file_enumerator_dispose; + gobject_class->finalize = favorite_vfs_file_enumerator_finalize; + + enumerator_class->next_file = next_file; + enumerator_class->next_files_async = next_files_async; + enumerator_class->next_files_finish = next_files_finished; + enumerator_class->close_fn = close_fn; +} + +GFileEnumerator * +favorite_vfs_file_enumerator_new (GFile *file, + const gchar *attributes, + GFileQueryInfoFlags flags, + GList *uris) +{ + FavoriteVfsFileEnumerator *enumerator = g_object_new (FAVORITE_TYPE_VFS_FILE_ENUMERATOR, NULL); + FavoriteVfsFileEnumeratorPrivate *priv = favorite_vfs_file_enumerator_get_instance_private(enumerator); + + priv->uris = g_list_copy_deep (uris, (GCopyFunc) g_strdup, NULL); + priv->current_pos = priv->uris; + + priv->file = g_object_ref (file); + priv->attributes = g_strdup (attributes); + priv->flags = flags; + + return G_FILE_ENUMERATOR (enumerator); +} + diff --git a/libxapp/favorite-vfs-file-enumerator.h b/libxapp/favorite-vfs-file-enumerator.h new file mode 100644 index 0000000..4e9b8b6 --- /dev/null +++ b/libxapp/favorite-vfs-file-enumerator.h @@ -0,0 +1,23 @@ +#ifndef FAVORITE_VFS_FILE_ENUMERATOR_H +#define FAVORITE_VFS_FILE_ENUMERATOR_H + +#include +#include + +G_BEGIN_DECLS + +#define FAVORITE_TYPE_VFS_FILE_ENUMERATOR favorite_vfs_file_enumerator_get_type() + +G_DECLARE_FINAL_TYPE (FavoriteVfsFileEnumerator, favorite_vfs_file_enumerator, \ + FAVORITE, VFS_FILE_ENUMERATOR, \ + GFileEnumerator) + +GFileEnumerator * +favorite_vfs_file_enumerator_new (GFile *file, + const gchar *attributes, + GFileQueryInfoFlags flags, + GList *favorites); + +G_END_DECLS + +#endif // FAVORITE_VFS_FILE_ENUMERATOR_H diff --git a/libxapp/favorite-vfs-file-monitor.c b/libxapp/favorite-vfs-file-monitor.c new file mode 100644 index 0000000..58cf926 --- /dev/null +++ b/libxapp/favorite-vfs-file-monitor.c @@ -0,0 +1,452 @@ +#include "xapp-favorites.h" +#include "favorite-vfs-file.h" +#include "favorite-vfs-file-monitor.h" + +typedef struct +{ + gulong changed_handler_id; + GHashTable *file_monitors; + GList *infos; + + GVolumeMonitor *mount_mon; +} FavoriteVfsFileMonitorPrivate; + +struct _FavoriteVfsFileMonitor +{ + GObject parent_instance; + FavoriteVfsFileMonitorPrivate *priv; +}; + +G_DEFINE_TYPE_WITH_PRIVATE(FavoriteVfsFileMonitor, \ + favorite_vfs_file_monitor, \ + G_TYPE_FILE_MONITOR) + +GFile *_favorite_vfs_file_new_for_info (XAppFavoriteInfo *info); + +// static void +// rename_favorite (GFile *old_file, +// GFile *new_file) +// { +// gchar *old_file_uri, *new_file_uri; + +// old_file_uri = g_file_get_uri (old_file); +// new_file_uri = g_file_get_uri (new_file); + +// _xapp_favorites_rename (xapp_favorites_get_default (), +// old_file_uri, +// new_file_uri); + +// g_free (old_file_uri); +// g_free (new_file_uri); +// } + +static void +favorite_real_file_changed (GFileMonitor *rfmonitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event_type, + gpointer user_data) +{ + // Disabled + return; + +// g_return_if_fail (FAVORITE_IS_VFS_FILE_MONITOR (user_data)); +// FavoriteVfsFileMonitor *monitor = FAVORITE_VFS_FILE_MONITOR (user_data); +// FavoriteVfsFileMonitorPrivate *priv = favorite_vfs_file_monitor_get_instance_private (monitor); + +// g_debug ("real file changed: %s: %d", g_file_get_uri (file), event_type); + +// switch (event_type) +// { +// case G_FILE_MONITOR_EVENT_MOVED_OUT: +// break; +// { +// gchar *uri = g_file_get_uri (file); + +// g_debug ("Deleted: %s\n", uri); + +// xapp_favorites_remove (xapp_favorites_get_default (), uri); +// g_free (uri); +// } + +// break; +// case G_FILE_MONITOR_EVENT_RENAMED: +// break; +// { +// gchar *uri = g_file_get_uri (file); + +// g_debug ("Renamed: %s\n", uri); + +// rename_favorite (file, other_file); +// } +// break; +// case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED: +// case G_FILE_MONITOR_EVENT_CHANGED: +// { +// gchar *uri = g_file_get_uri (file); + +// GList *iter; + +// for (iter = priv->infos; iter != NULL; iter = iter->next) +// { +// XAppFavoriteInfo *info = (XAppFavoriteInfo *) iter->data; + +// if (g_strcmp0 (uri, info->uri) == 0) +// { +// GFile *fav_file; +// gchar *uri; + +// uri = path_to_fav_uri (info->display_name); +// fav_file = g_file_new_for_uri (uri); + +// g_debug ("Changed: %s", uri); +// g_free (uri); + +// g_file_monitor_emit_event (G_FILE_MONITOR (monitor), +// fav_file, +// NULL, +// G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED); +// g_file_monitor_emit_event (G_FILE_MONITOR (monitor), +// fav_file, +// NULL, +// G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT); +// g_object_unref (fav_file); + +// break; +// } +// } +// } +// break; +// case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT: +// case G_FILE_MONITOR_EVENT_CREATED: +// // case G_FILE_MONITOR_EVENT_CHANGED: +// case G_FILE_MONITOR_EVENT_PRE_UNMOUNT: +// case G_FILE_MONITOR_EVENT_UNMOUNTED: +// case G_FILE_MONITOR_EVENT_MOVED: +// case G_FILE_MONITOR_EVENT_MOVED_IN: +// case G_FILE_MONITOR_EVENT_DELETED: +// break; +// default: +// g_warn_if_reached (); +// } +} + +static void +unmonitor_files (FavoriteVfsFileMonitor *monitor) +{ + /* Disabled. See below */ + return; + + FavoriteVfsFileMonitorPrivate *priv = favorite_vfs_file_monitor_get_instance_private (monitor); + + if (priv->file_monitors != NULL) + { + g_hash_table_destroy (priv->file_monitors); + priv->file_monitors = NULL; + } +} + +static void +monitor_files (FavoriteVfsFileMonitor *monitor) +{ + + /* Disabled - this isn't necessary right now but could be expanded to help + * support less integrated apps. */ + return; + + FavoriteVfsFileMonitorPrivate *priv = favorite_vfs_file_monitor_get_instance_private (monitor); + GList *iter; + + priv->file_monitors = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, (GDestroyNotify) g_object_unref); + + for (iter = priv->infos; iter != NULL; iter = iter->next) + { + XAppFavoriteInfo *info = (XAppFavoriteInfo *) iter->data; + GFileMonitor *real_monitor; + GFile *real_file; + GError *error; + + g_debug ("Monitoring real file: %s\n", info->uri); + + error = NULL; + real_file = g_file_new_for_uri (info->uri); + real_monitor = g_file_monitor (real_file, + G_FILE_MONITOR_WATCH_MOVES, + NULL, + &error); + g_object_unref (real_file); + + if (real_monitor == NULL) + { + if (error != NULL) + { + g_warning ("Unable to add file monitor for '%s': %s", info->uri, error->message); + g_error_free (error); + } + + continue; + } + + g_hash_table_insert (priv->file_monitors, + (gpointer) g_strdup (info->uri), + (gpointer) real_monitor); + + g_signal_connect (real_monitor, + "changed", + G_CALLBACK (favorite_real_file_changed), + monitor); + } +} + +static gint +find_info_by_uri (gconstpointer ptr_a, + gconstpointer ptr_b) +{ + XAppFavoriteInfo *info = (XAppFavoriteInfo *) ptr_a; + const gchar *uri = (gchar *) ptr_b; + + return g_strcmp0 (info->uri, uri); +} + +static void +favorites_changed (XAppFavorites *favorites, + gpointer user_data) +{ + g_return_if_fail (XAPP_IS_FAVORITES (favorites)); + g_return_if_fail (FAVORITE_IS_VFS_FILE_MONITOR (user_data)); + + FavoriteVfsFileMonitor *monitor = FAVORITE_VFS_FILE_MONITOR (user_data); + FavoriteVfsFileMonitorPrivate *priv = favorite_vfs_file_monitor_get_instance_private (monitor); + GList *added, *removed; + GList *iter, *new_infos; + + if (g_file_monitor_is_cancelled (G_FILE_MONITOR (monitor))) + { + return; + } + + added = removed = NULL; + + new_infos = xapp_favorites_get_favorites (favorites, NULL); + + for (iter = priv->infos; iter != NULL; iter = iter->next) + { + XAppFavoriteInfo *old_info = (XAppFavoriteInfo *) iter->data; + GList *res = g_list_find_custom (new_infos, + (gpointer) old_info->uri, + (GCompareFunc) find_info_by_uri); + if (res == NULL) + { + removed = g_list_prepend (removed, old_info); + } + } + + for (iter = new_infos; iter != NULL; iter = iter->next) + { + XAppFavoriteInfo *new_info = (XAppFavoriteInfo *) iter->data; + GList *res = g_list_find_custom (priv->infos, + (gpointer) new_info->uri, + (GCompareFunc) find_info_by_uri); + if (res == NULL) + { + added = g_list_prepend (added, new_info); + } + } + + for (iter = added; iter != NULL; iter = iter->next) + { + XAppFavoriteInfo *added_info = (XAppFavoriteInfo *) iter->data; + + GFile *file = _favorite_vfs_file_new_for_info (added_info); + + g_file_monitor_emit_event (G_FILE_MONITOR (monitor), + file, + NULL, + G_FILE_MONITOR_EVENT_CREATED); + g_file_monitor_emit_event (G_FILE_MONITOR (monitor), + file, + NULL, + G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT); + g_object_unref (file); + } + + for (iter = removed; iter != NULL; iter = iter->next) + { + XAppFavoriteInfo *removed_info = (XAppFavoriteInfo *) iter->data; + + GFile *file = _favorite_vfs_file_new_for_info (removed_info); + + g_file_monitor_emit_event (G_FILE_MONITOR (monitor), + file, + NULL, + G_FILE_MONITOR_EVENT_DELETED); + g_file_monitor_emit_event (G_FILE_MONITOR (monitor), + file, + NULL, + G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT); + g_object_unref (file); + } + + GList *tmp = priv->infos; + priv->infos = new_infos; + + g_list_free_full (tmp, (GDestroyNotify) xapp_favorite_info_free); + + //FIXME: add/remove individually + unmonitor_files (monitor); + monitor_files (monitor); +} + +static void +mounts_changed (GVolumeMonitor *mount_mon, + GMount *mount, + gpointer user_data) +{ + g_return_if_fail (FAVORITE_IS_VFS_FILE_MONITOR (user_data)); + + FavoriteVfsFileMonitor *monitor = FAVORITE_VFS_FILE_MONITOR (user_data); + FavoriteVfsFileMonitorPrivate *priv = favorite_vfs_file_monitor_get_instance_private (monitor); + + GFile *root; + GList *iter, *mount_favorites; + + root = g_mount_get_root (mount); + mount_favorites = NULL; + + // Find any favorites that are descendent from root. + + for (iter = priv->infos; iter != NULL; iter = iter->next) + { + XAppFavoriteInfo *info = (XAppFavoriteInfo *) iter->data; + GFile *fav_file = g_file_new_for_uri (info->uri); + gchar *relpath; + + relpath = g_file_get_relative_path (root, fav_file); + + if (relpath != NULL) + { + mount_favorites = g_list_prepend (mount_favorites, info); + } + + g_free (relpath); + g_object_unref (fav_file); + } + + if (mount_favorites != NULL) + { + for (iter = mount_favorites; iter != NULL; iter = iter->next) { + XAppFavoriteInfo *info = (XAppFavoriteInfo *) iter->data; + GFile *fav_file; + gchar *uri; + + uri = path_to_fav_uri (info->display_name); + fav_file = g_file_new_for_uri (uri); + + g_file_monitor_emit_event (G_FILE_MONITOR (monitor), + fav_file, + NULL, + G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED); + g_file_monitor_emit_event (G_FILE_MONITOR (monitor), + fav_file, + NULL, + G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT); + + g_free (uri); + g_object_unref (fav_file); + } + + g_list_free (mount_favorites); + } + + g_object_unref (root); + + unmonitor_files (monitor); + monitor_files (monitor); +} + +static gboolean +favorite_vfs_file_monitor_cancel (GFileMonitor* gfilemon) +{ + FavoriteVfsFileMonitor *monitor = FAVORITE_VFS_FILE_MONITOR (gfilemon); + FavoriteVfsFileMonitorPrivate *priv = favorite_vfs_file_monitor_get_instance_private (monitor); + + if (priv->changed_handler_id > 0) + { + g_signal_handler_disconnect (xapp_favorites_get_default (), priv->changed_handler_id); + } + + return TRUE; +} + +static void +favorite_vfs_file_monitor_init (FavoriteVfsFileMonitor *monitor) +{ + FavoriteVfsFileMonitorPrivate *priv = favorite_vfs_file_monitor_get_instance_private (monitor); + + priv->mount_mon = g_volume_monitor_get (); + g_signal_connect (priv->mount_mon, + "mount-added", + G_CALLBACK (mounts_changed), + monitor); + g_signal_connect (priv->mount_mon, + "mount-removed", + G_CALLBACK (mounts_changed), + monitor); + + priv->infos = xapp_favorites_get_favorites (xapp_favorites_get_default (), NULL); + priv->changed_handler_id = g_signal_connect (xapp_favorites_get_default (), + "changed", + G_CALLBACK (favorites_changed), + monitor); + + monitor_files (monitor); +} + +static void +favorite_vfs_file_monitor_dispose (GObject *object) +{ + FavoriteVfsFileMonitor *monitor = FAVORITE_VFS_FILE_MONITOR(object); + FavoriteVfsFileMonitorPrivate *priv = favorite_vfs_file_monitor_get_instance_private(monitor); + + unmonitor_files (monitor); + + g_signal_handlers_disconnect_by_func (priv->mount_mon, mounts_changed, monitor); + g_clear_object (&priv->mount_mon); + + if (priv->infos != NULL) + { + g_list_free_full (priv->infos, (GDestroyNotify) xapp_favorite_info_free); + priv->infos = NULL; + } + + G_OBJECT_CLASS (favorite_vfs_file_monitor_parent_class)->dispose (object); +} + +static void +favorite_vfs_file_monitor_finalize (GObject *object) +{ + // FavoriteVfsFileMonitor *self = FAVORITE_VFS_FILE_MONITOR(object); + // FavoriteVfsFileMonitorPrivate *priv = favorite_vfs_file_monitor_get_instance_private(self); + + G_OBJECT_CLASS (favorite_vfs_file_monitor_parent_class)->finalize (object); +} + +static void +favorite_vfs_file_monitor_class_init (FavoriteVfsFileMonitorClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GFileMonitorClass *monitor_class = G_FILE_MONITOR_CLASS (klass); + + gobject_class->dispose = favorite_vfs_file_monitor_dispose; + gobject_class->finalize = favorite_vfs_file_monitor_finalize; + + monitor_class->cancel = favorite_vfs_file_monitor_cancel; +} + +GFileMonitor * +favorite_vfs_file_monitor_new (void) +{ + return G_FILE_MONITOR (g_object_new (FAVORITE_TYPE_VFS_FILE_MONITOR, NULL)); +} + diff --git a/libxapp/favorite-vfs-file-monitor.h b/libxapp/favorite-vfs-file-monitor.h new file mode 100644 index 0000000..e3ce69d --- /dev/null +++ b/libxapp/favorite-vfs-file-monitor.h @@ -0,0 +1,18 @@ +#ifndef __FAVORITE_VFS_FILE_MONITOR_H__ +#define __FAVORITE_VFS_FILE_MONITOR_H__ + +#include +#include + +G_BEGIN_DECLS + +#define FAVORITE_TYPE_VFS_FILE_MONITOR (favorite_vfs_file_monitor_get_type ()) + +G_DECLARE_FINAL_TYPE (FavoriteVfsFileMonitor, favorite_vfs_file_monitor, + FAVORITE, VFS_FILE_MONITOR, GFileMonitor) + +GFileMonitor *favorite_vfs_file_monitor_new (void); + +G_END_DECLS + +#endif /* __FAVORITE_VFS_FILE_MONITOR_H__ */ diff --git a/libxapp/favorite-vfs-file.c b/libxapp/favorite-vfs-file.c new file mode 100644 index 0000000..7a9504b --- /dev/null +++ b/libxapp/favorite-vfs-file.c @@ -0,0 +1,1501 @@ +#include +#include + +#include "favorite-vfs-file.h" +#include "favorite-vfs-file-enumerator.h" +#include "favorite-vfs-file-monitor.h" + +#define FAVORITES_SCHEMA "org.x.apps.favorites" +#define FAVORITE_DCONF_METADATA_KEY "root-metadata" + +static GSettings *settings = NULL; + +typedef struct +{ + gchar *uri; // favorites://foo + + XAppFavoriteInfo *info; +} FavoriteVfsFilePrivate; + +struct _FavoriteVfsFile +{ + GObject parent_instance; + FavoriteVfsFilePrivate *priv; +}; + +GList *_xapp_favorites_get_display_names (XAppFavorites *favorites); +static void favorite_vfs_file_gfile_iface_init (GFileIface *iface); + +gchar * +path_to_fav_uri (const gchar *path) +{ + g_return_val_if_fail (path != NULL, NULL); + + return g_strconcat (ROOT_URI, path, NULL); +} + +gchar * +fav_uri_to_display_name (const gchar *uri) +{ + g_return_val_if_fail (uri != NULL, NULL); + g_return_val_if_fail (g_str_has_prefix (uri, ROOT_URI), NULL); + + const gchar *ptr; + + ptr = uri + strlen (ROOT_URI); + + if (ptr[0] == '/') + { + ptr++; + } + + return g_strdup (ptr); +} + +G_DEFINE_TYPE_EXTENDED (FavoriteVfsFile, + favorite_vfs_file, + G_TYPE_OBJECT, + 0, + G_ADD_PRIVATE (FavoriteVfsFile) + G_IMPLEMENT_INTERFACE (G_TYPE_FILE, favorite_vfs_file_gfile_iface_init)) + +static gboolean +is_root_file (FavoriteVfsFile *file) +{ + FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (file); + + return g_strcmp0 (priv->uri, ROOT_URI) == 0; +} + +static GFile * +file_dup (GFile *file) +{ + FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file)); + + return favorite_vfs_file_new_for_uri (priv->uri); +} + +static guint +file_hash (GFile *file) +{ + FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file)); + + return g_str_hash (priv->uri); +} + +static gboolean +file_equal (GFile *file1, + GFile *file2) +{ + FavoriteVfsFilePrivate *priv1 = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file1)); + FavoriteVfsFilePrivate *priv2 = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file2)); + + return g_strcmp0 (priv1->uri, priv2->uri) == 0; +} + +static gboolean +file_is_native (GFile *file) +{ + FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file)); + + if (priv->info != NULL && priv->info->uri != NULL) + { + GFile *real_file; + gboolean is_really_native; + + real_file = g_file_new_for_uri (priv->info->uri); + is_really_native = g_file_is_native (real_file); + + g_object_unref (real_file); + return is_really_native; + } + + return FALSE; +} + +static gboolean +file_has_uri_scheme (GFile *file, + const gchar *uri_scheme) +{ + return g_strcmp0 (uri_scheme, URI_SCHEME) == 0; +} + +static gchar * +file_get_uri_scheme (GFile *file) +{ + return g_strdup (URI_SCHEME); +} + +static gchar * +file_get_basename (GFile *file) +{ + FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file)); + + if (priv->info == NULL) + { + return g_strdup ("/"); + } + + return g_strdup (priv->info->display_name); +} + +static gchar * +file_get_path (GFile *file) +{ + FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file)); + + if (file_is_native (file)) + { + GFile *real_file; + gchar *ret; + + real_file = g_file_new_for_uri (priv->info->uri); + + // file can't be native without an info, so we don't need to check for null here + ret = g_file_get_path (real_file); + g_object_unref (real_file); + + return ret; + } + + // GtkFileChooser checks for a path (and not null) before allowing a shortcut to + // be added using gtk_file_chooser_add_shortcut_folder_uri(). Even though this / doesn't + // make much sense for favorites:/// it allows it to be added to the file chooser without + // being forced to override its 'local-only' property. + if (is_root_file (FAVORITE_VFS_FILE (file))) + { + return g_strdup ("/"); + } + + return NULL; +} + +static gchar * +file_get_uri (GFile *file) +{ + FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file)); + + return g_strdup (priv->uri); +} + +static gchar * +file_get_parse_name (GFile *file) +{ + return file_get_uri (file); +} + +static GFile * +file_get_parent (GFile *file) +{ + FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file)); + + // We're only ever one level deep. + if (priv->info != NULL) + { + return g_file_new_for_uri (ROOT_URI); + } + + return NULL; +} + +static gboolean +file_prefix_matches (GFile *parent, + GFile *descendant) +{ + g_autofree gchar *puri = NULL; + g_autofree gchar *duri = NULL; + gchar *ptr = NULL; + + puri = g_file_get_uri (parent); + duri = g_file_get_uri (descendant); + + ptr = g_strstr_len (puri, -1, duri); + + if ((ptr == puri) && ptr[strlen (duri) + 1] == '/') + { + return TRUE; + } + + return FALSE; +} + +static gchar * +file_get_relative_path (GFile *parent, + GFile *descendant) +{ + g_autofree gchar *puri = NULL; + g_autofree gchar *duri = NULL; + g_autofree gchar *rpath = NULL; + gchar *ptr = NULL; + + puri = g_file_get_uri (parent); + duri = g_file_get_uri (descendant); + + ptr = g_strstr_len (puri, -1, duri); + + if ((ptr == puri) && ptr[strlen (duri) + 1] == '/') + { + rpath = g_strdup (puri + strlen (duri) + 1); + + return rpath; + } + + return NULL; +} + +static GFile * +file_resolve_relative_path (GFile *file, + const gchar *relative_path) +{ + FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file)); + GFile *relative_file; + gchar *uri; + + if (g_path_is_absolute (relative_path)) + { + return g_file_new_for_path (relative_path); + } + + if (priv->info != NULL && priv->info->uri != NULL) + { + GFile *real_file; + real_file = g_file_new_for_uri (priv->info->uri); + + relative_file = g_file_resolve_relative_path (real_file, + relative_path); + + g_object_unref (real_file); + return relative_file; + } + + if (g_strcmp0 (relative_path, ".") == 0) + { + relative_file = file_dup (file); + + return relative_file; + } + + uri = path_to_fav_uri (relative_path); + + relative_file = g_file_new_for_uri (uri); + g_free (uri); + + return relative_file; +} + +static GFile * +file_get_child_for_display_name (GFile *file, + const char *display_name, + GError **error) +{ + return g_file_get_child (file, display_name); +} + +static GFile * +file_set_display_name (GFile *file, + const gchar *display_name, + GCancellable *cancellable, + GError **error) +{ + FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file)); + + if (priv->info != NULL && priv->info->uri != NULL) + { + GFile *real_file; + GFile *ret; + + real_file = g_file_new_for_uri (priv->info->uri); + ret = g_file_set_display_name (real_file, + display_name, + cancellable, + error); + g_object_unref (real_file); + + return ret; + } + + g_set_error_literal (error, G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + "Can't rename file"); + + return NULL; +} + +static GFileEnumerator * +file_enumerate_children(GFile *file, + const char *attributes, + GFileQueryInfoFlags flags, + GCancellable *cancellable, + GError **error) +{ + FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file)); + GFileEnumerator *enumerator; + + if (priv->info != NULL && priv->info->uri != NULL) + { + GFile *real_file; + real_file = g_file_new_for_uri (priv->info->uri); + + enumerator = g_file_enumerate_children (real_file, + attributes, + flags, + cancellable, + error); + + g_object_unref (real_file); + return enumerator; + } + + GList *uris; + + uris = _xapp_favorites_get_display_names (xapp_favorites_get_default ()); + enumerator = favorite_vfs_file_enumerator_new (file, + attributes, + flags, + uris); + + g_list_free (uris); + + return enumerator; +} + +static GFileInfo * +file_query_info (GFile *file, + const char *attributes, + GFileQueryInfoFlags flags, + GCancellable *cancellable, + GError **error) +{ + FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file)); + GFileInfo *info; + GIcon *icon; + + if (priv->info != NULL) + { + if (!priv->info->uri) + { + if (error != NULL) + { + *error = g_error_new (G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "File not found"); + } + return NULL; + } + + GFile *real_file = g_file_new_for_uri (priv->info->uri); + + info = g_file_query_info (real_file, attributes, flags, cancellable, error); + + if (info != NULL) + { + gchar *local_path; + + g_file_info_set_display_name (info, priv->info->display_name); + g_file_info_set_name (info, priv->info->display_name); + g_file_info_set_is_symlink (info, TRUE); + + local_path = g_file_get_path (real_file); + + if (local_path != NULL) + { + g_file_info_set_symlink_target (info, local_path); + g_free (local_path); + } + else + { + g_file_info_set_symlink_target (info, priv->info->uri); + } + + // Recent sets this also. If it's set, this uri is used to display the "location" + // for the file (the directory in which real file resides). + g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI, priv->info->uri); + + g_file_info_set_attribute_string (info, FAVORITE_AVAILABLE_METADATA_KEY, META_TRUE); + } + else + { + g_clear_error (error); + gchar *content_type; + + info = g_file_info_new (); + + g_file_info_set_display_name (info, priv->info->display_name); + g_file_info_set_name (info, priv->info->display_name); + g_file_info_set_is_symlink (info, TRUE); + + g_file_info_set_symlink_target (info, priv->info->uri); + + /* Prevent showing a 'thumbnailing' icon */ + g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_THUMBNAILING_FAILED, TRUE); + + /* This will keep the sort position the same for missing or unmounted files */ + g_file_info_set_attribute_string (info, FAVORITE_METADATA_KEY, META_TRUE); + g_file_info_set_attribute_string (info, FAVORITE_AVAILABLE_METADATA_KEY, META_FALSE); + g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI, priv->info->uri); + + content_type = g_content_type_from_mime_type (priv->info->cached_mimetype); + + icon = g_content_type_get_icon (content_type); + g_file_info_set_icon (info, icon); + g_object_unref (icon); + + icon = g_content_type_get_symbolic_icon (content_type); + g_file_info_set_symbolic_icon (info, icon); + g_object_unref (icon); + + g_free (content_type); + } + + g_object_unref (real_file); + + return info; + } + + if (is_root_file (FAVORITE_VFS_FILE (file))) + { + GFileAttributeMatcher *matcher = g_file_attribute_matcher_new (attributes); + + info = g_file_info_new (); + + if (g_file_attribute_matcher_matches (matcher, G_FILE_ATTRIBUTE_STANDARD_NAME)) + g_file_info_set_name (info, "/"); + + if (g_file_attribute_matcher_matches (matcher, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME)) + g_file_info_set_display_name (info, _("Favorites")); + + if (g_file_attribute_matcher_matches (matcher, G_FILE_ATTRIBUTE_STANDARD_TYPE)) + g_file_info_set_file_type (info, G_FILE_TYPE_DIRECTORY); + + if (g_file_attribute_matcher_matches (matcher, G_FILE_ATTRIBUTE_STANDARD_ICON)) + { + icon = g_themed_icon_new ("xapp-favorites"); + g_file_info_set_icon (info, icon); + + g_object_unref (icon); + } + + if (g_file_attribute_matcher_matches (matcher, G_FILE_ATTRIBUTE_STANDARD_SYMBOLIC_ICON)) + { + icon = g_themed_icon_new ("xapp-favorites-symbolic"); + g_file_info_set_symbolic_icon (info, icon); + g_object_unref (icon); + } + + if (g_file_attribute_matcher_matches (matcher, G_FILE_ATTRIBUTE_GVFS_BACKEND)) + g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_GVFS_BACKEND, "favorites"); + + if (g_file_attribute_matcher_matches (matcher, FAVORITE_AVAILABLE_METADATA_KEY)) + g_file_info_set_attribute_string (info, FAVORITE_AVAILABLE_METADATA_KEY, META_TRUE); + + if (g_file_attribute_matcher_enumerate_namespace (matcher, "metadata")) + { + gchar **entries = g_settings_get_strv (settings, FAVORITE_DCONF_METADATA_KEY); + + if (entries != NULL) + { + gint i; + + for (i = 0; entries[i] != NULL; i++) + { + gchar **t_n_v; + + t_n_v = g_strsplit (entries[i], "==", 3); + + if (g_strv_length (t_n_v) == 3) + { + if (g_strcmp0 (t_n_v[0], "string") == 0) + { + g_file_info_set_attribute_string (info, t_n_v[1], t_n_v[2]); + } + else + if (g_strcmp0 (t_n_v[0], "strv") == 0) + { + gchar **members = g_strsplit (t_n_v[2], "|", -1); + + g_file_info_set_attribute_stringv (info, t_n_v[1], members); + + g_strfreev (members); + } + } + + g_strfreev (t_n_v); + } + } + + g_strfreev (entries); + } + + g_file_attribute_matcher_unref (matcher); + } + + return info; +} + +GFileInfo * +file_query_filesystem_info (GFile *file, + const char *attributes, + GCancellable *cancellable, + GError **error) +{ + FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file)); + + if (priv->info != NULL && priv->info->uri != NULL) + { + GFileInfo *info; + GFile *real_file; + real_file = g_file_new_for_uri (priv->info->uri); + + info = g_file_query_filesystem_info (real_file, + attributes, + cancellable, + error); + + g_object_unref (real_file); + return info; + } + + GFileInfo *info; + GFileAttributeMatcher *matcher; + + info = g_file_info_new (); + + matcher = g_file_attribute_matcher_new (attributes); + + if (g_file_attribute_matcher_matches (matcher, G_FILE_ATTRIBUTE_FILESYSTEM_TYPE)) + { + g_file_info_set_attribute_string (info, + G_FILE_ATTRIBUTE_FILESYSTEM_TYPE, "favorites"); + } + + if (g_file_attribute_matcher_matches (matcher, G_FILE_ATTRIBUTE_FILESYSTEM_READONLY)) + { + g_file_info_set_attribute_boolean (info, + G_FILE_ATTRIBUTE_FILESYSTEM_READONLY, TRUE); + } + + g_file_attribute_matcher_unref (matcher); + + return info; +} + +GMount * +file_find_enclosing_mount (GFile *file, + GCancellable *cancellable, + GError **error) +{ + FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file)); + + if (priv->info != NULL && priv->info->uri != NULL) + { + GMount *mount; + GFile *real_file; + real_file = g_file_new_for_uri (priv->info->uri); + + mount = g_file_find_enclosing_mount (real_file, + cancellable, + error); + + g_object_unref (real_file); + return mount; + } + + g_set_error_literal (error, G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + "Can't find favorite file enclosing mount"); + + return NULL; +} + +GFileAttributeInfoList * +file_query_settable_attributes (GFile *file, + GCancellable *cancellable, + GError **error) +{ + FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file)); + + if (priv->info != NULL && priv->info->uri != NULL) + { + GFileAttributeInfoList *list; + GFile *real_file; + real_file = g_file_new_for_uri (priv->info->uri); + + list = g_file_query_settable_attributes (real_file, + cancellable, + error); + + g_object_unref (real_file); + return list; + } + + return g_file_attribute_info_list_new (); +} + +GFileAttributeInfoList * +file_query_writable_namespaces (GFile *file, + GCancellable *cancellable, + GError **error) +{ + FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file)); + GFileAttributeInfoList *list; + + if (priv->info != NULL && priv->info->uri != NULL) + { + GFile *real_file; + real_file = g_file_new_for_uri (priv->info->uri); + + list = g_file_query_writable_namespaces (real_file, + cancellable, + error); + + g_object_unref (real_file); + return list; + } + + list = g_file_attribute_info_list_new (); + + g_file_attribute_info_list_add (list, + "metadata", + G_FILE_ATTRIBUTE_TYPE_STRING, + G_FILE_ATTRIBUTE_INFO_NONE); + + return list; +} + +static void +remove_root_metadata (const gchar *attr_name) +{ + GPtrArray *new_array; + gchar **old_metadata, **new_metadata; + gint i; + + old_metadata = g_settings_get_strv (settings, FAVORITE_DCONF_METADATA_KEY); + + if (old_metadata == NULL) + { + return; + } + + new_array = g_ptr_array_new (); + + for (i = 0; old_metadata[i] != NULL; i++) + { + gchar **t_n_v; + + t_n_v = g_strsplit (old_metadata[i], "==", 3); + + if (g_strcmp0 (t_n_v[1], attr_name) != 0) + { + g_ptr_array_add (new_array, g_strdup (old_metadata[i])); + } + + g_strfreev (t_n_v); + } + + g_ptr_array_add (new_array, NULL); + g_strfreev (old_metadata); + + new_metadata = (gchar **) g_ptr_array_free (new_array, FALSE); + + g_settings_set_strv (settings, FAVORITE_DCONF_METADATA_KEY, (const gchar * const *) new_metadata); + g_strfreev (new_metadata); +} + +static void +set_or_update_root_metadata (const gchar *attr_name, + const gpointer value, + GFileAttributeType type) +{ + GPtrArray *new_array; + gchar **old_metadata, **new_metadata; + gint i; + gchar *entry; + gboolean exists; + + old_metadata = g_settings_get_strv (settings, FAVORITE_DCONF_METADATA_KEY); + + if (old_metadata == NULL) + { + return; + } + + switch (type) + { + case G_FILE_ATTRIBUTE_TYPE_STRING: + { + entry = g_strdup_printf ("string==%s==%s", attr_name, (gchar *) value); + break; + } + case G_FILE_ATTRIBUTE_TYPE_STRINGV: + { + gchar *val_strv = g_strjoinv ("|", (gchar **) value); + entry = g_strdup_printf ("strv==%s==%s", attr_name, val_strv); + g_free (val_strv); + break; + } + default: + break; + } + + exists = FALSE; + new_array = g_ptr_array_new (); + + for (i = 0; old_metadata[i] != NULL; i++) + { + gchar **t_n_v; + + t_n_v = g_strsplit (old_metadata[i], "==", 3); + + if (g_strcmp0 (t_n_v[1], attr_name) == 0) + { + g_ptr_array_add (new_array, entry); + exists = TRUE; + } + else + { + g_ptr_array_add (new_array, g_strdup (old_metadata[i])); + } + + g_strfreev (t_n_v); + } + + if (!exists) + { + g_ptr_array_add (new_array, entry); + } + + g_ptr_array_add (new_array, NULL); + g_strfreev (old_metadata); + + new_metadata = (gchar **) g_ptr_array_free (new_array, FALSE); + + g_settings_set_strv (settings, FAVORITE_DCONF_METADATA_KEY, (const gchar * const *) new_metadata); + g_strfreev (new_metadata); +} + +gboolean +file_set_attribute (GFile *file, + const gchar *attribute, + GFileAttributeType type, + gpointer value_p, + GFileQueryInfoFlags flags, + GCancellable *cancellable, + GError **error) +{ + FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file)); + gboolean ret; + + if (priv->info != NULL && priv->info->uri != NULL) + { + GFile *real_file; + gboolean ret; + real_file = g_file_new_for_uri (priv->info->uri); + + ret = g_file_set_attribute (real_file, + attribute, + type, + value_p, + flags, + cancellable, + error); + + g_object_unref (real_file); + return ret; + } + + ret = FALSE; + + if (!is_root_file (FAVORITE_VFS_FILE (file))) + { + g_set_error (error, G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + "Can't set attributes for %s - only the root (favorites:///) is supported.", priv->uri); + } + else + { + if (g_str_has_prefix (attribute, "metadata")) + { + if (type == G_FILE_ATTRIBUTE_TYPE_INVALID || value_p == NULL || ((char *) value_p)[0] == '\0') + { + // unset metadata + remove_root_metadata (attribute); + ret = TRUE; + } + else + { + if (type == G_FILE_ATTRIBUTE_TYPE_STRING || type == G_FILE_ATTRIBUTE_TYPE_STRINGV) + { + set_or_update_root_metadata (attribute, (gchar *) value_p, type); + ret = TRUE; + } + else + { + g_set_error (error, G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + "Can't set attribute '%s' for favorites:/// file " + "(only string-type metadata are allowed).", attribute); + } + } + } + else + { + g_set_error (error, G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + "Can't set attribute '%s' for favorites:/// file " + "(only 'metadata' namespace is allowed).", attribute); + } + } + + return ret; +} + +static gboolean +file_set_attributes_from_info (GFile *file, + GFileInfo *info, + GFileQueryInfoFlags flags, + GCancellable *cancellable, + GError **error) +{ + FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file)); + gboolean res; + + if (priv->info != NULL && priv->info->uri != NULL) + { + GFile *real_file; + gboolean ret = FALSE; + real_file = g_file_new_for_uri (priv->info->uri); + + ret = g_file_set_attributes_from_info (real_file, info, flags, cancellable, error); + + g_object_unref (real_file); + return ret; + } + + res = TRUE; + + if (g_file_info_has_namespace (info, "metadata")) + { + GFileAttributeType type; + gchar **attributes; + gpointer value_p; + gint i; + + attributes = g_file_info_list_attributes (info, "metadata"); + + for (i = 0; attributes[i] != NULL; i++) + { + if (g_file_info_get_attribute_data (info, attributes[i], &type, &value_p, NULL)) + { + if (!file_set_attribute (file, + attributes[i], + type, + value_p, + flags, + cancellable, + error)) + { + g_file_info_set_attribute_status (info, attributes[i], G_FILE_ATTRIBUTE_STATUS_ERROR_SETTING); + error = NULL; // from gvfs gdaemonvfs.c - ignore subsequent errors iterating thru attribute list. + res = FALSE; + } + else + { + g_file_info_set_attribute_status (info, attributes[i], G_FILE_ATTRIBUTE_STATUS_SET); + } + } + } + + g_strfreev (attributes); + } + + return res; +} + +static GFileInputStream * +file_read_fn (GFile *file, + GCancellable *cancellable, + GError **error) +{ + FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file)); + + if (priv->info != NULL && priv->info->uri != NULL) + { + GFile *real_file = g_file_new_for_uri (priv->info->uri); + + GFileInputStream *stream; + + stream = g_file_read (real_file, cancellable, error); + + g_object_unref (real_file); + return stream; + } + + g_set_error_literal (error, G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + _("Operation not supported")); + + return NULL; +} + +GFileOutputStream * +file_append_to (GFile *file, + GFileCreateFlags flags, + GCancellable *cancellable, + GError **error) +{ + FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file)); + + if (priv->info != NULL && priv->info->uri != NULL) + { + GFile *real_file = g_file_new_for_uri (priv->info->uri); + + GFileOutputStream *stream; + + stream = g_file_append_to (real_file, + flags, + cancellable, + error); + + g_object_unref (real_file); + return stream; + } + + g_set_error_literal (error, G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + _("Operation not supported")); + return NULL; +} + +GFileOutputStream * +file_create (GFile *file, + GFileCreateFlags flags, + GCancellable *cancellable, + GError **error) +{ + FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file)); + + if (priv->info != NULL && priv->info->uri != NULL) + { + GFile *real_file = g_file_new_for_uri (priv->info->uri); + + GFileOutputStream *stream; + + stream = g_file_create (real_file, + flags, + cancellable, + error); + + g_object_unref (real_file); + return stream; + } + + g_set_error_literal (error, G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + _("Operation not supported")); + + return NULL; +} + +static GFileOutputStream * +file_replace (GFile *file, + const char *etag, + gboolean make_backup, + GFileCreateFlags flags, + GCancellable *cancellable, + GError **error) +{ + FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file)); + + if (priv->info != NULL && priv->info->uri != NULL) + { + GFile *real_file = g_file_new_for_uri (priv->info->uri); + + GFileOutputStream *stream; + + stream = g_file_replace (real_file, + etag, + make_backup, + flags, + cancellable, + error); + + g_object_unref (real_file); + return stream; + } + + g_set_error_literal (error, G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + _("Operation not supported")); + + return NULL; +} + +static GFileIOStream * +file_open_readwrite (GFile *file, + GCancellable *cancellable, + GError **error) +{ + FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file)); + + if (priv->info != NULL && priv->info->uri != NULL) + { + GFileIOStream *res; + GFile *real_file = g_file_new_for_uri (priv->info->uri); + + res = g_file_open_readwrite (real_file, + cancellable, + error); + + g_object_unref (real_file); + return res; + } + + g_set_error_literal (error, G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + _("Operation not supported")); + + return NULL; +} + +static GFileIOStream * +file_create_readwrite (GFile *file, + GFileCreateFlags flags, + GCancellable *cancellable, + GError **error) +{ + FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file)); + + if (priv->info != NULL && priv->info->uri != NULL) + { + GFileIOStream *res; + GFile *real_file = g_file_new_for_uri (priv->info->uri); + + res = g_file_create_readwrite (real_file, + flags, + cancellable, + error); + + g_object_unref (real_file); + return res; + } + + g_set_error_literal (error, G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + _("Operation not supported")); + + return NULL; +} + +static GFileIOStream * +file_replace_readwrite (GFile *file, + const char *etag, + gboolean make_backup, + GFileCreateFlags flags, + GCancellable *cancellable, + GError **error) +{ + FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file)); + + if (priv->info != NULL && priv->info->uri != NULL) + { + GFileIOStream *res; + GFile *real_file = g_file_new_for_uri (priv->info->uri); + + res = g_file_replace_readwrite (real_file, + etag, + make_backup, + flags, + cancellable, + error); + + g_object_unref (real_file); + return res; + } + + g_set_error_literal (error, G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + _("Operation not supported")); + + return NULL; +} + +static gboolean +file_delete (GFile *file, + GCancellable *cancellable, + GError **error) +{ + FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file)); + + if (priv->info != NULL && priv->info->uri != NULL) + { + gboolean res; + GFile *real_file = g_file_new_for_uri (priv->info->uri); + + res = g_file_delete (real_file, + cancellable, + error); + + g_object_unref (real_file); + return res; + } + + g_set_error_literal (error, G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + _("Operation not supported")); + + return FALSE; +} + +static gboolean +file_trash (GFile *file, + GCancellable *cancellable, + GError **error) +{ + FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file)); + + if (priv->info != NULL && priv->info->uri != NULL) + { + gboolean res; + GFile *real_file = g_file_new_for_uri (priv->info->uri); + + res = g_file_trash (real_file, + cancellable, + error); + g_object_unref (real_file); + + return res; + } + + g_set_error_literal (error, G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + "Not supported"); + + return FALSE; +} + +gboolean +file_move (GFile *source, + GFile *destination, + GFileCopyFlags flags, + GCancellable *cancellable, + GFileProgressCallback progress_callback, + gpointer progress_callback_data, + GError **error) +{ + FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (source)); + + if (priv->info != NULL && priv->info->uri != NULL) + { + gboolean res; + GFile *real_file = g_file_new_for_uri (priv->info->uri); + + res = g_file_move (real_file, + destination, + flags, + cancellable, + progress_callback, + progress_callback_data, + error); + + g_object_unref (real_file); + return res; + } + + g_set_error_literal (error, G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + _("Operation not supported")); + + return FALSE; +} + +static GFileMonitor * +file_monitor_dir (GFile *file, + GFileMonitorFlags flags, + GCancellable *cancellable, + GError **error) +{ + FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file)); + + if (priv->info != NULL && priv->info->uri != NULL) + { + GFile *real_file; + GFileMonitor *monitor; + + real_file = g_file_new_for_uri (priv->info->uri); + monitor = g_file_monitor_directory (real_file, + flags, + cancellable, + error); + g_object_unref (real_file); + + return monitor; + } + else + if (is_root_file (FAVORITE_VFS_FILE (file))) + { + return favorite_vfs_file_monitor_new (); + } + + g_set_error_literal (error, G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + _("Operation not supported")); + + return NULL; +} + +static GFileMonitor * +file_monitor_file (GFile *file, + GFileMonitorFlags flags, + GCancellable *cancellable, + GError **error) +{ + FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file)); + + if (priv->info != NULL && priv->info->uri != NULL) + { + GFile *real_file; + GFileMonitor *monitor; + + real_file = g_file_new_for_uri (priv->info->uri); + monitor = g_file_monitor_file (real_file, + flags, + cancellable, + error); + g_object_unref (real_file); + + return monitor; + } + + g_set_error_literal (error, G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + _("Operation not supported")); + + return NULL; +} + +gboolean +file_measure_disk_usage (GFile *file, + GFileMeasureFlags flags, + GCancellable *cancellable, + GFileMeasureProgressCallback progress_callback, + gpointer progress_data, + guint64 *disk_usage, + guint64 *num_dirs, + guint64 *num_files, + GError **error) +{ + FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file)); + + if (priv->info != NULL && priv->info->uri != NULL) + { + gboolean res; + GFile *real_file = g_file_new_for_uri (priv->info->uri); + + res = g_file_measure_disk_usage (real_file, + flags, + cancellable, + progress_callback, + progress_data, + disk_usage, + num_dirs, + num_files, + error); + + g_object_unref (real_file); + return res; + } + + g_set_error_literal (error, G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + _("Operation not supported")); + + return FALSE; +} + +static void favorite_vfs_file_gfile_iface_init (GFileIface *iface) +{ + iface->dup = file_dup; + iface->hash = file_hash; + iface->equal = file_equal; + iface->is_native = file_is_native; + iface->has_uri_scheme = file_has_uri_scheme; + iface->get_uri_scheme = file_get_uri_scheme; + iface->get_basename = file_get_basename; + iface->get_path = file_get_path; + iface->get_uri = file_get_uri; + iface->get_parse_name = file_get_parse_name; + iface->get_parent = file_get_parent; + iface->prefix_matches = file_prefix_matches; + iface->get_relative_path = file_get_relative_path; + iface->resolve_relative_path = file_resolve_relative_path; + iface->get_child_for_display_name = file_get_child_for_display_name; + iface->set_display_name = file_set_display_name; + iface->enumerate_children = file_enumerate_children; + iface->query_info = file_query_info; + iface->query_filesystem_info = file_query_filesystem_info; + iface->find_enclosing_mount = file_find_enclosing_mount; + iface->query_settable_attributes = file_query_settable_attributes; + iface->query_writable_namespaces = file_query_writable_namespaces; + iface->set_attribute = file_set_attribute; + iface->set_attributes_from_info = file_set_attributes_from_info; + iface->read_fn = file_read_fn; + iface->append_to = file_append_to; + iface->create = file_create; + iface->replace = file_replace; + iface->open_readwrite = file_open_readwrite; + iface->create_readwrite = file_create_readwrite; + iface->replace_readwrite = file_replace_readwrite; + iface->delete_file = file_delete; + iface->trash = file_trash; + // iface->make_directory = file_make_directory; ### Don't support + // iface->make_symbolic_link = file_make_symbolic_link; ### Don't support + iface->move = file_move; + iface->monitor_dir = file_monitor_dir; + iface->monitor_file = file_monitor_file; + iface->measure_disk_usage = file_measure_disk_usage; + iface->supports_thread_contexts = TRUE; +} + +static void favorite_vfs_file_dispose (GObject *object) +{ + FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (object)); + + if (priv->info != NULL) + { + xapp_favorite_info_free (priv->info); + priv->info = NULL; + } + + g_clear_pointer (&priv->uri, g_free); + + G_OBJECT_CLASS (favorite_vfs_file_parent_class)->dispose (object); +} + +static void +ensure_metadata_store (FavoriteVfsFile *file) +{ + if (is_root_file (file)) + { + if (settings == NULL) + { + settings = g_settings_new (FAVORITES_SCHEMA); + g_object_add_weak_pointer (G_OBJECT (settings), (gpointer) &settings); + } + else + { + g_object_ref (settings); + } + } +} + +static void favorite_vfs_file_class_init (FavoriteVfsFileClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->dispose = favorite_vfs_file_dispose; +} + +static void favorite_vfs_file_init (FavoriteVfsFile *self) +{ +} + +GFile *_favorite_vfs_file_new_for_info (XAppFavoriteInfo *info) +{ + FavoriteVfsFile *new_file; + + new_file = g_object_new (FAVORITE_TYPE_VFS_FILE, NULL); + + FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (new_file)); + + priv->uri = path_to_fav_uri (info->display_name); + priv->info = xapp_favorite_info_copy (info); + ensure_metadata_store (new_file); + + return G_FILE (new_file); +} + +gchar *favorite_vfs_file_get_real_uri (GFile *file) +{ + FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (file)); + + if (priv->info != NULL && priv->info->uri != NULL) + { + return g_strdup (priv->info->uri); + } + + return NULL; +} + +GFile *favorite_vfs_file_new_for_uri (const char *uri) +{ + FavoriteVfsFile *new_file; + g_autofree gchar *basename = NULL; + + new_file = g_object_new (FAVORITE_TYPE_VFS_FILE, NULL); + + g_debug ("FavoriteVfsFile new for uri: %s", uri); + + FavoriteVfsFilePrivate *priv = favorite_vfs_file_get_instance_private (FAVORITE_VFS_FILE (new_file)); + + priv->uri = g_strdup (uri); + ensure_metadata_store (new_file); + + if (g_strcmp0 (uri, ROOT_URI) == 0) + { + priv->info = NULL; + } + else + { + gchar *display_name; + + display_name = fav_uri_to_display_name (uri); + XAppFavoriteInfo *info = xapp_favorites_find_by_display_name (xapp_favorites_get_default (), + display_name); + + if (info != NULL) + { + priv->info = xapp_favorite_info_copy (info); + } + else + { + info = g_slice_new0 (XAppFavoriteInfo); + info->uri = g_strdup (NULL); + info->display_name = g_strdup (display_name); + info->cached_mimetype = NULL; + + priv->info = info; + } + + g_free (display_name); + } + + return G_FILE (new_file); +} + +GFile *favorite_vfs_file_new (void) +{ + return favorite_vfs_file_new_for_uri (ROOT_URI); +} + +static GFile * +favorite_vfs_lookup (GVfs *vfs, + const char *identifier, + gpointer user_data) +{ + if (g_str_has_prefix (identifier, ROOT_URI)) + { + return favorite_vfs_file_new_for_uri (identifier); + } + + return NULL; +} + +void +init_favorite_vfs (void) +{ + GVfs *vfs; + vfs = g_vfs_get_default (); + + g_vfs_register_uri_scheme (vfs, "favorites", + favorite_vfs_lookup, NULL, NULL, + favorite_vfs_lookup, NULL, NULL); +} + diff --git a/libxapp/favorite-vfs-file.h b/libxapp/favorite-vfs-file.h new file mode 100644 index 0000000..bf0ecf6 --- /dev/null +++ b/libxapp/favorite-vfs-file.h @@ -0,0 +1,35 @@ +#ifndef FAVORITE_VFS_FILE_H +#define FAVORITE_VFS_FILE_H + +#include "xapp-favorites.h" +#include +#include + +G_BEGIN_DECLS + +#define FAVORITE_TYPE_VFS_FILE (favorite_vfs_file_get_type ()) + +G_DECLARE_FINAL_TYPE (FavoriteVfsFile, favorite_vfs_file, \ + FAVORITE, VFS_FILE, GObject) + +// Initializer for favorites:/// - called when the XAppFavorites singleton is created +void init_favorite_vfs (void); + +GFile *favorite_vfs_file_new_for_uri (const char *uri); +gchar *favorite_vfs_file_get_real_uri (GFile *file); + +#define URI_SCHEME "favorites" +#define ROOT_URI ("favorites:///") + +#define FAVORITE_METADATA_KEY "metadata::xapp-favorite" +#define FAVORITE_AVAILABLE_METADATA_KEY "metadata::xapp-favorite-available" + +#define META_TRUE "true" +#define META_FALSE "false" + +gchar *path_to_fav_uri (const gchar *path); +gchar *fav_uri_to_display_name (const gchar *uri); + +G_END_DECLS + +#endif // FAVORITE_VFS_FILE_H diff --git a/libxapp/g-codegen.py b/libxapp/g-codegen.py deleted file mode 100755 index bf2ceb6..0000000 --- a/libxapp/g-codegen.py +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env python3 - -''' -FIXME - -This script is used only to call gdbus-codegen and simulate the -generation of the source code and header as different targets. - -Both are generated implicitly, so meson is not able to know how -many files are generated, so it does generate only one opaque -target that represents the two files. - -originally from: -https://gitlab.gnome.org/GNOME/gnome-settings-daemon/commit/5924d72931a030b24554116a48140a661a99652b - -Please see: - https://bugzilla.gnome.org/show_bug.cgi?id=791015 - https://github.com/mesonbuild/meson/pull/2930 -''' - -import subprocess -import sys -import os - -subprocess.call([ - 'gdbus-codegen', - '--interface-prefix=' + sys.argv[1], - '--generate-c-code=' + os.path.join(sys.argv[4], sys.argv[2]), - '--c-namespace=XApp', - '--annotate', sys.argv[1], 'org.gtk.GDBus.C.Name', sys.argv[3], - sys.argv[5] -]) diff --git a/libxapp/meson.build b/libxapp/meson.build index d6ce7c9..7c7013e 100644 --- a/libxapp/meson.build +++ b/libxapp/meson.build @@ -1,14 +1,25 @@ glib_min_ver = '>=2.37.3' +gio_dep = dependency('gio-2.0', version: glib_min_ver, required: true) +glib_dep = dependency('glib-2.0', version: glib_min_ver, required: true) +gtk3_dep = dependency('gtk+-3.0', version: '>=3.3.16', required: true) + libdeps = [] -libdeps += dependency('gio-2.0', version: glib_min_ver, required: true) -libdeps += dependency('glib-2.0', version: glib_min_ver, required: true) -libdeps += dependency('gtk+-3.0', version: '>=3.3.16', required: true) +libdeps += gio_dep +libdeps += glib_dep +libdeps += gtk3_dep libdeps += dependency('gdk-pixbuf-2.0', version: '>=2.22.0', required: true) libdeps += dependency('cairo', required: true) libdeps += dependency('x11', required: true) +favorite_vfs_sources = [ + 'favorite-vfs-file.c', + 'favorite-vfs-file-enumerator.c', + 'favorite-vfs-file-monitor.c' +] + xapp_headers = [ + 'xapp-favorites.h', 'xapp-gtk-window.h', 'xapp-icon-chooser-button.h', 'xapp-icon-chooser-dialog.h', @@ -21,6 +32,7 @@ ] xapp_sources = [ + 'xapp-favorites.c', 'xapp-glade-catalog.c', 'xapp-gtk-window.c', 'xapp-icon-chooser-button.c', @@ -83,7 +95,7 @@ ) libxapp = library('xapp', - sources : xapp_headers + xapp_sources + xapp_enums + dbus_headers, + sources : xapp_headers + xapp_sources + xapp_enums + dbus_headers + favorite_vfs_sources, include_directories: [top_inc], version: meson.project_version(), soversion: '1', @@ -134,3 +146,11 @@ metadata_dirs: meson.current_source_dir(), install: true ) + +gtk3_module = shared_module( + 'xapp-gtk3-module', ['xapp-gtk3-module.c'], + include_directories: [top_inc], + dependencies: [gtk3_dep, libxapp_dep], + install: true, + install_dir: join_paths(gtk3_dep.get_pkgconfig_variable('libdir'),'gtk-3.0','modules') +) \ No newline at end of file diff --git a/libxapp/xapp-favorites.c b/libxapp/xapp-favorites.c new file mode 100644 index 0000000..6379d09 --- /dev/null +++ b/libxapp/xapp-favorites.c @@ -0,0 +1,1352 @@ + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "xapp-favorites.h" +#include "favorite-vfs-file.h" + +#define FAVORITES_SCHEMA "org.x.apps.favorites" +#define FAVORITES_KEY "list" +#define SETTINGS_DELIMITER "::" +#define MAX_DISPLAY_URI_LENGTH 20 + +G_DEFINE_BOXED_TYPE (XAppFavoriteInfo, xapp_favorite_info, xapp_favorite_info_copy, xapp_favorite_info_free); +/** + * SECTION:xapp-favorites + * @Short_description: Keeps track of favorite files. + * @Title: XAppFavorites + * + * The XAppFavorites class allows applications display frequently-used files and + * provide a safe mechanism for launching them. + * + * A list of #XAppFavoriteInfos can be retrieved in full, or only for specific mimetypes. + * + * XAppFavorites are new for 2.0 + */ + +/** + * xapp_favorite_info_copy: + * @info: The #XAppFavoriteInfo to duplicate. + * + * Makes an exact copy of an existing #XAppFavoriteInfo. + * + * Returns: (transfer full): a new #XAppFavoriteInfo. Free using #xapp_favorite_info_free. + * + * Since 2.0 + */ +XAppFavorites *global_favorites; + +XAppFavoriteInfo * +xapp_favorite_info_copy (const XAppFavoriteInfo *info) +{ + // g_debug ("XAppFavoriteInfo: copy"); + g_return_val_if_fail (info != NULL, NULL); + + XAppFavoriteInfo *_info = g_slice_dup (XAppFavoriteInfo, info); + _info->uri = g_strdup (info->uri); + _info->display_name = g_strdup (info->display_name); + _info->cached_mimetype = g_strdup (info->cached_mimetype); + + return _info; +} + +/** + * xapp_favorite_info_free: + * @info: The #XAppFavoriteInfo to free. + * + * Destroys the #XAppFavoriteInfo. + * + * Since 2.0 + */ +void +xapp_favorite_info_free (XAppFavoriteInfo *info) +{ + g_debug ("XAppFavoriteInfo free (%s)", info->uri); + g_return_if_fail (info != NULL); + + g_free (info->uri); + g_free (info->display_name); + g_free (info->cached_mimetype); + g_slice_free (XAppFavoriteInfo, info); +} + +typedef struct +{ + GHashTable *infos; + GHashTable *menus; + + GSettings *settings; + + gulong settings_listener_id; + guint changed_timer_id; +} XAppFavoritesPrivate; + +struct _XAppFavorites +{ + GObject parent_instance; +}; + +G_DEFINE_TYPE_WITH_PRIVATE (XAppFavorites, xapp_favorites, G_TYPE_OBJECT) + +enum +{ + CHANGED, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = {0, }; + +static void finish_add_favorite (XAppFavorites *favorites, + const gchar *uri, + const gchar *mimetype, + gboolean from_saved); + +static gboolean +changed_callback (gpointer data) +{ + g_return_val_if_fail (XAPP_IS_FAVORITES (data), G_SOURCE_REMOVE); + XAppFavorites *favorites = XAPP_FAVORITES (data); + + XAppFavoritesPrivate *priv = xapp_favorites_get_instance_private (favorites); + g_debug ("XAppFavorites: list updated, emitting changed signal"); + + priv->changed_timer_id = 0; + g_signal_emit (favorites, signals[CHANGED], 0); + + return G_SOURCE_REMOVE; +} + +static void +queue_changed (XAppFavorites *favorites) +{ + XAppFavoritesPrivate *priv = xapp_favorites_get_instance_private (favorites); + + if (priv->changed_timer_id > 0) + { + g_source_remove (priv->changed_timer_id); + } + + priv->changed_timer_id = g_idle_add ((GSourceFunc) changed_callback, favorites); +} + +static void +sync_metadata_callback (GObject *source, + GAsyncResult *res, + gpointer user_data) +{ + // Disabled + return; + +// GFile *file; +// GError *error; + +// file = G_FILE (source); +// error = NULL; + +// if (!g_file_set_attributes_finish (file, +// res, +// NULL, +// &error)) +// { +// if (error != NULL) +// { +// if (error->code != G_IO_ERROR_NOT_FOUND) +// { +// g_warning ("Could not update file metadata for favorite file '%s': %s", g_file_get_uri (file), error->message); +// } + +// g_error_free (error); +// } +// } +// else +// { +// if (g_file_is_native (file)) +// { +// // I can't think of any other way to touch a file so a file monitor might notice +// // the attribute change. It shouldn't be too much trouble since most times add/remove +// // will be done in the file manager (where the update can be triggered internally). + +// gchar *local_path = g_file_get_path (file); +// g_utime (local_path, NULL); +// g_free (local_path); +// } +// } +} + +static void +sync_file_metadata (XAppFavorites *favorites, + const gchar *uri, + gboolean is_favorite) +{ + /* Disabled - this is less than optimal, and is implemented instead in + * nemo, currently. This could be changed later to help support other browsers. + * Also, this only works with local files. */ + return; + + /* borrowed from nemo-vfs-file.c */ + GFileInfo *info; + GFile *file; + + g_debug ("Sync metadata: %s - Favorite? %d", uri, is_favorite); + + info = g_file_info_new (); + + if (is_favorite) { + g_file_info_set_attribute_string (info, FAVORITE_METADATA_KEY, META_TRUE); + } else { + /* Unset the key */ + g_file_info_set_attribute (info, FAVORITE_METADATA_KEY, G_FILE_ATTRIBUTE_TYPE_INVALID, NULL); + } + + file = g_file_new_for_uri (uri); + + g_file_set_attributes_async (file, + info, + 0, + G_PRIORITY_DEFAULT, + NULL, + sync_metadata_callback, + favorites); + + g_object_unref (file); + g_object_unref (info); +} + +static void +store_favorites (XAppFavorites *favorites) +{ + XAppFavoritesPrivate *priv = xapp_favorites_get_instance_private (favorites); + GList *iter, *keys; + GPtrArray *array; + gchar **new_settings; + + array = g_ptr_array_new (); + + keys = g_hash_table_get_keys (priv->infos); + + for (iter = keys; iter != NULL; iter = iter->next) + { + XAppFavoriteInfo *info = (XAppFavoriteInfo *) g_hash_table_lookup (priv->infos, iter->data); + gchar *entry; + + entry = g_strjoin (SETTINGS_DELIMITER, + info->uri, + info->cached_mimetype, + NULL); + + g_ptr_array_add (array, entry); + } + + g_ptr_array_add (array, NULL); + + g_list_free (keys); + + new_settings = (gchar **) g_ptr_array_free (array, FALSE); + + g_signal_handler_block (priv->settings, priv->settings_listener_id); + g_settings_set_strv (priv->settings, FAVORITES_KEY, (const gchar* const*) new_settings); + g_signal_handler_unblock (priv->settings, priv->settings_listener_id); + + g_debug ("XAppFavorites: store_favorites: favorites saved"); + + g_free (new_settings); +} + +static void +load_favorites (XAppFavorites *favorites, + gboolean signal_changed) +{ + XAppFavoritesPrivate *priv = xapp_favorites_get_instance_private (favorites); + gchar **raw_list; + gint i; + + if (priv->infos != NULL) + { + g_hash_table_destroy (priv->infos); + } + + priv->infos = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, (GDestroyNotify) xapp_favorite_info_free); + + raw_list = g_settings_get_strv (priv->settings, FAVORITES_KEY); + + if (!raw_list) + { + // no favorites + return; + } + + for (i = 0; i < g_strv_length (raw_list); i++) + { + gchar **entry = g_strsplit (raw_list[i], SETTINGS_DELIMITER, 2); + + finish_add_favorite (favorites, + entry[0], // uri + entry[1], // cached_mimetype + TRUE); + + g_strfreev (entry); + } + + g_strfreev (raw_list); + + g_debug ("XAppFavorites: load_favorite: favorites loaded (%d)", i); + + if (signal_changed) + { + queue_changed (favorites); + } +} + +static void +rename_favorite (XAppFavorites *favorites, + const gchar *old_uri, + const gchar *new_uri) +{ + XAppFavoriteInfo *info; + XAppFavoritesPrivate *priv = xapp_favorites_get_instance_private (favorites); + gchar *final_new_uri = NULL; + + if (g_str_has_prefix (old_uri, ROOT_URI)) + { + // Renaming occurred inside of favorites:/// we need to identify by + // display name. + + const gchar *old_display_name = old_uri + strlen (ROOT_URI); + const gchar *new_display_name = new_uri + strlen (ROOT_URI); + + info = xapp_favorites_find_by_display_name (favorites, old_display_name); + + if (info) + { + GFile *real_file, *parent, *renamed_file; + + real_file = g_file_new_for_uri (info->uri); + parent = g_file_get_parent (real_file); + + renamed_file = g_file_get_child_for_display_name (parent, + new_display_name, + NULL); + + if (renamed_file != NULL) + { + final_new_uri = g_file_get_uri (renamed_file); + } + + g_object_unref (real_file); + g_object_unref (parent); + g_clear_object (&renamed_file); + } + } + else + { + info = g_hash_table_lookup (priv->infos, old_uri); + final_new_uri = g_strdup (new_uri); + } + + if (info != NULL && final_new_uri != NULL) + { + gchar *mimetype = g_strdup (info->cached_mimetype); + + sync_file_metadata (favorites, info->uri, FALSE); + + g_hash_table_remove (priv->infos, + (gconstpointer) info->uri); + + finish_add_favorite (favorites, + final_new_uri, + mimetype, + FALSE); + + sync_file_metadata (favorites, final_new_uri, TRUE); + + g_free (mimetype); + } + + g_free (final_new_uri); +} + +static void +remove_favorite (XAppFavorites *favorites, + const gchar *uri) +{ + XAppFavoritesPrivate *priv = xapp_favorites_get_instance_private (favorites); + gchar *real_uri; + + if (g_str_has_prefix (uri, "favorites")) + { + GFile *file = g_file_new_for_uri (uri); + real_uri = favorite_vfs_file_get_real_uri (file); + + g_object_unref (file); + } + else + { + real_uri = g_strdup (uri); + } + + g_debug ("XAppFavorites: remove favorite: %s", real_uri); + + // It may be orphaned for some reason.. even if it's not in gsettings, still try + // to remove the favorite attribute. + sync_file_metadata (favorites, real_uri, FALSE); + + if (!g_hash_table_remove (priv->infos, real_uri)) + { + g_debug ("XAppFavorites: remove_favorite: could not find favorite for uri '%s'", real_uri); + g_free (real_uri); + return; + } + + g_free (real_uri); + + store_favorites (favorites); + queue_changed (favorites); +} + +static void +deduplicate_display_names (XAppFavorites *favorites, + GHashTable *infos) +{ + GList *fav_uris, *ptr; + GHashTable *lists_of_keys_by_basename = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, NULL); + GHashTableIter iter; + + fav_uris = g_hash_table_get_keys (infos); + + for (ptr = fav_uris; ptr != NULL; ptr = ptr->next) + { + GList *uris; + const gchar *uri = (gchar *) ptr->data; + gchar *original_display_name = g_path_get_basename (uri); + + if (g_hash_table_contains (lists_of_keys_by_basename, original_display_name)) + { + uris = g_hash_table_lookup (lists_of_keys_by_basename, original_display_name); + + // this could be prepend, but then the value in the table would have to be replaced + uris = g_list_append ((GList *) uris, g_strdup (uri)); + } + else + { + uris = g_list_prepend (NULL, g_strdup (uri)); + g_hash_table_insert (lists_of_keys_by_basename, + g_strdup (original_display_name), + uris); + } + + g_free (original_display_name); + } + + g_list_free (fav_uris); + + gpointer key, value; + + g_hash_table_iter_init (&iter, lists_of_keys_by_basename); + + while (g_hash_table_iter_next (&iter, &key, &value)) + { + GList *same_names_list, *uri_ptr; + const gchar *common_display_name; + + if (((GList *) value)->next == NULL) + { + // Single member of current common name list; + g_list_free_full ((GList *) value, g_free); + continue; + } + // Now we know we have a list of uris that would have identical display names + // Add a part of the uri after each to distinguish them. + common_display_name = (const gchar *) key; + same_names_list = (GList *) value; + + for (uri_ptr = same_names_list; uri_ptr != NULL; uri_ptr = uri_ptr->next) + { + XAppFavoriteInfo *info; + GFile *uri_file, *home_file, *parent_file; + GString *new_display_string; + const gchar *current_uri; + + current_uri = (const gchar *) uri_ptr->data; + + uri_file = g_file_new_for_uri (current_uri); + parent_file = g_file_get_parent (uri_file); + home_file = g_file_new_for_path (g_get_home_dir()); + + new_display_string = g_string_new (common_display_name); + g_string_append (new_display_string, " ("); + + // How much effort should we put into duplicate naming? Keeping it + // simple like this won't work all the time. + gchar *parent_basename = g_file_get_basename (parent_file); + g_string_append (new_display_string, parent_basename); + g_free (parent_basename); + + // TODO: ellipsized deduplication paths? + + // if (g_file_has_prefix (parent_file, home_file)) + // { + // gchar *home_rpath = g_file_get_relative_path (home_file, parent_file); + // gchar *home_basename = g_file_get_basename (home_file); + + // if (strlen (home_rpath) < MAX_DISPLAY_URI_LENGTH) + // { + // g_string_append (new_display_string, home_basename); + // g_string_append (new_display_string, "/"); + // g_string_append (new_display_string, home_rpath); + // } + // else + // { + // gchar *parent_basename = g_file_get_basename (parent_file); + + // g_string_append (new_display_string, home_basename); + // g_string_append (new_display_string, "/.../"); + // g_string_append (new_display_string, parent_basename); + + // g_free (parent_basename); + // } + + // g_free (home_rpath); + // g_free (home_basename); + // } + // else + // { + // GString *tmp_string = g_string_new (NULL); + + // if (g_file_is_native (parent_file)) + // { + // g_string_append (tmp_string, g_file_peek_path (parent_file)); + // } + // else + // { + // g_string_append (tmp_string, current_uri); + // } + + // if (tmp_string->len > MAX_DISPLAY_URI_LENGTH) + // { + // gint diff; + // gint replace_pos; + + // diff = tmp_string->len - MAX_DISPLAY_URI_LENGTH; + // replace_pos = (tmp_string->len / 2) - (diff / 2) - 2; + + // g_string_erase (tmp_string, + // replace_pos, + // diff); + // g_string_insert (tmp_string, + // replace_pos, + // "..."); + // } + + // g_string_append (new_display_string, tmp_string->str); + // g_string_free (tmp_string, TRUE); + // } + + g_object_unref (uri_file); + g_object_unref (home_file); + g_object_unref (parent_file); + + g_string_append (new_display_string, ")"); + + // Look up the info from our master table + info = g_hash_table_lookup (infos, current_uri); + g_free (info->display_name); + + info->display_name = g_string_free (new_display_string, FALSE); + } + + g_list_free_full (same_names_list, g_free); + } + + // We freed the individual lists just above, only the keys will need + // freed here. + g_hash_table_destroy (lists_of_keys_by_basename); +} + +static void +finish_add_favorite (XAppFavorites *favorites, + const gchar *uri, + const gchar *cached_mimetype, + gboolean from_saved) +{ + XAppFavoritesPrivate *priv = xapp_favorites_get_instance_private (favorites); + XAppFavoriteInfo *info; + gchar *unescaped_uri; + + // Check if it's there again, in case it was added while we were getting mimetype. + if (g_hash_table_contains (priv->infos, uri)) + { + g_debug ("XAppFavorites: favorite for '%s' exists, ignoring", uri); + return; + } + + info = g_slice_new0 (XAppFavoriteInfo); + info->uri = g_strdup (uri); + + unescaped_uri = g_uri_unescape_string (uri, NULL); + info->display_name = g_path_get_basename (unescaped_uri); + g_free (unescaped_uri); + + info->cached_mimetype = g_strdup (cached_mimetype); + + g_hash_table_insert (priv->infos, (gpointer) g_strdup (uri), (gpointer) info); + + g_debug ("XAppFavorites: added favorite: %s", uri); + + deduplicate_display_names (favorites, priv->infos); + + if (from_saved) + { + return; + } + + store_favorites (favorites); + queue_changed (favorites); +} + +static void +on_content_type_info_received (GObject *source, + GAsyncResult *res, + gpointer user_data) +{ + XAppFavorites *favorites = XAPP_FAVORITES (user_data); + GFile *file; + GFileInfo *file_info; + GError *error; + gchar *cached_mimetype, *uri; + + file = G_FILE (source); + uri = g_file_get_uri (file); + error = NULL; + cached_mimetype = NULL; + + file_info = g_file_query_info_finish (file, res, &error); + + if (error) + { + g_debug ("XAppFavorites: problem trying to figure out content type for uri '%s': %s", + uri, error->message); + g_error_free (error); + } + + if (file_info) + { + cached_mimetype = g_strdup (g_file_info_get_content_type (file_info)); + + if (cached_mimetype == NULL) + { + cached_mimetype = g_strdup ("application/unknown"); + } + + finish_add_favorite (favorites, + uri, + cached_mimetype, + FALSE); + + sync_file_metadata (favorites, uri, TRUE); + } + + g_free (uri); + g_free (cached_mimetype); + g_clear_object (&file_info); + g_object_unref (file); +} + +static void +add_favorite (XAppFavorites *favorites, + const gchar *uri) +{ + XAppFavoritesPrivate *priv = xapp_favorites_get_instance_private (favorites); + GFile *file; + + if (g_hash_table_contains (priv->infos, uri)) + { + g_debug ("XAppFavorites: favorite for '%s' exists, ignoring", uri); + return; + } + + file = g_file_new_for_uri (uri); + + g_file_query_info_async (file, + G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, + G_FILE_QUERY_INFO_NONE, + G_PRIORITY_LOW, + NULL, + on_content_type_info_received, + favorites); +} + +static void +on_settings_list_changed (GSettings *settings, + gchar *key, + gpointer user_data) +{ + XAppFavorites *favorites = XAPP_FAVORITES (user_data); + + load_favorites (favorites, TRUE); +} + +static void +xapp_favorites_init (XAppFavorites *favorites) +{ + XAppFavoritesPrivate *priv = xapp_favorites_get_instance_private (favorites); + + g_debug ("XAppFavorites: init:"); + + priv->settings = g_settings_new (FAVORITES_SCHEMA); + priv->settings_listener_id = g_signal_connect (priv->settings, + "changed::" FAVORITES_KEY, + G_CALLBACK (on_settings_list_changed), + favorites); + + load_favorites (favorites, FALSE); +} + +static void +xapp_favorites_dispose (GObject *object) +{ + XAppFavorites *favorites = XAPP_FAVORITES (object); + XAppFavoritesPrivate *priv = xapp_favorites_get_instance_private (favorites); + + g_debug ("XAppFavorites dispose (%p)", object); + + g_clear_object (&priv->settings); + g_clear_pointer (&priv->infos, g_hash_table_destroy); + + G_OBJECT_CLASS (xapp_favorites_parent_class)->dispose (object); +} + +static void +xapp_favorites_finalize (GObject *object) +{ + g_debug ("XAppFavorites finalize (%p)", object); + + G_OBJECT_CLASS (xapp_favorites_parent_class)->finalize (object); +} + +static void +xapp_favorites_class_init (XAppFavoritesClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->dispose = xapp_favorites_dispose; + gobject_class->finalize = xapp_favorites_finalize; + + /** + * XAppFavorites::changed: + + * Notifies when the favorites list has changed. + */ + signals [CHANGED] = + g_signal_new ("changed", + XAPP_TYPE_FAVORITES, + G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, 0); +} + +/** + * xapp_favorites_get_default: + * + * Returns the #XAppFavorites instance. + * + * Returns: (transfer none): the XAppFavorites instance for the process. Do not free. + * + * Since: 2.0 + */ +XAppFavorites * +xapp_favorites_get_default (void) +{ + if (global_favorites == NULL) + { + global_favorites = g_object_new (XAPP_TYPE_FAVORITES, NULL); + } + + return global_favorites; +} + +typedef struct { + GList *items; + const gchar **mimetypes; +} MatchData; + +void +match_mimetypes (gpointer key, + gpointer value, + gpointer user_data) +{ + MatchData *data = (MatchData *) user_data; + const XAppFavoriteInfo *info = (XAppFavoriteInfo *) value; + + if (data->mimetypes == NULL) + { + data->items = g_list_prepend (data->items, xapp_favorite_info_copy (info)); + return; + } + + gint i; + + for (i = 0; i < g_strv_length ((gchar **) data->mimetypes); i++) + { + if (g_content_type_is_mime_type (info->cached_mimetype, data->mimetypes[i])) + { + data->items = g_list_prepend (data->items, xapp_favorite_info_copy (info)); + return; + } + } +} + +/** + * xapp_favorites_get_favorites: + * @favorites: The #XAppFavorites + * @mimetypes: (nullable): The mimetypes to filter by for results + * + * Gets a list of all favorites. If mimetype is not %NULL, the list will + * contain only favorites with that mimetype. + * + * Returns: (element-type XAppFavoriteInfo) (transfer full): a list of #XAppFavoriteInfos. + Free the list with #g_list_free, free elements with #xapp_favorite_info_free. + * + * Since: 2.0 + */ +GList * +xapp_favorites_get_favorites (XAppFavorites *favorites, + const gchar **mimetypes) +{ + g_return_val_if_fail (XAPP_IS_FAVORITES (favorites), NULL); + XAppFavoritesPrivate *priv = xapp_favorites_get_instance_private (favorites); + GList *ret = NULL; + MatchData data; + + data.items = NULL; + data.mimetypes = mimetypes; + g_hash_table_foreach (priv->infos, + (GHFunc) match_mimetypes, + &data); + + ret = g_list_reverse (data.items); + + gchar *typestring = mimetypes ? g_strjoinv (", ", (gchar **) mimetypes) : NULL; + g_debug ("XAppFavorites: get_favorites returning list for mimetype '%s' (%d items)", + typestring, g_list_length (ret)); + g_free (typestring); + + return ret; +} + +/** + * xapp_favorites_get_n_favorites: + * @favorites: The #XAppFavorites + * + * Returns: The number of favorite files + + * Since: 2.0 + */ +gint +xapp_favorites_get_n_favorites (XAppFavorites *favorites) +{ + g_return_val_if_fail (XAPP_IS_FAVORITES (favorites), 0); + XAppFavoritesPrivate *priv = xapp_favorites_get_instance_private (favorites); + gint n; + + n = g_hash_table_size (priv->infos); + + g_debug ("XAppFavorites: get_n_favorites returning number of items: %d.", n); + + return n; +} + +static gboolean +lookup_display_name (gpointer key, + gpointer value, + gpointer user_data) +{ + XAppFavoriteInfo *info = (XAppFavoriteInfo *) value; + + if (g_strcmp0 (info->display_name, (const gchar *) user_data) == 0) + { + return TRUE; + } + + return FALSE; +} + +/** + * xapp_favorites_find_by_display_name: + * @favorites: The #XAppFavorites + * @display_name: (not nullable): The display name to lookup info for. + * + * Looks for an XAppFavoriteInfo that corresponds to @display_name. + * + * Returns: (transfer none): an XAppFavoriteInfo or NULL if one was not found. This is owned + * by the favorites manager and should not be freed. + * + * Since: 2.0 + */ +XAppFavoriteInfo * +xapp_favorites_find_by_display_name (XAppFavorites *favorites, + const gchar *display_name) +{ + g_return_val_if_fail (XAPP_IS_FAVORITES (favorites), NULL); + g_return_val_if_fail (display_name != NULL, NULL); + + XAppFavoriteInfo *info; + XAppFavoritesPrivate *priv = xapp_favorites_get_instance_private (favorites); + + info = g_hash_table_find (priv->infos, + (GHRFunc) lookup_display_name, + (gpointer) display_name); + + if (info != NULL) + { + return info; + } + + return NULL; +} + +/** + * xapp_favorites_find_by_uri: + * @favorites: The #XAppFavorites + * @uri: (not nullable): The uri to lookup info for. + * + * Looks for an XAppFavoriteInfo that corresponds to @uri. + * + * Returns: (transfer none): an XAppFavoriteInfo or NULL if one was not found. This is owned + * by the favorites manager and should not be freed. + * + * Since: 2.0 + */ +XAppFavoriteInfo * +xapp_favorites_find_by_uri (XAppFavorites *favorites, + const gchar *uri) +{ + g_return_val_if_fail (XAPP_IS_FAVORITES (favorites), NULL); + g_return_val_if_fail (uri != NULL, NULL); + + XAppFavoriteInfo *info; + XAppFavoritesPrivate *priv = xapp_favorites_get_instance_private (favorites); + + info = g_hash_table_lookup (priv->infos, uri); + + if (info != NULL) + { + return (XAppFavoriteInfo *) info; + } + + return NULL; +} + +/** + * xapp_favorites_add: + * @favorites: The #XAppFavorites + * @uri: The uri the favorite is for + * + * Adds a new favorite. If the uri already exists, this does nothing. + * + * Since: 2.0 + */ +void +xapp_favorites_add (XAppFavorites *favorites, + const gchar *uri) +{ + g_return_if_fail (XAPP_IS_FAVORITES (favorites)); + g_return_if_fail (uri != NULL); + + add_favorite (favorites, uri); +} + +/** + * xapp_favorites_remove: + * @favorites: The #XAppFavorites + * @uri: The uri for the favorite being removed + * + * Removes a favorite from the list. + * + * Since: 2.0 + */ +void +xapp_favorites_remove (XAppFavorites *favorites, + const gchar *uri) +{ + g_return_if_fail (XAPP_IS_FAVORITES (favorites)); + g_return_if_fail (uri != NULL); + + remove_favorite (favorites, uri); +} + +static void +launch_uri_callback (GObject *source, + GAsyncResult *res, + gpointer user_data) +{ + gchar *uri = (gchar *) user_data; + GError *error; + + error = NULL; + + if (!g_app_info_launch_default_for_uri_finish (res, &error)) + { + if (error) + { + g_debug ("XAppFavorites: launch: error opening uri '%s': %s", uri, error->message); + g_error_free (error); + } + } + + g_free (uri); +} + +/** + * xapp_favorites_launch: + * @favorites: The #XAppFavorites + * @uri: The uri for the favorite to launch + * @timestamp: The timestamp from an event or 0 + * + * Opens a favorite in its default app. + * + * Since: 2.0 + */ +void +xapp_favorites_launch (XAppFavorites *favorites, + const gchar *uri, + guint32 timestamp) +{ + GdkDisplay *display; + GdkAppLaunchContext *launch_context; + + display = gdk_display_get_default (); + launch_context = gdk_display_get_app_launch_context (display); + gdk_app_launch_context_set_timestamp (launch_context, timestamp); + + g_app_info_launch_default_for_uri_async (uri, + G_APP_LAUNCH_CONTEXT (launch_context), + NULL, + launch_uri_callback, + g_strdup (uri)); + + g_object_unref (launch_context); +} + +/** + * xapp_favorites_rename: + * @old_uri: the old favorite's uri. + * @new_uri: The new uri. + * + * Removes old_uri and adds new_uri. This is mainly for file managers to use as + * a convenience instead of add/remove, and guarantees the result, without having to + * worry about multiple dbus calls (gsettings). + * + * Since: 2.0 + */ +void +xapp_favorites_rename (XAppFavorites *favorites, + const gchar *old_uri, + const gchar *new_uri) +{ + g_return_if_fail (XAPP_IS_FAVORITES (favorites)); + g_return_if_fail (old_uri != NULL && new_uri != NULL); + + rename_favorite (favorites, old_uri, new_uri); +} + +typedef struct { + XAppFavorites *favorites; + guint update_id; + + GDestroyNotify destroy_func; + gpointer user_data; +} DestroyData; + +typedef struct { + XAppFavorites *favorites; + XAppFavoritesItemSelectedCallback callback; + gchar *uri; + gpointer user_data; +} ItemCallbackData; + +static void +cb_data_destroy_notify (gpointer callback_data, + GObject *object) +{ + DestroyData *dd = (DestroyData *) callback_data; + + if (dd->update_id > 0) + { + g_signal_handler_disconnect (dd->favorites, dd->update_id); + } + + dd->destroy_func (dd->user_data); + + g_slice_free (DestroyData, dd); +} + +static void +free_item_callback_data (gpointer callback_data, + GClosure *closure) +{ + ItemCallbackData *data = (ItemCallbackData *) callback_data; + g_free (data->uri); + g_slice_free (ItemCallbackData, data); +} + +static void +item_activated (GObject *item, + gpointer user_data) +{ + ItemCallbackData *data = (ItemCallbackData *) user_data; + + data->callback (data->favorites, + data->uri, + data->user_data); +} + +static void +remove_menu_item (GtkWidget *item, + gpointer user_data) +{ + gtk_container_remove (GTK_CONTAINER (user_data), item); +} + +static void +populate_menu (XAppFavorites *favorites, + GtkMenu *menu) +{ + GList *fav_list, *ptr; + GtkWidget *item; + XAppFavoritesItemSelectedCallback callback; + gpointer user_data; + const gchar **mimetypes; + + gtk_container_foreach (GTK_CONTAINER (menu), (GtkCallback) remove_menu_item, menu); + + mimetypes = (const gchar **) g_object_get_data (G_OBJECT (menu), "mimetypes"); + callback = g_object_get_data (G_OBJECT (menu), "activate-cb"); + user_data = g_object_get_data (G_OBJECT (menu), "user-data"); + + fav_list = xapp_favorites_get_favorites (favorites, mimetypes); + + if (fav_list == NULL) + { + return; + } + + for (ptr = fav_list; ptr != NULL; ptr = ptr->next) + { + XAppFavoriteInfo *info = (XAppFavoriteInfo *) ptr->data; + ItemCallbackData *data; + + if (mimetypes != NULL) + { + item = gtk_menu_item_new_with_label (info->display_name); + } + else + { + GtkWidget *image; + GIcon *icon; + + icon = g_content_type_get_symbolic_icon (info->cached_mimetype); + image = gtk_image_new_from_gicon (icon, GTK_ICON_SIZE_MENU); + g_object_unref (icon); + + item = gtk_image_menu_item_new_with_label (info->display_name); + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image); + } + + data = g_slice_new0 (ItemCallbackData); + data->favorites = favorites; + data->uri = g_strdup (info->uri); + data->callback = callback; + data->user_data = user_data; + + gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); + + g_signal_connect_data (item, + "activate", G_CALLBACK (item_activated), + data, (GClosureNotify) free_item_callback_data, 0); + } + + gtk_widget_show_all (GTK_WIDGET (menu)); +} + +static void +refresh_menu_items (XAppFavorites *favorites, + gpointer user_data) +{ + g_return_if_fail (XAPP_IS_FAVORITES (favorites)); + g_return_if_fail (GTK_IS_MENU (user_data)); + + GtkMenu *menu = GTK_MENU (user_data); + + populate_menu (favorites, menu); +} + +/** + * xapp_favorites_create_menu: + * @favorites: The #XAppFavorites instance. + * @mimetypes: (nullable): The mimetypes to filter for, or NULL to include all favorites. + * @callback: (scope notified): (closure user_data): The callback to use when a menu item has been selected. + * @user_data: (closure): The data to pass to the callback + * @func: Destroy function for user_data + * + * Generates a GtkMenu widget populated with favorites. The callback will be called when + * a menu item has been activated, and will include the uri of the respective item. + * + * Returns: (transfer full): a new #GtkMenu populated with a list of favorites, or NULL + if there are no favorites. + * + * Since: 2.0 + */ +GtkWidget * +xapp_favorites_create_menu (XAppFavorites *favorites, + const gchar **mimetypes, + XAppFavoritesItemSelectedCallback callback, + gpointer user_data, + GDestroyNotify func) +{ + g_return_val_if_fail (XAPP_IS_FAVORITES (favorites), NULL); + GtkWidget *menu; + + menu = gtk_menu_new (); + + g_object_set_data_full (G_OBJECT (menu), + "mimetype", g_strdupv ((gchar **) mimetypes), + (GDestroyNotify) g_strfreev); + + g_object_set_data (G_OBJECT (menu), + "activate-cb", callback); + + g_object_set_data (G_OBJECT (menu), + "user-data", user_data); + + populate_menu (favorites, GTK_MENU (menu)); + + DestroyData *dd = g_slice_new0 (DestroyData); + dd->destroy_func = func; + dd->user_data = user_data; + dd->favorites = favorites; + dd->update_id = g_signal_connect (favorites, + "changed", + G_CALLBACK (refresh_menu_items), + menu); + + g_object_weak_ref (G_OBJECT (menu), (GWeakNotify) cb_data_destroy_notify, dd); + + return menu; +} + +static GList * +populate_action_list (XAppFavorites *favorites, + const gchar **mimetypes) +{ + GList *fav_list, *ptr; + GList *actions; + GtkAction *action; + gint i; + + fav_list = xapp_favorites_get_favorites (favorites, mimetypes); + + if (fav_list == NULL) + { + return NULL; + } + + actions = NULL; + + for (ptr = fav_list, i = 0; ptr != NULL; ptr = ptr->next, i++) + { + XAppFavoriteInfo *info = (XAppFavoriteInfo *) ptr->data; + + if (mimetypes != NULL) + { + action = g_object_new (GTK_TYPE_ACTION, + "name", info->uri, + "label", info->display_name, + NULL); + } + else + { + GIcon *icon; + icon = g_content_type_get_symbolic_icon (info->cached_mimetype); + + action = g_object_new (GTK_TYPE_ACTION, + "name", info->uri, + "label", info->display_name, + "gicon", icon, + NULL); + + g_free (icon); + } + + actions = g_list_prepend (actions, action); + } + + actions = g_list_reverse (actions); + + return actions; +} + +/** + * xapp_favorites_create_actions: + * @favorites: The #XAppFavorites instance. + * @mimetypes: (nullable): The mimetypes to filter for, or NULL to include all favorites. + * + * Generates a list of favorite GtkActions. + * + * Returns: (element-type Gtk.Action) (transfer full): a new #GtkActionGroup populated with a list of favorites, or NULL + if there are no favorites. + + * Since: 2.0 + */ +GList * +xapp_favorites_create_actions (XAppFavorites *favorites, + const gchar **mimetypes) +{ + g_return_val_if_fail (XAPP_IS_FAVORITES (favorites), NULL); + GList *actions; + + actions = populate_action_list (favorites, + mimetypes); + + return actions; +} + +/* Used by favorite_vfs_file */ +GList * +_xapp_favorites_get_display_names (XAppFavorites *favorites) +{ + g_return_val_if_fail (XAPP_IS_FAVORITES (favorites), NULL); + XAppFavoritesPrivate *priv = xapp_favorites_get_instance_private (favorites); + GHashTableIter iter; + GList *ret; + gpointer key, value; + + ret = NULL; + g_hash_table_iter_init (&iter, priv->infos); + + while (g_hash_table_iter_next (&iter, &key, &value)) + { + XAppFavoriteInfo *info = (XAppFavoriteInfo *) value; + ret = g_list_prepend (ret, info->display_name); + } + + ret = g_list_reverse (ret); + return ret; +} + diff --git a/libxapp/xapp-favorites.h b/libxapp/xapp-favorites.h new file mode 100644 index 0000000..c4b8802 --- /dev/null +++ b/libxapp/xapp-favorites.h @@ -0,0 +1,83 @@ +#ifndef __XAPP_FAVORITES_H__ +#define __XAPP_FAVORITES_H__ + +#include +#include + +#include + +G_BEGIN_DECLS + +#define XAPP_TYPE_FAVORITE_INFO (xapp_favorite_info_get_type ()) +typedef struct _XAppFavoriteInfo XAppFavoriteInfo; + +#define XAPP_TYPE_FAVORITES (xapp_favorites_get_type ()) + +G_DECLARE_FINAL_TYPE (XAppFavorites, xapp_favorites, XAPP, FAVORITES, GObject) + +XAppFavorites *xapp_favorites_get_default (void); +GList *xapp_favorites_get_favorites (XAppFavorites *favorites, + const gchar **mimetypes); +gint xapp_favorites_get_n_favorites (XAppFavorites *favorites); +XAppFavoriteInfo *xapp_favorites_find_by_display_name (XAppFavorites *favorites, + const gchar *display_name); +XAppFavoriteInfo *xapp_favorites_find_by_uri (XAppFavorites *favorites, + const gchar *uri); +void xapp_favorites_add (XAppFavorites *favorites, + const gchar *uri); +void xapp_favorites_remove (XAppFavorites *favorites, + const gchar *uri); +void xapp_favorites_launch (XAppFavorites *favorites, + const gchar *uri, + guint32 timestamp); +void xapp_favorites_rename (XAppFavorites *favorites, + const gchar *old_uri, + const gchar *new_uri); + +/** + * XAppFavoriteInfo: + * @uri: The uri to the favorite file. + * @display_name: The name for use when displaying the item. This may not exactly match + * the filename if there are files with the same name but in different folders. + * @cached_mimetype: The mimetype calculated for the uri when it was added to favorites. + * + * Information related to a single favorite file. + */ +struct _XAppFavoriteInfo +{ + gchar *uri; + gchar *display_name; + gchar *cached_mimetype; +}; + +GType xapp_favorite_info_get_type (void) G_GNUC_CONST; +XAppFavoriteInfo *xapp_favorite_info_copy (const XAppFavoriteInfo *info); +void xapp_favorite_info_free (XAppFavoriteInfo *info); + + +/* XAppFavoritesMenu */ + +/** + * FavoritesItemSelectedCallback + * @favorites: the #XAppFavorites instance + * @uri: the uri of the favorite that was selected + * @user_data: Callback data + * + * Callback when an item has been selected from the favorites + * #GtkMenu. + */ +typedef void (* XAppFavoritesItemSelectedCallback) (XAppFavorites *favorites, + const gchar *uri, + gpointer user_data); + +GtkWidget *xapp_favorites_create_menu (XAppFavorites *favorites, + const gchar **mimetypes, + XAppFavoritesItemSelectedCallback callback, + gpointer user_data, + GDestroyNotify func); +GList *xapp_favorites_create_actions (XAppFavorites *favorites, + const gchar **mimetypes); + +G_END_DECLS + +#endif /* __XAPP_FAVORITES_H__ */ diff --git a/libxapp/xapp-gtk3-module.c b/libxapp/xapp-gtk3-module.c new file mode 100644 index 0000000..389ff98 --- /dev/null +++ b/libxapp/xapp-gtk3-module.c @@ -0,0 +1,86 @@ +#include +#include + +#include "xapp-favorites.h" +#include "favorite-vfs-file.h" + +/* Gtk module justification: + * + * The sole purpose of this module currently is to add a 'Favorites' + * shortcut to GtkFileChooser dialogs. + * + * In gtk_module_init, the XAppFavorites singleton is initialized, and + * the 'favorites' uri scheme is added to the default vfs. Ordinarily + * non-file:// schemes aren't supported in these dialogs unless their + * 'local-only' property is set to FALSE. Since favorites are shortcuts + * to locally-available files, we lie to the chooser setup by returning + * "/" instead of NULL when g_file_get_path ("favorites:///") is called. + */ + + +/* Make sure GCC doesn't warn us about a missing prototype for this + * exported function */ +void gtk_module_init (gint *argc, gchar ***argv[]); + +static gboolean +selection_changed_cb (GSignalInvocationHint *ihint, + guint n_param_values, + const GValue *param_values, + gpointer data) +{ + GtkFileChooser *chooser = GTK_FILE_CHOOSER (g_value_get_object (¶m_values[0])); + GSList *list, *i; + gboolean already_applied = FALSE; + list = gtk_file_chooser_list_shortcut_folder_uris (chooser); + + for (i = list; i != NULL; i = i->next) + { + if (g_strcmp0 ((gchar *) i->data, "favorites:///") == 0) + { + already_applied = TRUE; + break; + } + } + + g_slist_free_full (list, g_free); + + if (!already_applied) + { + xapp_favorites_get_default (); + gtk_file_chooser_add_shortcut_folder_uri (chooser, "favorites:///", NULL); + } + + return TRUE; +} + +static void +add_chooser_hook (GType type) +{ + GTypeClass *type_class; + guint sigid; + + type_class = g_type_class_ref (type); + + sigid = g_signal_lookup ("selection-changed", type); + g_signal_add_emission_hook (sigid, 0, selection_changed_cb, NULL, NULL); + + g_type_class_unref (type_class); +} + +G_MODULE_EXPORT void gtk_module_init (gint *argc, gchar ***argv[]) { + // This won't instantiate XAppFavorites but will register the uri so + // it can be used by apps (like pix which doesn't use the favorites api, + // but just adds favorites:/// to its sidebar.) + init_favorite_vfs (); + + add_chooser_hook (GTK_TYPE_FILE_CHOOSER_WIDGET); + add_chooser_hook (GTK_TYPE_FILE_CHOOSER_DIALOG); + add_chooser_hook (GTK_TYPE_FILE_CHOOSER_BUTTON); +} + +G_MODULE_EXPORT gchar* g_module_check_init (GModule *module); + +G_MODULE_EXPORT gchar* g_module_check_init (GModule *module) { + g_module_make_resident(module); + return NULL; +} diff --git a/libxapp/xapp-status-icon-monitor.c b/libxapp/xapp-status-icon-monitor.c index 7c0ebe7..9a6525a 100644 --- a/libxapp/xapp-status-icon-monitor.c +++ b/libxapp/xapp-status-icon-monitor.c @@ -17,15 +17,14 @@ #include "xapp-status-icon-monitor.h" #include "xapp-statusicon-interface.h" -#define MONITOR_PATH "/org/x/StatusIconMonitor" #define MONITOR_NAME "org.x.StatusIconMonitor" #define STATUS_ICON_MATCH "org.x.StatusIcon." - -#define STATUS_ICON_ID_FORMAT "org.x.StatusIcon.PID-%d-%d" -#define STATUS_ICON_PATH_PREFIX "/org/x/StatusIcon/" - -#define STATUS_NOTIFIER_WATCHER_NAME "org.x.StatusNotifierWatcher" +#define STATUS_ICON_INTERFACE "org.x.StatusIcon" + +#define STATUS_ICON_PATH "/org/x/StatusIcon" +#define STATUS_ICON_PATH_PREFIX STATUS_ICON_PATH "/" + #define WATCHER_MAX_RESTARTS 2 enum @@ -59,15 +58,10 @@ { GDBusConnection *connection; - GHashTable *icons; - gchar *name; + GHashTable *object_managers; guint owner_id; guint listener_id; - guint sn_watcher_id; - - guint sn_watcher_retry_count; - } XAppStatusIconMonitorPrivate; struct _XAppStatusIconMonitor @@ -77,132 +71,150 @@ G_DEFINE_TYPE_WITH_PRIVATE (XAppStatusIconMonitor, xapp_status_icon_monitor, G_TYPE_OBJECT) -static void remove_icon (XAppStatusIconMonitor *self, const gchar *name); - -static void -on_proxy_name_owner_changed (GObject *object, - GParamSpec *pspec, - gpointer user_data) -{ - XAppStatusIconMonitor *self = XAPP_STATUS_ICON_MONITOR (user_data); - - gchar *name_owner = NULL; - gchar *proxy_name = NULL; +static void +on_object_manager_name_owner_changed (GObject *object, + GParamSpec *pspec, + gpointer user_data) +{ + XAppStatusIconMonitor *self = XAPP_STATUS_ICON_MONITOR (user_data); + XAppStatusIconMonitorPrivate *priv = xapp_status_icon_monitor_get_instance_private (self); + gchar *name, *owner; g_object_get (object, - "g-name-owner", &name_owner, - "g-name", &proxy_name, + "name-owner", &owner, + "name", &name, NULL); - g_debug("XAppStatusIconMonitor: proxy name owner changed - name owner '%s' is now '%s')", proxy_name, name_owner); - - if (name_owner == NULL) - { - remove_icon (self, proxy_name); - } - - g_free (name_owner); - g_free (proxy_name); -} - -static void -remove_icon (XAppStatusIconMonitor *self, - const gchar *name) -{ - XAppStatusIconMonitorPrivate *priv = xapp_status_icon_monitor_get_instance_private (self); - XAppStatusIconInterface *proxy; - - proxy = g_hash_table_lookup (priv->icons, name); - - if (proxy) - { - g_object_ref (proxy); - - g_signal_handlers_disconnect_by_func (proxy, - on_proxy_name_owner_changed, - self); - - if (g_hash_table_remove (priv->icons, name)) - { - g_debug("XAppStatusIconMonitor: removing icon: '%s'", name); - - g_signal_emit (self, signals[ICON_REMOVED], 0, proxy); - } - else - { - g_assert_not_reached (); - } - - g_object_unref (proxy); - } -} - -static void -new_status_icon_proxy_complete (GObject *object, - GAsyncResult *res, - gpointer user_data) -{ - XAppStatusIconMonitor *self = XAPP_STATUS_ICON_MONITOR (user_data); - XAppStatusIconMonitorPrivate *priv = xapp_status_icon_monitor_get_instance_private (self); - XAppStatusIconInterface *proxy; + g_debug("XAppStatusIconMonitor: app name owner changed - name '%s' is now %s)", + name, owner != NULL ? "owned" : "unowned"); + + if (owner == NULL) + { + g_hash_table_remove (priv->object_managers, name); + } + + g_free (owner); + g_free (name); +} + +static void +object_manager_object_added (XAppObjectManagerClient *manager, + GDBusObject *object, + gpointer user_data) +{ + XAppStatusIconMonitor *self = XAPP_STATUS_ICON_MONITOR (user_data); + GDBusInterface *proxy; + + proxy = g_dbus_object_get_interface (object, STATUS_ICON_INTERFACE); + + g_signal_emit (self, signals[ICON_ADDED], 0, XAPP_STATUS_ICON_INTERFACE_PROXY (proxy)); + + g_object_unref (proxy); +} + +static void +object_manager_object_removed (XAppObjectManagerClient *manager, + GDBusObject *object, + gpointer user_data) +{ + XAppStatusIconMonitor *self = XAPP_STATUS_ICON_MONITOR (user_data); + GDBusInterface *proxy; + + proxy = g_dbus_object_get_interface (object, STATUS_ICON_INTERFACE); + + g_signal_emit (self, signals[ICON_REMOVED], 0, XAPP_STATUS_ICON_INTERFACE_PROXY (proxy)); + + g_object_unref (proxy); +} + +static void +new_object_manager_created (GObject *object, + GAsyncResult *res, + gpointer user_data) +{ + XAppStatusIconMonitor *self = XAPP_STATUS_ICON_MONITOR (user_data); + XAppStatusIconMonitorPrivate *priv = xapp_status_icon_monitor_get_instance_private (self); + GDBusObjectManager *obj_mgr; + GList *objects = NULL, *iter; + GError *error; - gchar *g_name; + gchar *name; error = NULL; - proxy = xapp_status_icon_interface_proxy_new_finish (res, - &error); + obj_mgr = xapp_object_manager_client_new_finish (res, + &error); if (error) { - g_warning ("Couldn't add status icon: %s", error->message); + g_warning ("Couldn't create object manager for bus name: %s", error->message); g_error_free (error); return; } - g_signal_connect_object (proxy, - "notify::g-name-owner", - G_CALLBACK (on_proxy_name_owner_changed), - self, - 0); - - g_object_get (proxy, "g-name", &g_name, NULL); - - g_hash_table_insert (priv->icons, - g_name, - proxy); - - g_signal_emit (self, signals[ICON_ADDED], 0, proxy); -} - -static void -add_icon (XAppStatusIconMonitor *self, - const gchar *name) -{ - XAppStatusIconMonitorPrivate *priv = xapp_status_icon_monitor_get_instance_private (self); - gchar *unique_path; - gint pid, id; - - if (sscanf (name, STATUS_ICON_ID_FORMAT, &pid, &id) == 2) - { - unique_path = g_strdup_printf (STATUS_ICON_PATH_PREFIX "%d", id); - - g_debug("XAppStatusIconMonitor: adding icon: '%s' with path '%s'", name, unique_path); - - xapp_status_icon_interface_proxy_new (priv->connection, - G_DBUS_PROXY_FLAGS_NONE, - name, - unique_path, - NULL, - new_status_icon_proxy_complete, - self); - - g_free (unique_path); + g_object_get (obj_mgr, "name", &name, NULL); + + g_debug("XAppStatusIconMonitor: Object manager added for new bus name: '%s'", name); + + g_signal_connect (obj_mgr, + "notify::name-owner", + G_CALLBACK (on_object_manager_name_owner_changed), + self); + + g_signal_connect (obj_mgr, + "object-added", + G_CALLBACK (object_manager_object_added), + self); + + g_signal_connect (obj_mgr, + "object-removed", + G_CALLBACK (object_manager_object_removed), + self); + + g_hash_table_insert (priv->object_managers, + name, + obj_mgr); + + objects = g_dbus_object_manager_get_objects (obj_mgr); + + for (iter = objects; iter != NULL; iter = iter->next) + { + GDBusObject *object = G_DBUS_OBJECT (iter->data); + GDBusInterface *proxy = g_dbus_object_get_interface (object, STATUS_ICON_INTERFACE); + + g_signal_emit (self, signals[ICON_ADDED], 0, proxy); + + g_object_unref (proxy); + } + + g_list_free_full (objects, g_object_unref); +} + +static void +add_object_manager_for_name (XAppStatusIconMonitor *self, + const gchar *name) +{ + XAppStatusIconMonitorPrivate *priv = xapp_status_icon_monitor_get_instance_private (self); + gchar **name_parts = NULL; + + name_parts = g_strsplit (name, ".", -1); + + if (g_strv_length (name_parts) == 4) + { + xapp_object_manager_client_new (priv->connection, + G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_DO_NOT_AUTO_START, + name, + STATUS_ICON_PATH, + NULL, + new_object_manager_created, + self); } else { - g_debug ("XAppStatusIconMonitor: adding icon failed, name '%s' is invalid", name); - } + g_debug ("XAppStatusIconMonitor: adding object manager failed, bus name '%s' is invalid", name); + } + + g_strfreev (name_parts); } static void @@ -236,8 +248,8 @@ /* the '.' at the end so we don't catch ourselves in this */ if (g_str_has_prefix (str, STATUS_ICON_MATCH)) { - g_debug ("XAppStatusIconMonitor: found icon: %s", str); - add_icon (self, str); + g_debug ("XAppStatusIconMonitor: found new status icon app: %s", str); + add_object_manager_for_name (self, str); } } @@ -250,8 +262,7 @@ { XAppStatusIconMonitorPrivate *priv = xapp_status_icon_monitor_get_instance_private (self); - - g_debug("XAppStatusIconMonitor: looking for status icons on the bus"); + g_debug("XAppStatusIconMonitor: looking for status icon apps on the bus"); /* If there are no monitors (applets) already running when this is set up, * this won't find anything. The XAppStatusIcons will be in fallback mode, @@ -290,42 +301,6 @@ } static void -status_icon_name_appeared (XAppStatusIconMonitor *self, - const gchar *name, - const gchar *owner) -{ - XAppStatusIconMonitorPrivate *priv = xapp_status_icon_monitor_get_instance_private (self); - - if (!g_str_has_prefix (name, STATUS_ICON_MATCH)) - { - return; - } - - if (g_hash_table_contains (priv->icons, name)) - { - return; - } - - g_debug ("XAppStatusIconMonitor: new icon appeared: %s", name); - - add_icon (self, name); -} - -static void -status_icon_name_vanished (XAppStatusIconMonitor *self, - const gchar *name) -{ - if (!g_str_has_prefix (name, STATUS_ICON_MATCH)) - { - return; - } - - g_debug ("XAppStatusIconMonitor: icon presence vanished: %s", name); - - remove_icon (self, name); -} - -static void name_owner_changed (GDBusConnection *connection, const gchar *sender_name, const gchar *object_path, @@ -344,24 +319,18 @@ g_variant_get (parameters, "(&s&s&s)", &name, &old_owner, &new_owner); - if (old_owner[0] != '\0') - { - status_icon_name_vanished (self, name); - } - if (new_owner[0] != '\0') { - status_icon_name_appeared (self, name, new_owner); - } -} - + add_object_manager_for_name (self, name); + } +} static void add_name_listener (XAppStatusIconMonitor *self) { XAppStatusIconMonitorPrivate *priv = xapp_status_icon_monitor_get_instance_private (self); - g_debug ("XAppStatusIconMonitor: Adding NameOwnerChanged listener for status icons"); + g_debug ("XAppStatusIconMonitor: Adding NameOwnerChanged listener for status icon apps"); priv->listener_id = g_dbus_connection_signal_subscribe (priv->connection, "org.freedesktop.DBus", @@ -393,7 +362,7 @@ { XAppStatusIconMonitor *self = XAPP_STATUS_ICON_MONITOR (user_data); - g_debug ("XAppStatusIconMonitor: name acquired on dbus"); + g_debug ("XAppStatusIconMonitor: Name owned on bus: %s", name); add_name_listener (self); find_and_add_icons (self); @@ -408,7 +377,7 @@ XAppStatusIconMonitor *self = XAPP_STATUS_ICON_MONITOR (user_data); XAppStatusIconMonitorPrivate *priv = xapp_status_icon_monitor_get_instance_private (self); - g_debug ("XAppStatusIconMonitor: session bus connection acquired"); + g_debug ("XAppStatusIconMonitor: Connected to bus: %s", name); priv->connection = connection; } @@ -417,17 +386,17 @@ connect_to_bus (XAppStatusIconMonitor *self) { XAppStatusIconMonitorPrivate *priv = xapp_status_icon_monitor_get_instance_private (self); - + gchar *valid_app_name, *owned_name; static gint unique_id = 0; - char *owner_name = g_strdup_printf("%s.PID-%d-%d", MONITOR_NAME, getpid (), unique_id); - - unique_id++; - - g_debug ("XAppStatusIconMonitor: Attempting to acquire presence on dbus as %s", owner_name); + valid_app_name = g_strdelimit (g_strdup (g_get_application_name ()), ".-,=+~`/", '_'); + owned_name = g_strdup_printf ("%s.%s_%d", MONITOR_NAME, valid_app_name, unique_id++); + g_free (valid_app_name); + + g_debug ("XAppStatusIconMonitor: Attempting to own name on bus: %s", owned_name); priv->owner_id = g_bus_own_name (G_BUS_TYPE_SESSION, - owner_name, + owned_name, G_DBUS_CONNECTION_FLAGS_NONE, on_bus_acquired, on_name_acquired, @@ -435,7 +404,7 @@ self, NULL); - g_free(owner_name); + g_free(owned_name); } static void @@ -443,12 +412,8 @@ { XAppStatusIconMonitorPrivate *priv = xapp_status_icon_monitor_get_instance_private (self); - priv->name = g_strdup_printf("%s", g_get_application_name()); - - priv->icons = g_hash_table_new_full (g_str_hash, g_str_equal, - g_free, g_object_unref); - - priv->sn_watcher_retry_count = 0; + priv->object_managers = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, g_object_unref); connect_to_bus (self); } @@ -469,23 +434,20 @@ priv->listener_id = 0; } + if (priv->object_managers != NULL) + { + g_hash_table_unref (priv->object_managers); + priv->object_managers = NULL; + } + if (priv->owner_id > 0) { g_bus_unown_name(priv->owner_id); priv->owner_id = 0; } - if (priv->sn_watcher_id > 0) - { - g_bus_unwatch_name (priv->sn_watcher_id); - priv->sn_watcher_id = 0; - } - g_clear_object (&priv->connection); } - - g_free (priv->name); - g_clear_pointer (&priv->icons, g_hash_table_unref); G_OBJECT_CLASS (xapp_status_icon_monitor_parent_class)->dispose (object); } @@ -539,6 +501,31 @@ G_TYPE_NONE, 1, XAPP_TYPE_STATUS_ICON_INTERFACE_PROXY); } +static void +gather_objects_foreach_func (gpointer key, + gpointer value, + gpointer user_data) +{ + GDBusObjectManager *obj_mgr = G_DBUS_OBJECT_MANAGER (value); + GList **ret = (GList **) user_data; + + GList *objects = NULL, *iter; + + objects = g_dbus_object_manager_get_objects (obj_mgr); + + for (iter = objects; iter != NULL; iter = iter->next) + { + GDBusObject *object = G_DBUS_OBJECT (iter->data); + GDBusInterface *proxy = g_dbus_object_get_interface (object, STATUS_ICON_INTERFACE); + + *ret = g_list_prepend (*ret, proxy); + + g_object_unref (proxy); + } + + g_list_free_full (objects, g_object_unref); +} + /** * xapp_status_icon_monitor_list_icons: * @monitor: a #XAppStatusIconMonitor @@ -553,10 +540,15 @@ xapp_status_icon_monitor_list_icons (XAppStatusIconMonitor *monitor) { g_return_val_if_fail (XAPP_IS_STATUS_ICON_MONITOR (monitor), NULL); + GList *ret = NULL; XAppStatusIconMonitorPrivate *priv = xapp_status_icon_monitor_get_instance_private (monitor); - return g_hash_table_get_values (priv->icons); + g_hash_table_foreach (priv->object_managers, + (GHFunc) gather_objects_foreach_func, + &ret); + + return ret; } /** diff --git a/libxapp/xapp-status-icon.c b/libxapp/xapp-status-icon.c index be642d1..4153a44 100644 --- a/libxapp/xapp-status-icon.c +++ b/libxapp/xapp-status-icon.c @@ -20,7 +20,8 @@ #define FDO_DBUS_NAME "org.freedesktop.DBus" #define FDO_DBUS_PATH "/org/freedesktop/DBus" -#define ICON_PATH "/org/x/StatusIcon" +#define ICON_BASE_PATH "/org/x/StatusIcon" +#define ICON_SUB_PATH (ICON_BASE_PATH "/Icon") #define ICON_NAME "org.x.StatusIcon" #define STATUS_ICON_MONITOR_MATCH "org.x.StatusIconMonitor" @@ -30,7 +31,13 @@ #define MAX_SANE_ICON_SIZE 96 #define FALLBACK_ICON_SIZE 24 -static gint unique_id = 0; +// This gets reffed and unreffed according to individual icon presence. +// For the first icon, it gets created when exporting the icon's inteface. +// For each additional icon, it gets reffed again. On destruction, the +// opposite occurs - unrefs, and the final unref calls a weak notify +// function, which clears this pointer and unown's the process's bus name. +static GDBusObjectManagerServer *obj_server = NULL; +static guint name_owner_id = 0; enum { @@ -50,6 +57,7 @@ PROP_PRIMARY_MENU, PROP_SECONDARY_MENU, PROP_ICON_SIZE, + PROP_NAME, N_PROPERTIES }; @@ -67,8 +75,9 @@ */ typedef struct { - XAppStatusIconInterface *skeleton; GDBusConnection *connection; + XAppStatusIconInterface *interface_skeleton; + XAppObjectSkeleton *object_skeleton; GCancellable *cancellable; @@ -86,7 +95,6 @@ gint icon_size; gchar *metadata; - guint owner_id; guint listener_id; gint fail_counter; @@ -103,7 +111,7 @@ static void refresh_icon (XAppStatusIcon *self); static void use_gtk_status_icon (XAppStatusIcon *self); -static void tear_down_dbus (XAppStatusIcon *self); +static void remove_icon_path_from_bus (XAppStatusIcon *self); static void cancellable_reset (XAppStatusIcon *self) @@ -266,7 +274,7 @@ if (icon->priv->state == XAPP_STATUS_ICON_STATE_NATIVE) { - xapp_status_icon_interface_set_primary_menu_is_open (icon->priv->skeleton, FALSE); + xapp_status_icon_interface_set_primary_menu_is_open (icon->priv->interface_skeleton, FALSE); } g_signal_handlers_disconnect_by_func (widget, primary_menu_unmapped, icon); @@ -283,7 +291,7 @@ if (icon->priv->state == XAPP_STATUS_ICON_STATE_NATIVE) { - xapp_status_icon_interface_set_secondary_menu_is_open (icon->priv->skeleton, FALSE); + xapp_status_icon_interface_set_secondary_menu_is_open (icon->priv->interface_skeleton, FALSE); } g_signal_handlers_disconnect_by_func (widget, secondary_menu_unmapped, icon); @@ -328,7 +336,7 @@ { if (self->priv->state == XAPP_STATUS_ICON_STATE_NATIVE) { - xapp_status_icon_interface_set_primary_menu_is_open (self->priv->skeleton, TRUE); + xapp_status_icon_interface_set_primary_menu_is_open (self->priv->interface_skeleton, TRUE); } g_signal_connect (gtk_widget_get_toplevel (GTK_WIDGET (menu)), @@ -341,7 +349,7 @@ { if (self->priv->state == XAPP_STATUS_ICON_STATE_NATIVE) { - xapp_status_icon_interface_set_secondary_menu_is_open (self->priv->skeleton, TRUE); + xapp_status_icon_interface_set_secondary_menu_is_open (self->priv->interface_skeleton, TRUE); } g_signal_connect (gtk_widget_get_toplevel (GTK_WIDGET (menu)), @@ -729,7 +737,7 @@ g_clear_object (&self->priv->gtk_status_icon); - g_object_set (G_OBJECT (priv->skeleton), + g_object_set (G_OBJECT (priv->interface_skeleton), "name", priv->name, "label", priv->label, "icon-name", priv->icon_name, @@ -738,7 +746,7 @@ "metadata", priv->metadata, NULL); - g_dbus_interface_skeleton_flush (G_DBUS_INTERFACE_SKELETON (priv->skeleton)); + g_dbus_interface_skeleton_flush (G_DBUS_INTERFACE_SKELETON (priv->interface_skeleton)); } static void @@ -770,41 +778,66 @@ { "handle-scroll", handle_scroll_method } }; +static void +obj_server_finalized (gpointer data, + GObject *object) +{ + g_debug ("XAppStatusIcon: Final icon removed, clearing object manager (%s)", g_get_application_name ()); + + if (name_owner_id > 0) + { + g_bus_unown_name(name_owner_id); + name_owner_id = 0; + } + + obj_server = NULL; +} + +static void +ensure_object_manager (XAppStatusIcon *self) +{ + if (obj_server == NULL) + { + g_debug ("XAppStatusIcon: New object manager for (%s)", g_get_application_name ()); + + obj_server = g_dbus_object_manager_server_new (ICON_BASE_PATH); + g_dbus_object_manager_server_set_connection (obj_server, self->priv->connection); + g_object_weak_ref (G_OBJECT (obj_server),(GWeakNotify) obj_server_finalized, self); + } + else + { + g_object_ref (obj_server); + } +} + static gboolean export_icon_interface (XAppStatusIcon *self) { - GError *error = NULL; gint i; - gchar *unique_path; - - if (self->priv->skeleton) { + + ensure_object_manager (self); + + if (self->priv->interface_skeleton) + { return TRUE; } - self->priv->skeleton = xapp_status_icon_interface_skeleton_new (); - - unique_path = g_strdup_printf (ICON_PATH "/%d", unique_id); - - g_debug ("XAppStatusIcon: exporting StatusIcon dbus interface to %s", unique_path); - - g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (self->priv->skeleton), - self->priv->connection, - unique_path, - &error); - - g_free (unique_path); - - if (error != NULL) { - g_critical ("XAppStatusIcon: could not export StatusIcon interface: %s", error->message); - g_error_free (error); - - return FALSE; - } + self->priv->object_skeleton = xapp_object_skeleton_new (ICON_SUB_PATH); + self->priv->interface_skeleton = xapp_status_icon_interface_skeleton_new (); + + xapp_object_skeleton_set_status_icon_interface (self->priv->object_skeleton, + self->priv->interface_skeleton); + + g_dbus_object_manager_server_export_uniquely (obj_server, + G_DBUS_OBJECT_SKELETON (self->priv->object_skeleton)); + + g_object_unref (self->priv->object_skeleton); + g_object_unref (self->priv->interface_skeleton); for (i = 0; i < G_N_ELEMENTS (skeleton_signals); i++) { SkeletonSignal sig = skeleton_signals[i]; - g_signal_connect (self->priv->skeleton, + g_signal_connect (self->priv->interface_skeleton, sig.signal_name, G_CALLBACK (sig.callback), self); @@ -816,20 +849,40 @@ static void connect_with_status_applet (XAppStatusIcon *self) { - - char *owner_name = g_strdup_printf("%s.PID-%d-%d", ICON_NAME, getpid (), unique_id); - - unique_id++; - - g_debug ("XAppStatusIcon: Attempting to acquire presence on dbus as %s", owner_name); - - self->priv->owner_id = g_bus_own_name_on_connection (self->priv->connection, - owner_name, - G_DBUS_CONNECTION_FLAGS_NONE, - on_name_acquired, - on_name_lost, - self, - NULL); + gchar **name_parts = NULL; + gchar *owner_name; + + name_parts = g_strsplit (self->priv->name, ".", -1); + + if (g_dbus_is_name (self->priv->name) && + g_str_has_prefix (self->priv->name, ICON_NAME) && + g_strv_length (name_parts) == 4) + { + owner_name = g_strdup (self->priv->name); + } + else + { + gchar *valid_app_name = g_strdelimit (g_strdup (g_get_application_name ()), " .-,=+~`/", '_'); + + owner_name = g_strdup_printf ("%s.%s", + ICON_NAME, + valid_app_name); + g_free (valid_app_name); + } + + g_strfreev (name_parts); + + if (name_owner_id == 0) + { + g_debug ("XAppStatusIcon: Attempting to own name on bus '%s'", owner_name); + name_owner_id = g_bus_own_name_on_connection (self->priv->connection, + owner_name, + G_BUS_NAME_OWNER_FLAGS_DO_NOT_QUEUE, + on_name_acquired, + on_name_lost, + self, + NULL); + } g_free(owner_name); } @@ -896,7 +949,7 @@ g_debug ("XAppStatusIcon: falling back to GtkStatusIcon"); - tear_down_dbus (self); + remove_icon_path_from_bus (self); // Make sure there wasn't already one g_clear_object (&self->priv->gtk_status_icon); @@ -971,9 +1024,8 @@ if (found && export_icon_interface (self)) { - if (self->priv->owner_id > 0) + if (name_owner_id > 0) { - g_debug ("XAppStatusIcon: We already exist on the bus, syncing icon properties"); sync_skeleton (self); } else @@ -1065,21 +1117,15 @@ return; } - if (self->priv->connection) - { - complete_icon_setup (self); - return; - } - - use_gtk_status_icon (self); + complete_icon_setup (self); } static void refresh_icon (XAppStatusIcon *self) { - if (!self->priv->connection) - { - g_debug ("XAppStatusIcon: Trying to acquire session bus connection"); + if (self->priv->connection == NULL) + { + g_debug ("XAppStatusIcon: Connecting to session bus"); cancellable_reset (self); @@ -1111,6 +1157,18 @@ case PROP_ICON_SIZE: XAPP_STATUS_ICON (object)->priv->icon_size = CLAMP (g_value_get_int (value), 0, MAX_SANE_ICON_SIZE); break; + case PROP_NAME: + { + const gchar *name = g_value_get_string (value); + // Can't be null. We set to g_get_application_name() by default. + if (name == NULL || name[0] == '\0') + { + break; + } + } + + xapp_status_icon_set_name (XAPP_STATUS_ICON (object), g_value_get_string (value)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -1136,6 +1194,9 @@ case PROP_ICON_SIZE: g_value_set_int (value, icon->priv->icon_size); break; + case PROP_NAME: + g_value_set_string (value, icon->priv->name); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -1147,7 +1208,8 @@ { self->priv = xapp_status_icon_get_instance_private (self); - self->priv->name = g_strdup_printf("%s", g_get_application_name()); + self->priv->name = g_strdup (g_get_application_name()); + self->priv->state = XAPP_STATUS_ICON_STATE_NO_SUPPORT; self->priv->icon_size = FALLBACK_ICON_SIZE; self->priv->icon_name = g_strdup (" "); @@ -1161,25 +1223,22 @@ } static void -tear_down_dbus (XAppStatusIcon *self) +remove_icon_path_from_bus (XAppStatusIcon *self) { g_return_if_fail (XAPP_IS_STATUS_ICON (self)); - if (self->priv->owner_id > 0) - { - g_debug ("XAppStatusIcon: removing dbus presence (%p)", self); - - g_bus_unown_name(self->priv->owner_id); - self->priv->owner_id = 0; - } - - if (self->priv->skeleton) - { - g_debug ("XAppStatusIcon: removing dbus interface (%p)", self); - - g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (self->priv->skeleton)); - g_object_unref (self->priv->skeleton); - self->priv->skeleton = NULL; + if (self->priv->object_skeleton) + { + const gchar *path; + path = g_dbus_object_get_object_path (G_DBUS_OBJECT (self->priv->object_skeleton)); + + g_debug ("XAppStatusIcon: removing interface at path '%s'", path); + + g_dbus_object_manager_server_unexport (obj_server, path); + self->priv->interface_skeleton = NULL; + self->priv->object_skeleton = NULL; + + g_object_unref (obj_server); } } @@ -1209,11 +1268,12 @@ self->priv->gtk_status_icon = NULL; } - tear_down_dbus (self); + remove_icon_path_from_bus (self); if (self->priv->listener_id > 0) { g_dbus_connection_signal_unsubscribe (self->priv->connection, self->priv->listener_id); + self->priv->listener_id = 0; } g_clear_object (&self->priv->connection); @@ -1318,6 +1378,23 @@ G_PARAM_READWRITE)); /** + * XAppStatusIcon:name: + * + * The name of the icon for sorting purposes. If this is in the form of 'org.x.StatusIcon.foo` + * and set immediately upon creation of the icon, it will also attempt to own this dbus name; + * this can be useful in sandboxed environments where a well-defined name is required. If + * additional icons are created, only the name given to the initial one will be used for dbus, + * though different names can still affect the sort order. This is set to the value of + * g_get_application_name() if no other name is provided. + */ + g_object_class_install_property (gobject_class, PROP_NAME, + g_param_spec_string ("name", + "The name of the icon for sorting purposes.", + NULL, + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + /** * XAppStatusIcon::button-press-event: * @icon: The #XAppStatusIcon * @x: The absolute x position to use for menu positioning @@ -1430,14 +1507,23 @@ return; } + if (name == NULL || name[0] == '\0') + { + // name can't be null. We set to g_get_application_name() at startup, + // and the set_property handler silently ignores nulls, but if this + // is explicit, warn about it. + g_warning ("Can't set icon the name to null or empty string"); + return; + } + g_clear_pointer (&icon->priv->name, g_free); icon->priv->name = g_strdup (name); g_debug ("XAppStatusIcon set_name: %s", name); - if (icon->priv->skeleton) - { - xapp_status_icon_interface_set_name (icon->priv->skeleton, name); + if (icon->priv->interface_skeleton) + { + xapp_status_icon_interface_set_name (icon->priv->interface_skeleton, name); } /* Call this directly instead of in the update_fallback_icon() function, @@ -1474,9 +1560,9 @@ g_debug ("XAppStatusIcon set_icon_name: %s", icon_name); - if (icon->priv->skeleton) - { - xapp_status_icon_interface_set_icon_name (icon->priv->skeleton, icon_name); + if (icon->priv->interface_skeleton) + { + xapp_status_icon_interface_set_icon_name (icon->priv->interface_skeleton, icon_name); } update_fallback_icon (icon); @@ -1497,7 +1583,7 @@ { g_return_val_if_fail (XAPP_IS_STATUS_ICON (icon), FALLBACK_ICON_SIZE); - if (icon->priv->skeleton == NULL) + if (icon->priv->interface_skeleton == NULL) { g_debug ("XAppStatusIcon get_icon_size: %d (fallback)", FALLBACK_ICON_SIZE); @@ -1506,7 +1592,7 @@ gint size; - size = xapp_status_icon_interface_get_icon_size (icon->priv->skeleton); + size = xapp_status_icon_interface_get_icon_size (icon->priv->interface_skeleton); g_debug ("XAppStatusIcon get_icon_size: %d", size); @@ -1537,9 +1623,9 @@ g_debug ("XAppStatusIcon set_tooltip_text: %s", tooltip_text); - if (icon->priv->skeleton) - { - xapp_status_icon_interface_set_tooltip_text (icon->priv->skeleton, tooltip_text); + if (icon->priv->interface_skeleton) + { + xapp_status_icon_interface_set_tooltip_text (icon->priv->interface_skeleton, tooltip_text); } update_fallback_icon (icon); @@ -1569,9 +1655,9 @@ g_debug ("XAppStatusIcon set_label: '%s'", label); - if (icon->priv->skeleton) - { - xapp_status_icon_interface_set_label (icon->priv->skeleton, label); + if (icon->priv->interface_skeleton) + { + xapp_status_icon_interface_set_label (icon->priv->interface_skeleton, label); } } @@ -1598,9 +1684,9 @@ g_debug ("XAppStatusIcon set_visible: %s", visible ? "TRUE" : "FALSE"); - if (icon->priv->skeleton) - { - xapp_status_icon_interface_set_visible (icon->priv->skeleton, visible); + if (icon->priv->interface_skeleton) + { + xapp_status_icon_interface_set_visible (icon->priv->interface_skeleton, visible); } update_fallback_icon (icon); @@ -1783,6 +1869,23 @@ } /** + * xapp_status_icon_new_with_name: + * + * Creates a new #XAppStatusIcon instance and sets its name to %name. + * + * Returns: (transfer full): a new #XAppStatusIcon. Use g_object_unref when finished. + * + * Since: 1.6 + */ +XAppStatusIcon * +xapp_status_icon_new_with_name (const gchar *name) +{ + return g_object_new (XAPP_TYPE_STATUS_ICON, + "name", name, + NULL); +} + +/** * xapp_status_icon_get_state: * @icon: an #XAppStatusIcon * @@ -1835,9 +1938,9 @@ icon->priv->metadata = g_strdup (metadata); g_free (old_meta); - if (icon->priv->skeleton) - { - xapp_status_icon_interface_set_metadata (icon->priv->skeleton, metadata); + if (icon->priv->interface_skeleton) + { + xapp_status_icon_interface_set_metadata (icon->priv->interface_skeleton, metadata); } } diff --git a/libxapp/xapp-status-icon.h b/libxapp/xapp-status-icon.h index d76125a..4f1fa52 100644 --- a/libxapp/xapp-status-icon.h +++ b/libxapp/xapp-status-icon.h @@ -47,6 +47,7 @@ XAppStatusIcon *xapp_status_icon_new (void); +XAppStatusIcon *xapp_status_icon_new_with_name (const gchar *name); void xapp_status_icon_set_name (XAppStatusIcon *icon, const gchar *name); void xapp_status_icon_set_icon_name (XAppStatusIcon *icon, const gchar *icon_name); gint xapp_status_icon_get_icon_size (XAppStatusIcon *icon); diff --git a/meson-scripts/g-codegen.py b/meson-scripts/g-codegen.py index bf2ceb6..6d2894c 100755 --- a/meson-scripts/g-codegen.py +++ b/meson-scripts/g-codegen.py @@ -27,6 +27,7 @@ '--interface-prefix=' + sys.argv[1], '--generate-c-code=' + os.path.join(sys.argv[4], sys.argv[2]), '--c-namespace=XApp', + '--c-generate-object-manager', '--annotate', sys.argv[1], 'org.gtk.GDBus.C.Name', sys.argv[3], sys.argv[5] ]) diff --git a/meson.build b/meson.build index a2a414f..aab0afc 100644 --- a/meson.build +++ b/meson.build @@ -1,6 +1,6 @@ project('xapp', 'c', - version : '1.8.9' + version : '2.0.0' ) gnome = import('gnome') @@ -8,7 +8,7 @@ i18n = import('i18n') dbus_services_dir = dependency('dbus-1').get_pkgconfig_variable('session_bus_services_dir', - define_variable: ['prefix', get_option('prefix')]) + define_variable: ['datadir', get_option('datadir')]) libexec_path = join_paths(get_option('prefix'), get_option('libexecdir'), 'xapps', 'sn-watcher') cdata = configuration_data() @@ -50,6 +50,7 @@ subdir('icons') subdir('status-applets') subdir('scripts') + subdir('data') endif if get_option('status-notifier') and not app_lib_only diff --git a/schemas/org.x.apps.gschema.xml b/schemas/org.x.apps.gschema.xml index d213001..d2f91a6 100644 --- a/schemas/org.x.apps.gschema.xml +++ b/schemas/org.x.apps.gschema.xml @@ -2,6 +2,18 @@ + + + + + + [] + List of favorites, stored in display order, with the format of uri::mimetype + + + [] + List of gvfs metadata for the favorites:/// root (for remembering sort order in nemo, etc). + diff --git a/scripts/xfce4-set-wallpaper b/scripts/xfce4-set-wallpaper index 7a06802..52fa374 100755 --- a/scripts/xfce4-set-wallpaper +++ b/scripts/xfce4-set-wallpaper @@ -1,11 +1,12 @@ #!/bin/bash # Author: Weitian Leung -# Version: 2.0 +# Version: 2.1 # License: GPL-3.0 # Description: set a picture as xfce4 wallpaper -wallpaper=$1 +# xfce4-desktop requires an absolute path. +wallpaper="$(realpath "$1")" # check image mime_type=`file --mime-type -b "$wallpaper"` diff --git a/status-applets/mate/mate-xapp-status-applet.py b/status-applets/mate/mate-xapp-status-applet.py index b26d5f2..b23af1d 100755 --- a/status-applets/mate/mate-xapp-status-applet.py +++ b/status-applets/mate/mate-xapp-status-applet.py @@ -372,6 +372,13 @@ self.monitor.connect("icon-added", self.on_icon_added) self.monitor.connect("icon-removed", self.on_icon_removed) + def make_key(self, proxy): + name = proxy.get_name() + path = proxy.get_object_path() + + # print("Key: %s" % (name+path)) + return name + path + def destroy_monitor (self): for key in self.indicators.keys(): self.indicator_box.remove(self.indicators[key]) @@ -380,20 +387,20 @@ self.indicators = {} def on_icon_added(self, monitor, proxy): - name = proxy.get_name() - - self.indicators[name] = StatusWidget(proxy, self.applet.get_orient(), self.applet.get_size()) - self.indicator_box.add(self.indicators[name]) - self.indicators[name].connect("re-sort", self.sort_icons) + key = self.make_key(proxy) + + self.indicators[key] = StatusWidget(proxy, self.applet.get_orient(), self.applet.get_size()) + self.indicator_box.add(self.indicators[key]) + self.indicators[key].connect("re-sort", self.sort_icons) self.sort_icons() def on_icon_removed(self, monitor, proxy): - name = proxy.get_name() - - self.indicator_box.remove(self.indicators[name]) - self.indicators[name].disconnect_by_func(self.sort_icons) - del(self.indicators[name]) + key = self.make_key(proxy) + + self.indicator_box.remove(self.indicators[key]) + self.indicators[key].disconnect_by_func(self.sort_icons) + del(self.indicators[key]) self.sort_icons() @@ -441,7 +448,7 @@ # for i in icon_list: # print("before: ", i.proxy.props.icon_name, i.proxy.props.name.lower()) - icon_list.sort(key=lambda icon: icon.proxy.props.name.lower()) + icon_list.sort(key=lambda icon: icon.proxy.props.name.replace("org.x.StatusIcon.", "").lower()) icon_list.sort(key=lambda icon: icon.proxy.props.icon_name.lower().endswith("symbolic")) # for i in icon_list: diff --git a/test-scripts/xapp-favorites b/test-scripts/xapp-favorites new file mode 100755 index 0000000..01a9e53 --- /dev/null +++ b/test-scripts/xapp-favorites @@ -0,0 +1,159 @@ +#!/usr/bin/python3 +import gi +gi.require_version('Gtk', '3.0') +gi.require_version('XApp', '1.0') +from gi.repository import Gio, GLib, GObject, Gtk, XApp, Pango +import sys +import signal + +signal.signal(signal.SIGINT, signal.SIG_DFL) + + +DBUS_NAME = "org.x.StatusIcon" +DBUS_PATH = "/org/x/StatusIcon" + +class ListItem(Gtk.ListBoxRow): + def __init__(self, favinfo, mgr): + super(Gtk.ListBoxRow, self).__init__() + self.favinfo = favinfo + self.mgr = mgr + + self.box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) + self.add(self.box) + + label = Gtk.Label(label=self.favinfo.display_name, xalign=0) + self.box.pack_start(label, False, False, 6) + + delete = Gtk.Button.new_from_icon_name("edit-delete-symbolic", Gtk.IconSize.BUTTON) + delete.set_relief(Gtk.ReliefStyle.NONE) + self.box.pack_end(delete, False, False, 6) + delete.connect("clicked", self.on_delete_clicked) + + self.show_all() + + def on_delete_clicked(self, widget, data=None): + self.mgr.remove(self.favinfo.uri) + +class FavoriteList(GObject.Object): + + def __init__(self): + super(FavoriteList, self).__init__() + self.window = None + self.manager = XApp.Favorites.get_default() + self.manager.connect("changed", self.on_manager_changed) + + self.window = Gtk.Window() + self.window.set_default_size(600, 400) + self.window.connect("destroy", self.on_window_destroy) + self.window_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) + self.main_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, + margin=6, + spacing=0) + + bar = Gtk.MenuBar() + menu_item = Gtk.MenuItem.new_with_label("Favorites") + favorites = self.manager.create_menu(None, self.favorite_menu_item_activated) + menu_item.set_submenu(favorites) + bar.append(menu_item) + + self.window_box.pack_start(bar, False, False, 0) + self.window_box.pack_start(self.main_box, True, True, 0) + + # list stuff + sw_frame = Gtk.Frame() + sw = Gtk.ScrolledWindow(hadjustment=None, vadjustment=None) + sw_frame.add(sw) + + self.list_box = Gtk.ListBox(activate_on_single_click=False) + self.list_box.connect("selected-rows-changed", self.on_selection_changed) + self.list_box.connect("row-activated", self.on_row_activated) + sw.add(self.list_box) + + self.main_box.pack_start(sw_frame, True, True, 6) + self.window.add(self.window_box) + + # controls + + control_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) + self.add_button = Gtk.Button.new_from_icon_name("list-add-symbolic", Gtk.IconSize.BUTTON) + self.add_button.connect("clicked", self.on_add_clicked) + + control_box.pack_start(self.add_button, False, False, 6) + + self.main_box.pack_start(control_box, False, False, 6) + + self.window.show_all() + + self.selected_uri = None + self.loading = False + + self.load_favorites() + + def favorite_menu_item_activated(self, manager, uri, data=None): + print("activated", uri) + self.manager.launch(uri, Gtk.get_current_event_time()) + + def load_favorites(self): + self.loading = True # this will preserve the last selection + + for child in self.list_box.get_children(): + self.list_box.remove(child) + + previously_selected = None + + favorites = self.manager.get_favorites(None) + + for info in favorites: + row = ListItem(info, self.manager) + + self.list_box.insert(row, -1) + + if self.selected_uri == info.uri: + previously_selected = row + + self.loading = False + + if previously_selected: + self.list_box.select_row(previously_selected) + + def on_row_activated(self, box, row, data=None): + self.manager.launch(row.favinfo.uri, Gtk.get_current_event_time()) + + def on_selection_changed(self, box, data=None): + if self.loading: + return + + row = self.list_box.get_selected_row() + + if row: + self.selected_uri = row.favinfo.uri + else: + self.selected_uri = None + + def on_add_clicked(self, widget, data=None): + dialog = Gtk.FileChooserDialog(title="Add file to favorites", + parent=self.window, + action=Gtk.FileChooserAction.OPEN) + + dialog.add_buttons("Add", Gtk.ResponseType.OK, + "Cancel", Gtk.ResponseType.CANCEL) + + # dialog.add_shortcut_folder_uri ("favorites:///") + + res = dialog.run() + if res == Gtk.ResponseType.OK: + uri = dialog.get_uri() + + self.manager.add(uri) + + dialog.destroy() + + def on_manager_changed(self, manager, data=None): + self.load_favorites() + + def on_window_destroy(self, widget, data=None): + Gtk.main_quit() + +if __name__ == '__main__': + test = FavoriteList() + Gtk.main() diff --git a/test-scripts/xapp-status-applet b/test-scripts/xapp-status-applet index e5f6d2a..1cf488d 100755 --- a/test-scripts/xapp-status-applet +++ b/test-scripts/xapp-status-applet @@ -167,7 +167,7 @@ self.disco_button.set_label("Connect monitor") def on_icon_added(self, monitor, proxy): - name = proxy.get_name() + name = proxy.get_name() + proxy.get_object_path() self.indicators[name] = StatusWidget(proxy) self.indicator_box.add(self.indicators[name]) @@ -176,7 +176,7 @@ self.sort_icons() def on_icon_removed(self, monitor, proxy): - name = proxy.get_name() + name = proxy.get_name() + proxy.get_object_path() self.indicator_box.remove(self.indicators[name]) self.indicators[name].disconnect_by_func(self.sort_icons) @@ -190,7 +190,7 @@ # for i in icon_list: # print("before: ", i.proxy.props.icon_name, i.proxy.props.name.lower()) - icon_list.sort(key=lambda icon: icon.proxy.props.name.lower()) + icon_list.sort(key=lambda icon: icon.proxy.props.name.replace("org.x.StatusIcon.", "").lower()) icon_list.sort(key=lambda icon: icon.proxy.props.icon_name.lower().endswith("symbolic")) # for i in icon_list: diff --git a/xapp-sn-watcher/sn-item.c b/xapp-sn-watcher/sn-item.c index d5f8def..ca6d1c5 100644 --- a/xapp-sn-watcher/sn-item.c +++ b/xapp-sn-watcher/sn-item.c @@ -393,6 +393,11 @@ { surface = new_props->attention_icon_surface; } + else + if (new_props->icon_surface) + { + surface = new_props->icon_surface; + } } if (surface != NULL) @@ -423,9 +428,9 @@ } static gchar * -get_icon_filename_from_theme (SnItem *item, - const gchar *theme_path, - const gchar *icon_name) +get_name_or_path_from_theme (SnItem *item, + const gchar *theme_path, + const gchar *icon_name) { GtkIconInfo *info; gchar *filename; @@ -435,7 +440,7 @@ array[0] = icon_name; array[1] = NULL; - // We have a theme path, but try the system theme first + // We may have a theme path, but try the system theme first GtkIconTheme *theme = gtk_icon_theme_get_default (); host_icon_size = get_icon_size (item); @@ -445,7 +450,15 @@ lookup_ui_scale (), GTK_ICON_LOOKUP_FORCE_SVG | GTK_ICON_LOOKUP_FORCE_SYMBOLIC); - if (info == NULL) + if (info != NULL) + { + // If the icon is found in the system theme, we can just pass along the icon name + // as is, this way symbolics work properly. + g_object_unref (info); + return g_strdup (icon_name); + } + + if (theme_path != NULL) { // Make a temp theme based off of the provided path GtkIconTheme *theme = gtk_icon_theme_new (); @@ -477,8 +490,10 @@ const gchar *icon_theme_path, const gchar *icon_name) { - g_debug ("Checking for icon name for %s", - item->sortable_name); + g_debug ("Checking for icon name for %s - theme path: '%s', icon name: '%s'", + item->sortable_name, + icon_theme_path, + icon_name); if (icon_name == NULL) { @@ -488,17 +503,16 @@ if (g_path_is_absolute (icon_name)) { xapp_status_icon_set_icon_name (item->status_icon, icon_name); - return TRUE; } else { - gchar *filename = get_icon_filename_from_theme (item, icon_theme_path, icon_name); - - if (filename != NULL) - { - xapp_status_icon_set_icon_name (item->status_icon, filename); - g_free (filename); + gchar *used_name = get_name_or_path_from_theme (item, icon_theme_path, icon_name); + + if (used_name != NULL) + { + xapp_status_icon_set_icon_name (item->status_icon, used_name); + g_free (used_name); return TRUE; } } @@ -512,7 +526,10 @@ { const gchar *name_to_use = NULL; - if (item->status == STATUS_ACTIVE) + // Set an icon here, even if we're passive (hidden) - eventually only the + // status property might change, but we wouldn't have an icon then (unless + // the app sets the icon at the same time). + if (item->status == STATUS_ACTIVE || item->status == STATUS_PASSIVE) { if (new_props->icon_name) { @@ -717,7 +734,7 @@ { if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { - g_critical ("Could get propertyies for %s: %s\n", + g_critical ("Could not get properties for %s: %s\n", g_dbus_proxy_get_name (item->sn_item_proxy), error->message); } @@ -888,7 +905,7 @@ update_menu (item, new_props); } - if (new_props->update_icon) + if (new_props->update_icon || new_props->update_status) { update_icon (item, new_props); } diff --git a/xapp-sn-watcher/xapp-sn-watcher.c b/xapp-sn-watcher/xapp-sn-watcher.c index 3d25ef6..802bd8c 100644 --- a/xapp-sn-watcher/xapp-sn-watcher.c +++ b/xapp-sn-watcher/xapp-sn-watcher.c @@ -308,6 +308,18 @@ } NewSnProxyData; static void +free_sn_proxy_data (NewSnProxyData *data) +{ + g_free (data->key); + g_free (data->path); + g_free (data->bus_name); + g_free (data->service); + g_object_unref (data->invocation); + + g_slice_free (NewSnProxyData, data); +} + +static void sn_item_proxy_new_completed (GObject *source, GAsyncResult *res, gpointer user_data) @@ -322,6 +334,24 @@ proxy = sn_item_interface_proxy_new_finish (res, &error); + g_hash_table_steal_extended (watcher->items, + data->key, + &stolen_ptr, + NULL); + + if ((gchar *) stolen_ptr == NULL) + { + g_dbus_method_invocation_return_error (data->invocation, + G_DBUS_ERROR, + G_DBUS_ERROR_FAILED, + "New StatusNotifierItem disappeared before " + "its registration was complete."); + + free_sn_proxy_data (data); + g_clear_object (&proxy); + return; + } + if (error != NULL) { if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) @@ -330,11 +360,8 @@ data->bus_name, error->message); } - g_hash_table_steal_extended (watcher->items, - data->key, - &stolen_ptr, - NULL); g_free (stolen_ptr); + free_sn_proxy_data (data); g_dbus_method_invocation_take_error (data->invocation, error); return; @@ -342,11 +369,6 @@ item = sn_item_new ((GDBusProxy *) proxy, g_str_has_prefix (data->path, APPINDICATOR_PATH_PREFIX)); - - g_hash_table_steal_extended (watcher->items, - data->key, - &stolen_ptr, - NULL); g_hash_table_insert (watcher->items, stolen_ptr, @@ -360,12 +382,7 @@ sn_watcher_interface_complete_register_status_notifier_item (watcher->skeleton, data->invocation); - g_free (data->key); - g_free (data->path); - g_free (data->bus_name); - g_free (data->service); - g_object_unref (data->invocation); - g_slice_free (NewSnProxyData, data); + free_sn_proxy_data (data); } static gboolean @@ -494,6 +511,26 @@ } static void +unref_proxy (gpointer data) +{ + // if g_hash_table_remove is called from handle_sn_item_name_owner_lost + // *before* sn_item_proxy_new_completed is complete, the key will be + // pointing to a NULL value, so avoid trying to free it. + + if (data == NULL) + { + return; + } + + SnItem *item = SN_ITEM (data); + + if (item) + { + g_object_unref (item); + } +} + +static void watcher_startup (GApplication *application) { XAppSnWatcher *watcher = (XAppSnWatcher*) application; @@ -504,7 +541,7 @@ xapp_settings = g_settings_new (STATUS_ICON_SCHEMA); watcher->items = g_hash_table_new_full (g_str_hash, g_str_equal, - g_free, g_object_unref); + g_free, (GDestroyNotify) unref_proxy); /* This buys us 30 seconds (gapp timeout) - we'll either be re-held immediately * because there's a monitor or exit after the 30 seconds. */ diff --git a/xapp.pot b/xapp.pot index 4868479..791d7eb 100644 --- a/xapp.pot +++ b/xapp.pot @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-04-23 15:54+0100\n" +"POT-Creation-Date: 2020-11-25 11:16+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -16,6 +16,19 @@ "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=CHARSET\n" "Content-Transfer-Encoding: 8bit\n" + +#: libxapp/favorite-vfs-file.c:463 +msgid "Favorites" +msgstr "" + +#: libxapp/favorite-vfs-file.c:933 libxapp/favorite-vfs-file.c:963 +#: libxapp/favorite-vfs-file.c:992 libxapp/favorite-vfs-file.c:1026 +#: libxapp/favorite-vfs-file.c:1053 libxapp/favorite-vfs-file.c:1082 +#: libxapp/favorite-vfs-file.c:1115 libxapp/favorite-vfs-file.c:1142 +#: libxapp/favorite-vfs-file.c:1204 libxapp/favorite-vfs-file.c:1239 +#: libxapp/favorite-vfs-file.c:1269 libxapp/favorite-vfs-file.c:1308 +msgid "Operation not supported" +msgstr "" #: libxapp/xapp-icon-chooser-button.c:207 #: libxapp/xapp-icon-chooser-dialog.c:639 @@ -140,11 +153,11 @@ msgid "Image" msgstr "" -#: status-applets/mate/mate-xapp-status-applet.py:295 +#: status-applets/mate/mate-xapp-status-applet.py:330 msgid "About" msgstr "" -#: status-applets/mate/mate-xapp-status-applet.py:315 +#: status-applets/mate/mate-xapp-status-applet.py:350 #: status-applets/mate/org.x.MateXAppStatusApplet.mate-panel-applet.in.in:7 #: status-applets/mate/org.x.MateXAppStatusApplet.mate-panel-applet.in.in:11 msgid "Area where XApp status icons appear"