diff --git a/gio/gappinfo.c b/gio/gappinfo.c index 2d0b58cb2..b02b64af7 100644 --- a/gio/gappinfo.c +++ b/gio/gappinfo.c @@ -21,7 +21,10 @@ */ #include "config.h" + #include "gappinfo.h" +#include "gappinfoprivate.h" + #include "glibintl.h" #include #include @@ -1025,3 +1028,192 @@ g_app_launch_context_launch_failed (GAppLaunchContext *context, g_signal_emit (context, signals[LAUNCH_FAILED], 0, startup_notify_id); } + + +/* GAppInfoMonitor: + * + * We have one of each of these per main context and hand them out + * according to the thread default main context at the time of the call + * to g_app_info_monitor_get(). + * + * g_object_unref() is only ever called from the same context, so we + * effectively have a single-threaded scenario for each GAppInfoMonitor. + * + * We use a hashtable to cache the per-context monitor (but we do not + * hold a ref). During finalize, we remove it. This is possible + * because we don't have to worry about the usual races due to the + * single-threaded nature of each object. + * + * We keep a global list of all contexts that have a monitor for them, + * which we have to access under a lock. When we dispatch the events to + * be handled in each context, we don't pass the monitor, but the + * context itself. + * + * We dispatch from the GLib worker context, so if we passed the + * monitor, we would need to take a ref on it (in case it was destroyed + * in its own thread meanwhile). The monitor holds a ref on a context + * and the dispatch would mean that the context would hold a ref on the + * monitor. If someone stopped iterating the context at just this + * moment both the context and monitor would leak. + * + * Instead, we dispatch the context to itself. We don't hold a ref. + * There is the danger that the context will be destroyed during the + * dispatch, but if that is the case then we just won't receive our + * callback. + * + * When the dispatch occurs we just lookup the monitor in the hashtable, + * by context. We can now add and remove refs, since the context will + * have been acquired. + */ + +typedef struct _GAppInfoMonitorClass GAppInfoMonitorClass; + +struct _GAppInfoMonitor +{ + GObject parent_instance; + GMainContext *context; +}; + +struct _GAppInfoMonitorClass +{ + GObjectClass parent_class; +}; + +static GHashTable *g_app_info_monitors; +static GMutex g_app_info_monitor_lock; +static guint g_app_info_monitor_changed_signal; + +G_DEFINE_TYPE (GAppInfoMonitor, g_app_info_monitor, G_TYPE_OBJECT) + +static void +g_app_info_monitor_finalize (GObject *object) +{ + GAppInfoMonitor *monitor = G_APP_INFO_MONITOR (object); + + g_mutex_lock (&g_app_info_monitor_lock); + g_hash_table_remove (g_app_info_monitors, monitor->context); + g_mutex_unlock (&g_app_info_monitor_lock); + + G_OBJECT_CLASS (g_app_info_monitor_parent_class)->finalize (object); +} + +static void +g_app_info_monitor_init (GAppInfoMonitor *monitor) +{ +} + +static void +g_app_info_monitor_class_init (GAppInfoMonitorClass *class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (class); + + g_app_info_monitor_changed_signal = g_signal_new ("changed", G_TYPE_APP_INFO_MONITOR, G_SIGNAL_RUN_FIRST, + 0, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); + + object_class->finalize = g_app_info_monitor_finalize; +} + +/** + * g_app_info_monitor_get: + * + * Gets the #GAppInfoMonitor for the current thread-default main + * context. + * + * The #GAppInfoMonitor will emit a "changed" signal in the + * thread-default main context whenever the list of installed + * applications (as reported by g_app_info_get_all()) may have changed. + * + * You must only call g_object_unref() on the return value from under + * the same main context as you created it. + * + * Returns: (transfer full): a reference to a #GAppInfoMonitor + * + * Since: 2.40 + **/ +GAppInfoMonitor * +g_app_info_monitor_get (void) +{ + GAppInfoMonitor *monitor; + GMainContext *context; + + context = g_main_context_get_thread_default (); + if (!context) + context = g_main_context_default (); + + g_return_val_if_fail (g_main_context_acquire (context), NULL); + + g_mutex_lock (&g_app_info_monitor_lock); + if (!g_app_info_monitors) + g_app_info_monitors = g_hash_table_new (NULL, NULL); + + monitor = g_hash_table_lookup (g_app_info_monitors, context); + g_mutex_unlock (&g_app_info_monitor_lock); + + if (!monitor) + { + monitor = g_object_new (G_TYPE_APP_INFO_MONITOR, NULL); + monitor->context = g_main_context_ref (context); + + g_mutex_lock (&g_app_info_monitor_lock); + g_hash_table_insert (g_app_info_monitors, context, monitor); + g_mutex_unlock (&g_app_info_monitor_lock); + } + else + g_object_ref (monitor); + + g_main_context_release (context); + + return monitor; +} + +static gboolean +g_app_info_monitor_emit (gpointer user_data) +{ + GMainContext *context = user_data; + GAppInfoMonitor *monitor; + + g_mutex_lock (&g_app_info_monitor_lock); + monitor = g_hash_table_lookup (g_app_info_monitors, context); + g_mutex_unlock (&g_app_info_monitor_lock); + + /* It is possible that the monitor was already destroyed by the time + * we get here, so make sure it's not NULL. + */ + if (monitor != NULL) + { + /* We don't have to worry about another thread disposing the + * monitor but we do have to worry about the possibility that one + * of the attached handlers may do so. + * + * Take a ref so that the monitor doesn't disappear in the middle + * of the emission. + */ + g_object_ref (monitor); + g_signal_emit (monitor, g_app_info_monitor_changed_signal, 0); + g_object_unref (monitor); + } + + return FALSE; +} + +void +g_app_info_monitor_fire (void) +{ + GHashTableIter iter; + gpointer context; + + g_mutex_lock (&g_app_info_monitor_lock); + + g_hash_table_iter_init (&iter, g_app_info_monitors); + while (g_hash_table_iter_next (&iter, &context, NULL)) + { + GSource *idle; + + idle = g_idle_source_new (); + g_source_set_callback (idle, g_app_info_monitor_emit, context, NULL); + g_source_attach (idle, context); + g_source_unref (idle); + } + + g_mutex_unlock (&g_app_info_monitor_lock); +} diff --git a/gio/gappinfo.h b/gio/gappinfo.h index baed4c405..8b793cefb 100644 --- a/gio/gappinfo.h +++ b/gio/gappinfo.h @@ -296,6 +296,20 @@ GLIB_AVAILABLE_IN_ALL void g_app_launch_context_launch_failed (GAppLaunchContext *context, const char * startup_notify_id); +#define G_TYPE_APP_INFO_MONITOR (g_app_info_monitor_get_type ()) +#define G_APP_INFO_MONITOR(inst) (G_TYPE_CHECK_INSTANCE_CAST ((inst), \ + G_TYPE_APP_INFO_MONITOR, GAppInfoMonitor)) +#define G_IS_APP_INFO_MONITOR(inst) (G_TYPE_CHECK_INSTANCE_TYPE ((inst), \ + G_TYPE_APP_INFO_MONITOR)) + +typedef struct _GAppInfoMonitor GAppInfoMonitor; + +GLIB_AVAILABLE_IN_2_40 +GType g_app_info_monitor_get_type (void); + +GLIB_AVAILABLE_IN_2_40 +GAppInfoMonitor * g_app_info_monitor_get (void); + G_END_DECLS #endif /* __G_APP_INFO_H__ */ diff --git a/gio/gappinfoprivate.h b/gio/gappinfoprivate.h new file mode 100644 index 000000000..c9879777c --- /dev/null +++ b/gio/gappinfoprivate.h @@ -0,0 +1,28 @@ +/* GIO - GLib Input, Output and Streaming Library + * + * Copyright © 2013 Canonical Limited + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Ryan Lortie + */ + +#ifndef __G_APP_INFO_PRIVATE_H__ +#define __G_APP_INFO_PRIVATE_H__ + +void g_app_info_monitor_fire (void); + +#endif /* __G_APP_INFO_PRIVATE_H__ */ diff --git a/gio/gdesktopappinfo.c b/gio/gdesktopappinfo.c index 95759e259..5a21a742a 100644 --- a/gio/gdesktopappinfo.c +++ b/gio/gdesktopappinfo.c @@ -48,6 +48,7 @@ #include "glibintl.h" #include "giomodule-priv.h" #include "gappinfo.h" +#include "gappinfoprivate.h" #include "glocaldirectorymonitor.h" #include "dfi-reader.h" @@ -330,6 +331,9 @@ desktop_file_dir_changed (GFileMonitor *monitor, desktop_file_dir_reset (dir); g_mutex_unlock (&desktop_file_dir_lock); + + /* Notify anyone else who may be interested */ + g_app_info_monitor_fire (); } /* Internal utility functions {{{2 */