diff --git a/gio/gappinfo.c b/gio/gappinfo.c index 2a8ca8481..42feb9d7c 100644 --- a/gio/gappinfo.c +++ b/gio/gappinfo.c @@ -1658,19 +1658,34 @@ g_app_launch_context_launch_failed (GAppLaunchContext *context, * @short_description: Monitor application information for changes * * #GAppInfoMonitor is a very simple object used for monitoring the app - * info database for changes (ie: newly installed or removed - * applications). + * info database for changes (newly installed or removed applications). * * Call g_app_info_monitor_get() to get a #GAppInfoMonitor and connect - * to the "changed" signal. + * to the #GAppInfoMonitor::changed signal. The signal will be emitted once when + * the app info database changes, and will not be emitted again until after the + * next call to g_app_info_get_all() or another `g_app_info_*()` function. This + * is because monitoring the app info database for changes is expensive. + * + * The following functions will re-arm the #GAppInfoMonitor::changed signal so + * it can be emitted again: + * - g_app_info_get_all() + * - g_app_info_get_all_for_type() + * - g_app_info_get_default_for_type() + * - g_app_info_get_fallback_for_type() + * - g_app_info_get_recommended_for_type() + * - g_desktop_app_info_get_implementations() + * - g_desktop_app_info_new() + * - g_desktop_app_info_new_from_filename() + * - g_desktop_app_info_new_from_keyfile() + * - g_desktop_app_info_search() * * In the usual case, applications should try to make note of the change * (doing things like invalidating caches) but not act on it. In * particular, applications should avoid making calls to #GAppInfo APIs * in response to the change signal, deferring these until the time that - * the data is actually required. The exception to this case is when + * the updated data is actually required. The exception to this case is when * application information is actually being displayed on the screen - * (eg: during a search or when the list of all applications is shown). + * (for example, during a search or when the list of all applications is shown). * The reason for this is that changes to the list of installed * applications often come in groups (like during system updates) and * rescanning the list on every change is pointless and expensive. @@ -1728,8 +1743,10 @@ g_app_info_monitor_class_init (GAppInfoMonitorClass *class) /** * GAppInfoMonitor::changed: * - * Signal emitted when the app info database for changes (ie: newly installed - * or removed applications). + * Signal emitted when the app info database changes, when applications are + * installed or removed. + * + * Since: 2.40 **/ g_app_info_monitor_changed_signal = g_signal_new (I_("changed"), G_TYPE_APP_INFO_MONITOR, G_SIGNAL_RUN_FIRST, 0, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); @@ -1747,6 +1764,10 @@ g_app_info_monitor_class_init (GAppInfoMonitorClass *class) * thread-default main context whenever the list of installed * applications (as reported by g_app_info_get_all()) may have changed. * + * The #GAppInfoMonitor::changed signal will only be emitted once until + * g_app_info_get_all() (or another `g_app_info_*()` function) is called. Doing + * so will re-arm the signal ready to notify about the next change. + * * You must only call g_object_unref() on the return value from under * the same main context as you created it. * diff --git a/gio/tests/appmonitor.c b/gio/tests/appmonitor.c index 9db8c4dea..c1d68b889 100644 --- a/gio/tests/appmonitor.c +++ b/gio/tests/appmonitor.c @@ -1,6 +1,31 @@ +/* GLib testing framework examples and tests + * + * Copyright © 2013 Red Hat, Inc. + * Copyright © 2015, 2017, 2018 Endless Mobile, Inc. + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * 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.1 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, see . + */ + #include #include +#ifdef G_OS_UNIX +#include +#endif + typedef struct { gchar *applications_dir; @@ -24,6 +49,7 @@ teardown (Fixture *fixture, g_clear_pointer (&fixture->applications_dir, g_free); } +#ifdef G_OS_UNIX static gboolean create_app (gpointer data) { @@ -50,73 +76,95 @@ delete_app (gpointer data) g_remove (path); } -static gboolean changed_fired; - static void -changed_cb (GAppInfoMonitor *monitor, GMainLoop *loop) +changed_cb (GAppInfoMonitor *monitor, + gpointer user_data) { - changed_fired = TRUE; - g_main_loop_quit (loop); + gboolean *changed_fired = user_data; + + *changed_fired = TRUE; + g_main_context_wakeup (g_main_context_get_thread_default ()); } static gboolean -quit_loop (gpointer data) +timeout_cb (gpointer user_data) { - GMainLoop *loop = data; + gboolean *timed_out = user_data; - if (g_main_loop_is_running (loop)) - g_main_loop_quit (loop); + g_assert (!timed_out); + *timed_out = TRUE; + g_main_context_wakeup (g_main_context_get_thread_default ()); return G_SOURCE_REMOVE; } +#endif /* G_OS_UNIX */ static void test_app_monitor (Fixture *fixture, gconstpointer user_data) { +#ifdef G_OS_UNIX gchar *app_path; GAppInfoMonitor *monitor; - GMainLoop *loop; - -#ifdef G_OS_WIN32 - g_test_skip (".desktop monitor on win32"); - return; -#endif + GMainContext *context = NULL; /* use the global default main context */ + GSource *timeout_source = NULL; + GDesktopAppInfo *app = NULL; + gboolean changed_fired = FALSE; + gboolean timed_out = FALSE; app_path = g_build_filename (fixture->applications_dir, "app.desktop", NULL); /* FIXME: this shouldn't be required */ g_list_free_full (g_app_info_get_all (), g_object_unref); + /* Create an app monitor and check that its ::changed signal is emitted when + * a new app is installed. */ monitor = g_app_info_monitor_get (); - loop = g_main_loop_new (NULL, FALSE); - g_signal_connect (monitor, "changed", G_CALLBACK (changed_cb), loop); + g_signal_connect (monitor, "changed", G_CALLBACK (changed_cb), &changed_fired); g_idle_add (create_app, app_path); - g_timeout_add_seconds (3, quit_loop, loop); + timeout_source = g_timeout_source_new_seconds (3); + g_source_set_callback (timeout_source, timeout_cb, &timed_out, NULL); + g_source_attach (timeout_source, NULL); - g_main_loop_run (loop); - g_assert (changed_fired); + while (!changed_fired && !timed_out) + g_main_context_iteration (context, TRUE); + + g_assert_true (changed_fired); changed_fired = FALSE; - /* FIXME: this shouldn't be required */ - g_list_free_full (g_app_info_get_all (), g_object_unref); + g_source_destroy (timeout_source); + g_clear_pointer (&timeout_source, g_source_unref); - g_timeout_add_seconds (3, quit_loop, loop); + /* Check that the app is now queryable. This has the side-effect of re-arming + * the #GAppInfoMonitor::changed signal for the next part of the test. */ + app = g_desktop_app_info_new ("app.desktop"); + g_assert_nonnull (app); + g_clear_object (&app); + + /* Now check that ::changed is emitted when an app is uninstalled. */ + timeout_source = g_timeout_source_new_seconds (3); + g_source_set_callback (timeout_source, timeout_cb, &timed_out, NULL); + g_source_attach (timeout_source, NULL); delete_app (app_path); - g_main_loop_run (loop); + while (!changed_fired && !timed_out) + g_main_context_iteration (context, TRUE); - g_assert (changed_fired); + g_assert_true (changed_fired); - g_main_loop_unref (loop); + g_source_destroy (timeout_source); + g_clear_pointer (&timeout_source, g_source_unref); g_remove (app_path); g_object_unref (monitor); g_free (app_path); +#else /* if !G_OS_UNIX */ + g_test_skip (".desktop monitor on win32"); +#endif /* !G_OS_UNIX */ } int