/* GDBus - GLib D-Bus Library * * Copyright (C) 2008-2010 Red Hat, Inc. * * 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: David Zeuthen */ #include "config.h" #include #include "gdbusutils.h" #include "gdbusconnection.h" #include "gdbusnamewatching.h" #include "gdbusproxywatching.h" #include "gdbuserror.h" #include "gdbusprivate.h" #include "gdbusproxy.h" #include "gcancellable.h" #include "glibintl.h" #include "gioalias.h" /** * SECTION:gdbusproxywatching * @title: Watching Proxies * @short_description: Simple API for watching proxies * @include: gio/gio.h * * Convenience API for watching bus proxies. * * Simple application watching a proxyFIXME: MISSING XINCLUDE CONTENT */ /* ---------------------------------------------------------------------------------------------------- */ G_LOCK_DEFINE_STATIC (lock); static guint next_global_id = 1; static GHashTable *map_id_to_client = NULL; /* ---------------------------------------------------------------------------------------------------- */ typedef struct { guint id; GBusProxyAppearedCallback proxy_appeared_handler; GBusProxyVanishedCallback proxy_vanished_handler; gpointer user_data; GDestroyNotify user_data_free_func; GMainContext *main_context; gchar *name; gchar *name_owner; GDBusConnection *connection; guint name_watcher_id; GCancellable *cancellable; gchar *object_path; gchar *interface_name; GType interface_type; GDBusProxyFlags proxy_flags; GDBusProxy *proxy; gboolean initial_construction; } Client; static void client_unref (Client *client) { /* ensure we're only called from g_bus_unwatch_proxy */ g_assert (client->name_watcher_id == 0); g_free (client->name_owner); if (client->connection != NULL) g_object_unref (client->connection); if (client->proxy != NULL) g_object_unref (client->proxy); g_free (client->name); g_free (client->object_path); g_free (client->interface_name); if (client->main_context != NULL) g_main_context_unref (client->main_context); if (client->user_data_free_func != NULL) client->user_data_free_func (client->user_data); g_free (client); } /* ---------------------------------------------------------------------------------------------------- */ static void proxy_constructed_cb (GObject *source_object, GAsyncResult *res, gpointer user_data) { Client *client = user_data; GDBusProxy *proxy; GError *error; error = NULL; proxy = g_dbus_proxy_new_finish (res, &error); if (proxy == NULL) { /* g_warning ("error while constructing proxy: %s", error->message); */ g_error_free (error); /* handle initial construction, send out vanished if the name * is there but we constructing a proxy fails */ if (client->initial_construction) { if (client->proxy_vanished_handler != NULL) { client->proxy_vanished_handler (client->connection, client->name, client->user_data); } client->initial_construction = FALSE; } } else { g_assert (client->proxy == NULL); g_assert (client->cancellable != NULL); client->proxy = G_DBUS_PROXY (proxy); g_object_unref (client->cancellable); client->cancellable = NULL; /* perform callback */ if (client->proxy_appeared_handler != NULL) { client->proxy_appeared_handler (client->connection, client->name, client->name_owner, client->proxy, client->user_data); } client->initial_construction = FALSE; } } static void on_name_appeared (GDBusConnection *connection, const gchar *name, const gchar *name_owner, gpointer user_data) { Client *client = user_data; //g_debug ("\n\nname appeared (owner `%s')", name_owner); /* invariants */ g_assert (client->name_owner == NULL); g_assert (client->connection == NULL); g_assert (client->cancellable == NULL); client->name_owner = g_strdup (name_owner); client->connection = g_object_ref (connection); client->cancellable = g_cancellable_new (); g_dbus_proxy_new (client->connection, client->interface_type, client->proxy_flags, NULL, /* GDBusInterfaceInfo */ client->name_owner, client->object_path, client->interface_name, client->cancellable, proxy_constructed_cb, client); } static void on_name_vanished (GDBusConnection *connection, const gchar *name, gpointer user_data) { Client *client = user_data; /*g_debug ("\n\nname vanished");*/ g_free (client->name_owner); if (client->connection != NULL) g_object_unref (client->connection); client->name_owner = NULL; client->connection = NULL; /* free the proxy if we have it */ if (client->proxy != NULL) { g_assert (client->cancellable == NULL); g_object_unref (client->proxy); client->proxy = NULL; /* if we have the proxy, it means we last sent out a 'appeared' * callback - so send out a 'vanished' callback */ if (client->proxy_vanished_handler != NULL) { client->proxy_vanished_handler (client->connection, client->name, client->user_data); } client->initial_construction = FALSE; } else { /* otherwise cancel construction of the proxy if applicable */ if (client->cancellable != NULL) { g_cancellable_cancel (client->cancellable); g_object_unref (client->cancellable); client->cancellable = NULL; } else { /* handle initial construction, send out vanished if * the name isn't there */ if (client->initial_construction) { if (client->proxy_vanished_handler != NULL) { client->proxy_vanished_handler (client->connection, client->name, client->user_data); } client->initial_construction = FALSE; } } } } /** * g_bus_watch_proxy: * @bus_type: The type of bus to watch a name on. * @name: The name (well-known or unique) to watch. * @flags: Flags from the #GBusNameWatcherFlags enumeration. * @object_path: The object path of the remote object to watch. * @interface_name: The D-Bus interface name for the proxy. * @interface_type: The #GType for the kind of proxy to create. This must be a #GDBusProxy derived type. * @proxy_flags: Flags from #GDBusProxyFlags to use when constructing the proxy. * @proxy_appeared_handler: Handler to invoke when @name is known to exist and the * requested proxy is available. * @proxy_vanished_handler: Handler to invoke when @name is known to not exist * and the previously created proxy is no longer available. * @user_data: User data to pass to handlers. * @user_data_free_func: Function for freeing @user_data or %NULL. * * Starts watching a remote object at @object_path owned by @name on * the bus specified by @bus_type. When the object is available, a * #GDBusProxy (or derived class cf. @interface_type) instance is * constructed for the @interface_name D-Bus interface and then * @proxy_appeared_handler will be called when the proxy is ready and * all properties have been loaded. When @name vanishes, * @proxy_vanished_handler is called. * * This function makes it very simple to write applications that wants * to watch a well-known remote object on a well-known name, see . Basically, the application simply * starts using the proxy when @proxy_appeared_handler is called and * stops using it when @proxy_vanished_handler is called. Callbacks * will be invoked in the thread-default main * loop of the thread you are calling this function from. * * Applications typically use this function to watch the * manager object of a well-known name. Upon acquiring * a proxy for the manager object, applications typically construct * additional proxies in response to the result of enumeration methods * on the manager object. * * Many of the comments that apply to g_bus_watch_name() also apply * here. For example, you are guaranteed that one of the handlers will * be invoked (on the main thread) after calling this function and * also that the two handlers alternate. When you are done watching the * proxy, just call g_bus_unwatch_proxy(). * * Returns: An identifier (never 0) that can be used with * g_bus_unwatch_proxy() to stop watching the remote object. * * Since: 2.26 */ guint g_bus_watch_proxy (GBusType bus_type, const gchar *name, GBusNameWatcherFlags flags, const gchar *object_path, const gchar *interface_name, GType interface_type, GDBusProxyFlags proxy_flags, GBusProxyAppearedCallback proxy_appeared_handler, GBusProxyVanishedCallback proxy_vanished_handler, gpointer user_data, GDestroyNotify user_data_free_func) { Client *client; g_return_val_if_fail (g_dbus_is_name (name), 0); g_return_val_if_fail (g_variant_is_object_path (object_path), 0); g_return_val_if_fail (g_dbus_is_interface_name (interface_name), 0); g_return_val_if_fail (g_type_is_a (interface_type, G_TYPE_DBUS_PROXY), 0); G_LOCK (lock); client = g_new0 (Client, 1); client->id = next_global_id++; /* TODO: uh oh, handle overflow */ client->name = g_strdup (name); client->proxy_appeared_handler = proxy_appeared_handler; client->proxy_vanished_handler = proxy_vanished_handler; client->user_data = user_data; client->user_data_free_func = user_data_free_func; client->main_context = g_main_context_get_thread_default (); if (client->main_context != NULL) g_main_context_ref (client->main_context); client->name_watcher_id = g_bus_watch_name (bus_type, name, flags, on_name_appeared, on_name_vanished, client, NULL); client->object_path = g_strdup (object_path); client->interface_name = g_strdup (interface_name); client->interface_type = interface_type; client->proxy_flags = proxy_flags; client->initial_construction = TRUE; if (map_id_to_client == NULL) { map_id_to_client = g_hash_table_new (g_direct_hash, g_direct_equal); } g_hash_table_insert (map_id_to_client, GUINT_TO_POINTER (client->id), client); G_UNLOCK (lock); return client->id; } /** * g_bus_watch_proxy_on_connection: * @connection: A #GDBusConnection that is not closed. * @name: The name (well-known or unique) to watch. * @flags: Flags from the #GBusNameWatcherFlags enumeration. * @object_path: The object path of the remote object to watch. * @interface_name: The D-Bus interface name for the proxy. * @interface_type: The #GType for the kind of proxy to create. This must be a #GDBusProxy derived type. * @proxy_flags: Flags from #GDBusProxyFlags to use when constructing the proxy. * @proxy_appeared_handler: Handler to invoke when @name is known to exist and the * requested proxy is available. * @proxy_vanished_handler: Handler to invoke when @name is known to not exist * and the previously created proxy is no longer available. * @user_data: User data to pass to handlers. * @user_data_free_func: Function for freeing @user_data or %NULL. * * Like g_bus_watch_proxy() but takes a #GDBusConnection instead of a * #GBusType. * * Returns: An identifier (never 0) that can be used with * g_bus_unwatch_proxy() to stop watching the remote object. * * Since: 2.26 */ guint g_bus_watch_proxy_on_connection (GDBusConnection *connection, const gchar *name, GBusNameWatcherFlags flags, const gchar *object_path, const gchar *interface_name, GType interface_type, GDBusProxyFlags proxy_flags, GBusProxyAppearedCallback proxy_appeared_handler, GBusProxyVanishedCallback proxy_vanished_handler, gpointer user_data, GDestroyNotify user_data_free_func) { Client *client; g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), 0); g_return_val_if_fail (g_dbus_is_name (name), 0); g_return_val_if_fail (g_variant_is_object_path (object_path), 0); g_return_val_if_fail (g_dbus_is_interface_name (interface_name), 0); g_return_val_if_fail (g_type_is_a (interface_type, G_TYPE_DBUS_PROXY), 0); G_LOCK (lock); client = g_new0 (Client, 1); client->id = next_global_id++; /* TODO: uh oh, handle overflow */ client->name = g_strdup (name); client->proxy_appeared_handler = proxy_appeared_handler; client->proxy_vanished_handler = proxy_vanished_handler; client->user_data = user_data; client->user_data_free_func = user_data_free_func; client->main_context = g_main_context_get_thread_default (); if (client->main_context != NULL) g_main_context_ref (client->main_context); client->name_watcher_id = g_bus_watch_name_on_connection (connection, name, flags, on_name_appeared, on_name_vanished, client, NULL); client->object_path = g_strdup (object_path); client->interface_name = g_strdup (interface_name); client->interface_type = interface_type; client->proxy_flags = proxy_flags; client->initial_construction = TRUE; if (map_id_to_client == NULL) { map_id_to_client = g_hash_table_new (g_direct_hash, g_direct_equal); } g_hash_table_insert (map_id_to_client, GUINT_TO_POINTER (client->id), client); G_UNLOCK (lock); return client->id; } typedef struct { GClosure *proxy_appeared_closure; GClosure *proxy_vanished_closure; } WatchProxyData; static void watch_with_closures_on_proxy_appeared (GDBusConnection *connection, const gchar *name, const gchar *name_owner, GDBusProxy *proxy, gpointer user_data) { WatchProxyData *data = user_data; GValue params[4] = { { 0, }, { 0, }, { 0, }, { 0, } }; g_value_init (¶ms[0], G_TYPE_DBUS_CONNECTION); g_value_set_object (¶ms[0], connection); g_value_init (¶ms[1], G_TYPE_STRING); g_value_set_string (¶ms[1], name); g_value_init (¶ms[2], G_TYPE_STRING); g_value_set_string (¶ms[2], name_owner); g_value_init (¶ms[3], G_TYPE_DBUS_PROXY); g_value_set_object (¶ms[3], proxy); g_closure_invoke (data->proxy_appeared_closure, NULL, 4, params, NULL); } static void watch_with_closures_on_proxy_vanished (GDBusConnection *connection, const gchar *name, gpointer user_data) { WatchProxyData *data = user_data; GValue params[2] = { { 0, }, { 0, } }; g_value_init (¶ms[0], G_TYPE_DBUS_CONNECTION); g_value_set_object (¶ms[0], connection); g_value_init (¶ms[1], G_TYPE_STRING); g_value_set_string (¶ms[1], name); g_closure_invoke (data->proxy_vanished_closure, NULL, 2, params, NULL); } static void bus_watch_proxy_free_func (gpointer user_data) { WatchProxyData *data = user_data; if (data->proxy_appeared_closure != NULL) g_closure_unref (data->proxy_appeared_closure); if (data->proxy_vanished_closure != NULL) g_closure_unref (data->proxy_vanished_closure); g_free (data); } /** * g_bus_watch_proxy_with_closures: * @bus_type: The type of bus to watch a name on. * @name: The name (well-known or unique) to watch. * @flags: Flags from the #GBusNameWatcherFlags enumeration. * @object_path: The object path of the remote object to watch. * @interface_name: The D-Bus interface name for the proxy. * @interface_type: The #GType for the kind of proxy to create. This must be a #GDBusProxy derived type. * @proxy_flags: Flags from #GDBusProxyFlags to use when constructing the proxy. * @proxy_appeared_closure: (allow-none): #GClosure to invoke when @name is * known to exist and the requested proxy is available. * @proxy_vanished_closure: (allow-none): #GClosure to invoke when @name is * known to not exist and the previously created proxy is no longer available. * * Version of g_bus_watch_proxy() using closures instead of callbacks for * easier binding in other languages. * * Returns: An identifier (never 0) that can be used with * g_bus_unwatch_proxy() to stop watching the remote object. * * Rename to: g_bus_watch_proxy * * Since: 2.26 */ guint g_bus_watch_proxy_with_closures (GBusType bus_type, const gchar *name, GBusNameWatcherFlags flags, const gchar *object_path, const gchar *interface_name, GType interface_type, GDBusProxyFlags proxy_flags, GClosure *proxy_appeared_closure, GClosure *proxy_vanished_closure) { WatchProxyData *data; data = g_new0 (WatchProxyData, 1); if (proxy_appeared_closure != NULL) data->proxy_appeared_closure = g_closure_ref (proxy_appeared_closure); if (proxy_vanished_closure != NULL) data->proxy_vanished_closure = g_closure_ref (proxy_vanished_closure); return g_bus_watch_proxy (bus_type, name, flags, object_path, interface_name, interface_type, proxy_flags, proxy_appeared_closure != NULL ? watch_with_closures_on_proxy_appeared : NULL, proxy_vanished_closure != NULL ? watch_with_closures_on_proxy_vanished : NULL, data, bus_watch_proxy_free_func); } /** * g_bus_watch_proxy_on_connection_with_closures: * @connection: A #GDBusConnection that is not closed. * @name: The name (well-known or unique) to watch. * @flags: Flags from the #GBusNameWatcherFlags enumeration. * @object_path: The object path of the remote object to watch. * @interface_name: The D-Bus interface name for the proxy. * @interface_type: The #GType for the kind of proxy to create. This must be a #GDBusProxy derived type. * @proxy_flags: Flags from #GDBusProxyFlags to use when constructing the proxy. * @proxy_appeared_closure: (allow-none): #GClosure to invoke when @name is * known to exist and the requested proxy is available. * @proxy_vanished_closure: (allow-none): #GClosure to invoke when @name is * known to not exist and the previously created proxy is no longer available. * * Version of g_bus_watch_proxy_on_connection() using closures instead of * callbacks for easier binding in other languages. * * Returns: An identifier (never 0) that can be used with * g_bus_unwatch_proxy() to stop watching the remote object. * * Rename to: g_bus_watch_proxy_on_connection * * Since: 2.26 */ guint g_bus_watch_proxy_on_connection_with_closures ( GDBusConnection *connection, const gchar *name, GBusNameWatcherFlags flags, const gchar *object_path, const gchar *interface_name, GType interface_type, GDBusProxyFlags proxy_flags, GClosure *proxy_appeared_closure, GClosure *proxy_vanished_closure) { WatchProxyData *data; data = g_new0 (WatchProxyData, 1); if (proxy_appeared_closure != NULL) data->proxy_appeared_closure = g_closure_ref (proxy_appeared_closure); if (proxy_vanished_closure != NULL) data->proxy_vanished_closure = g_closure_ref (proxy_vanished_closure); return g_bus_watch_proxy_on_connection (connection, name, flags, object_path, interface_name, interface_type, proxy_flags, proxy_appeared_closure != NULL ? watch_with_closures_on_proxy_appeared : NULL, proxy_vanished_closure != NULL ? watch_with_closures_on_proxy_vanished : NULL, data, bus_watch_proxy_free_func); } /** * g_bus_unwatch_proxy: * @watcher_id: An identifier obtained from g_bus_watch_proxy() * * Stops watching proxy. * * Since: 2.26 */ void g_bus_unwatch_proxy (guint watcher_id) { Client *client; g_return_if_fail (watcher_id > 0); client = NULL; G_LOCK (lock); if (watcher_id == 0 || map_id_to_client == NULL || (client = g_hash_table_lookup (map_id_to_client, GUINT_TO_POINTER (watcher_id))) == NULL) { g_warning ("Invalid id %d passed to g_bus_unwatch_proxy()", watcher_id); goto out; } g_warn_if_fail (g_hash_table_remove (map_id_to_client, GUINT_TO_POINTER (watcher_id))); out: G_UNLOCK (lock); if (client != NULL) { g_bus_unwatch_name (client->name_watcher_id); client->name_watcher_id = 0; client_unref (client); } } #define __G_DBUS_PROXY_WATCHING_C__ #include "gioaliasdef.c"