| /* |
| * |
| * D-Bus helper library |
| * |
| * Copyright (C) 2004-2011 Marcel Holtmann <marcel@holtmann.org> |
| * |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program 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 General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| * |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #include <stdio.h> |
| #include <string.h> |
| #include <glib.h> |
| #include <dbus/dbus.h> |
| |
| #include "gdbus.h" |
| |
| #define METHOD_CALL_TIMEOUT (300 * 1000) |
| |
| #ifndef DBUS_INTERFACE_OBJECT_MANAGER |
| #define DBUS_INTERFACE_OBJECT_MANAGER DBUS_INTERFACE_DBUS ".ObjectManager" |
| #endif |
| |
| struct GDBusClient { |
| int ref_count; |
| DBusConnection *dbus_conn; |
| char *service_name; |
| char *base_path; |
| char *root_path; |
| guint watch; |
| guint added_watch; |
| guint removed_watch; |
| GPtrArray *match_rules; |
| DBusPendingCall *pending_call; |
| DBusPendingCall *get_objects_call; |
| GDBusWatchFunction connect_func; |
| void *connect_data; |
| GDBusWatchFunction disconn_func; |
| gboolean connected; |
| void *disconn_data; |
| GDBusMessageFunction signal_func; |
| void *signal_data; |
| GDBusProxyFunction proxy_added; |
| GDBusProxyFunction proxy_removed; |
| GDBusClientFunction ready; |
| void *ready_data; |
| GDBusPropertyFunction property_changed; |
| void *user_data; |
| GList *proxy_list; |
| }; |
| |
| struct GDBusProxy { |
| int ref_count; |
| GDBusClient *client; |
| char *obj_path; |
| char *interface; |
| GHashTable *prop_list; |
| guint watch; |
| GDBusPropertyFunction prop_func; |
| void *prop_data; |
| GDBusProxyFunction removed_func; |
| void *removed_data; |
| DBusPendingCall *get_all_call; |
| gboolean pending; |
| }; |
| |
| struct prop_entry { |
| char *name; |
| int type; |
| DBusMessage *msg; |
| }; |
| |
| static void modify_match_reply(DBusPendingCall *call, void *user_data) |
| { |
| DBusMessage *reply = dbus_pending_call_steal_reply(call); |
| DBusError error; |
| |
| dbus_error_init(&error); |
| |
| if (dbus_set_error_from_message(&error, reply) == TRUE) |
| dbus_error_free(&error); |
| |
| dbus_message_unref(reply); |
| } |
| |
| static gboolean modify_match(DBusConnection *conn, const char *member, |
| const char *rule) |
| { |
| DBusMessage *msg; |
| DBusPendingCall *call; |
| |
| msg = dbus_message_new_method_call(DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, |
| DBUS_INTERFACE_DBUS, member); |
| if (msg == NULL) |
| return FALSE; |
| |
| dbus_message_append_args(msg, DBUS_TYPE_STRING, &rule, |
| DBUS_TYPE_INVALID); |
| |
| if (g_dbus_send_message_with_reply(conn, msg, &call, -1) == FALSE) { |
| dbus_message_unref(msg); |
| return FALSE; |
| } |
| |
| dbus_pending_call_set_notify(call, modify_match_reply, NULL, NULL); |
| dbus_pending_call_unref(call); |
| |
| dbus_message_unref(msg); |
| |
| return TRUE; |
| } |
| |
| static void append_variant(DBusMessageIter *iter, int type, const void *val) |
| { |
| DBusMessageIter value; |
| char sig[2] = { type, '\0' }; |
| |
| dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, sig, &value); |
| |
| dbus_message_iter_append_basic(&value, type, val); |
| |
| dbus_message_iter_close_container(iter, &value); |
| } |
| |
| static void append_array_variant(DBusMessageIter *iter, int type, void *val, |
| int n_elements) |
| { |
| DBusMessageIter variant, array; |
| char type_sig[2] = { type, '\0' }; |
| char array_sig[3] = { DBUS_TYPE_ARRAY, type, '\0' }; |
| |
| dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, |
| array_sig, &variant); |
| |
| dbus_message_iter_open_container(&variant, DBUS_TYPE_ARRAY, |
| type_sig, &array); |
| |
| if (dbus_type_is_fixed(type) == TRUE) { |
| dbus_message_iter_append_fixed_array(&array, type, val, |
| n_elements); |
| } else if (type == DBUS_TYPE_STRING || type == DBUS_TYPE_OBJECT_PATH) { |
| const char ***str_array = val; |
| int i; |
| |
| for (i = 0; i < n_elements; i++) |
| dbus_message_iter_append_basic(&array, type, |
| &((*str_array)[i])); |
| } |
| |
| dbus_message_iter_close_container(&variant, &array); |
| |
| dbus_message_iter_close_container(iter, &variant); |
| } |
| |
| static void dict_append_basic(DBusMessageIter *dict, int key_type, |
| const void *key, int type, void *val) |
| { |
| DBusMessageIter entry; |
| |
| if (type == DBUS_TYPE_STRING) { |
| const char *str = *((const char **) val); |
| if (str == NULL) |
| return; |
| } |
| |
| dbus_message_iter_open_container(dict, DBUS_TYPE_DICT_ENTRY, |
| NULL, &entry); |
| |
| dbus_message_iter_append_basic(&entry, key_type, key); |
| |
| append_variant(&entry, type, val); |
| |
| dbus_message_iter_close_container(dict, &entry); |
| } |
| |
| void g_dbus_dict_append_entry(DBusMessageIter *dict, |
| const char *key, int type, void *val) |
| { |
| dict_append_basic(dict, DBUS_TYPE_STRING, &key, type, val); |
| } |
| |
| void g_dbus_dict_append_basic_array(DBusMessageIter *dict, int key_type, |
| const void *key, int type, void *val, |
| int n_elements) |
| { |
| DBusMessageIter entry; |
| |
| dbus_message_iter_open_container(dict, DBUS_TYPE_DICT_ENTRY, |
| NULL, &entry); |
| |
| dbus_message_iter_append_basic(&entry, key_type, key); |
| |
| append_array_variant(&entry, type, val, n_elements); |
| |
| dbus_message_iter_close_container(dict, &entry); |
| } |
| |
| void g_dbus_dict_append_array(DBusMessageIter *dict, |
| const char *key, int type, void *val, |
| int n_elements) |
| { |
| g_dbus_dict_append_basic_array(dict, DBUS_TYPE_STRING, &key, type, val, |
| n_elements); |
| } |
| |
| static void iter_append_iter(DBusMessageIter *base, DBusMessageIter *iter) |
| { |
| int type; |
| |
| type = dbus_message_iter_get_arg_type(iter); |
| |
| if (dbus_type_is_basic(type)) { |
| const void *value; |
| |
| dbus_message_iter_get_basic(iter, &value); |
| dbus_message_iter_append_basic(base, type, &value); |
| } else if (dbus_type_is_container(type)) { |
| DBusMessageIter iter_sub, base_sub; |
| char *sig; |
| |
| dbus_message_iter_recurse(iter, &iter_sub); |
| |
| switch (type) { |
| case DBUS_TYPE_ARRAY: |
| case DBUS_TYPE_VARIANT: |
| sig = dbus_message_iter_get_signature(&iter_sub); |
| break; |
| default: |
| sig = NULL; |
| break; |
| } |
| |
| dbus_message_iter_open_container(base, type, sig, &base_sub); |
| |
| if (sig != NULL) |
| dbus_free(sig); |
| |
| while (dbus_message_iter_get_arg_type(&iter_sub) != |
| DBUS_TYPE_INVALID) { |
| iter_append_iter(&base_sub, &iter_sub); |
| dbus_message_iter_next(&iter_sub); |
| } |
| |
| dbus_message_iter_close_container(base, &base_sub); |
| } |
| } |
| |
| static void prop_entry_update(struct prop_entry *prop, DBusMessageIter *iter) |
| { |
| DBusMessage *msg; |
| DBusMessageIter base; |
| |
| msg = dbus_message_new(DBUS_MESSAGE_TYPE_METHOD_RETURN); |
| if (msg == NULL) |
| return; |
| |
| dbus_message_iter_init_append(msg, &base); |
| iter_append_iter(&base, iter); |
| |
| if (prop->msg != NULL) |
| dbus_message_unref(prop->msg); |
| |
| prop->msg = dbus_message_copy(msg); |
| dbus_message_unref(msg); |
| } |
| |
| static struct prop_entry *prop_entry_new(const char *name, |
| DBusMessageIter *iter) |
| { |
| struct prop_entry *prop; |
| |
| prop = g_try_new0(struct prop_entry, 1); |
| if (prop == NULL) |
| return NULL; |
| |
| prop->name = g_strdup(name); |
| prop->type = dbus_message_iter_get_arg_type(iter); |
| |
| prop_entry_update(prop, iter); |
| |
| return prop; |
| } |
| |
| static void prop_entry_free(gpointer data) |
| { |
| struct prop_entry *prop = data; |
| |
| if (prop->msg != NULL) |
| dbus_message_unref(prop->msg); |
| |
| g_free(prop->name); |
| |
| g_free(prop); |
| } |
| |
| static void add_property(GDBusProxy *proxy, const char *name, |
| DBusMessageIter *iter, gboolean send_changed) |
| { |
| GDBusClient *client = proxy->client; |
| DBusMessageIter value; |
| struct prop_entry *prop; |
| |
| if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_VARIANT) |
| return; |
| |
| dbus_message_iter_recurse(iter, &value); |
| |
| prop = g_hash_table_lookup(proxy->prop_list, name); |
| if (prop != NULL) { |
| prop_entry_update(prop, &value); |
| goto done; |
| } |
| |
| prop = prop_entry_new(name, &value); |
| if (prop == NULL) |
| return; |
| |
| g_hash_table_replace(proxy->prop_list, prop->name, prop); |
| |
| done: |
| if (proxy->prop_func) |
| proxy->prop_func(proxy, name, &value, proxy->prop_data); |
| |
| if (client == NULL || send_changed == FALSE) |
| return; |
| |
| if (client->property_changed) |
| client->property_changed(proxy, name, &value, |
| client->user_data); |
| } |
| |
| static void update_properties(GDBusProxy *proxy, DBusMessageIter *iter, |
| gboolean send_changed) |
| { |
| DBusMessageIter dict; |
| |
| if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY) |
| return; |
| |
| dbus_message_iter_recurse(iter, &dict); |
| |
| while (dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY) { |
| DBusMessageIter entry; |
| const char *name; |
| |
| dbus_message_iter_recurse(&dict, &entry); |
| |
| if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_STRING) |
| break; |
| |
| dbus_message_iter_get_basic(&entry, &name); |
| dbus_message_iter_next(&entry); |
| |
| add_property(proxy, name, &entry, send_changed); |
| |
| dbus_message_iter_next(&dict); |
| } |
| } |
| |
| static void proxy_added(GDBusClient *client, GDBusProxy *proxy) |
| { |
| if (!proxy->pending) |
| return; |
| |
| if (client->proxy_added) |
| client->proxy_added(proxy, client->user_data); |
| |
| proxy->pending = FALSE; |
| } |
| |
| static void get_all_properties_reply(DBusPendingCall *call, void *user_data) |
| { |
| GDBusProxy *proxy = user_data; |
| GDBusClient *client = proxy->client; |
| DBusMessage *reply = dbus_pending_call_steal_reply(call); |
| DBusMessageIter iter; |
| DBusError error; |
| |
| g_dbus_client_ref(client); |
| |
| dbus_error_init(&error); |
| |
| if (dbus_set_error_from_message(&error, reply) == TRUE) { |
| dbus_error_free(&error); |
| goto done; |
| } |
| |
| dbus_message_iter_init(reply, &iter); |
| |
| update_properties(proxy, &iter, FALSE); |
| |
| done: |
| proxy_added(client, proxy); |
| |
| dbus_message_unref(reply); |
| |
| dbus_pending_call_unref(proxy->get_all_call); |
| proxy->get_all_call = NULL; |
| |
| g_dbus_client_unref(client); |
| } |
| |
| static void get_all_properties(GDBusProxy *proxy) |
| { |
| GDBusClient *client = proxy->client; |
| const char *service_name = client->service_name; |
| DBusMessage *msg; |
| |
| if (proxy->get_all_call) |
| return; |
| |
| msg = dbus_message_new_method_call(service_name, proxy->obj_path, |
| DBUS_INTERFACE_PROPERTIES, "GetAll"); |
| if (msg == NULL) |
| return; |
| |
| dbus_message_append_args(msg, DBUS_TYPE_STRING, &proxy->interface, |
| DBUS_TYPE_INVALID); |
| |
| if (g_dbus_send_message_with_reply(client->dbus_conn, msg, |
| &proxy->get_all_call, -1) == FALSE) { |
| dbus_message_unref(msg); |
| return; |
| } |
| |
| dbus_pending_call_set_notify(proxy->get_all_call, |
| get_all_properties_reply, proxy, NULL); |
| |
| dbus_message_unref(msg); |
| } |
| |
| GDBusProxy *g_dbus_proxy_lookup(GList *list, int *index, const char *path, |
| const char *interface) |
| { |
| GList *l; |
| |
| if (!interface) |
| return NULL; |
| |
| for (l = g_list_nth(list, index ? *index : 0); l; l = g_list_next(l)) { |
| GDBusProxy *proxy = l->data; |
| const char *proxy_iface = g_dbus_proxy_get_interface(proxy); |
| const char *proxy_path = g_dbus_proxy_get_path(proxy); |
| |
| if (index) |
| (*index)++; |
| |
| if (g_str_equal(proxy_iface, interface) == TRUE && |
| g_str_equal(proxy_path, path) == TRUE) |
| return proxy; |
| } |
| |
| return NULL; |
| } |
| |
| char *g_dbus_proxy_path_lookup(GList *list, int *index, const char *path) |
| { |
| int len = strlen(path); |
| GList *l; |
| |
| for (l = g_list_nth(list, index ? *index : 0); l; l = g_list_next(l)) { |
| GDBusProxy *proxy = l->data; |
| const char *proxy_path = g_dbus_proxy_get_path(proxy); |
| |
| if (index) |
| (*index)++; |
| |
| if (!strncasecmp(proxy_path, path, len)) |
| return strdup(proxy_path); |
| } |
| |
| return NULL; |
| } |
| |
| static gboolean properties_changed(DBusConnection *conn, DBusMessage *msg, |
| void *user_data) |
| { |
| GDBusProxy *proxy = user_data; |
| GDBusClient *client = proxy->client; |
| DBusMessageIter iter, entry; |
| const char *interface; |
| |
| if (dbus_message_iter_init(msg, &iter) == FALSE) |
| return TRUE; |
| |
| if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) |
| return TRUE; |
| |
| dbus_message_iter_get_basic(&iter, &interface); |
| dbus_message_iter_next(&iter); |
| |
| update_properties(proxy, &iter, TRUE); |
| |
| dbus_message_iter_next(&iter); |
| |
| if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) |
| return TRUE; |
| |
| dbus_message_iter_recurse(&iter, &entry); |
| |
| while (dbus_message_iter_get_arg_type(&entry) == DBUS_TYPE_STRING) { |
| const char *name; |
| |
| dbus_message_iter_get_basic(&entry, &name); |
| |
| g_hash_table_remove(proxy->prop_list, name); |
| |
| if (proxy->prop_func) |
| proxy->prop_func(proxy, name, NULL, proxy->prop_data); |
| |
| if (client->property_changed) |
| client->property_changed(proxy, name, NULL, |
| client->user_data); |
| |
| dbus_message_iter_next(&entry); |
| } |
| |
| return TRUE; |
| } |
| |
| static GDBusProxy *proxy_new(GDBusClient *client, const char *path, |
| const char *interface) |
| { |
| GDBusProxy *proxy; |
| |
| proxy = g_try_new0(GDBusProxy, 1); |
| if (proxy == NULL) |
| return NULL; |
| |
| proxy->client = client; |
| proxy->obj_path = g_strdup(path); |
| proxy->interface = g_strdup(interface); |
| |
| proxy->prop_list = g_hash_table_new_full(g_str_hash, g_str_equal, |
| NULL, prop_entry_free); |
| proxy->watch = g_dbus_add_properties_watch(client->dbus_conn, |
| client->service_name, |
| proxy->obj_path, |
| proxy->interface, |
| properties_changed, |
| proxy, NULL); |
| proxy->pending = TRUE; |
| |
| client->proxy_list = g_list_append(client->proxy_list, proxy); |
| |
| return g_dbus_proxy_ref(proxy); |
| } |
| |
| static void proxy_free(gpointer data) |
| { |
| GDBusProxy *proxy = data; |
| |
| if (proxy->client) { |
| GDBusClient *client = proxy->client; |
| |
| if (proxy->get_all_call != NULL) { |
| dbus_pending_call_cancel(proxy->get_all_call); |
| dbus_pending_call_unref(proxy->get_all_call); |
| proxy->get_all_call = NULL; |
| } |
| |
| if (client->proxy_removed) |
| client->proxy_removed(proxy, client->user_data); |
| |
| g_dbus_remove_watch(client->dbus_conn, proxy->watch); |
| |
| g_hash_table_remove_all(proxy->prop_list); |
| |
| proxy->client = NULL; |
| } |
| |
| if (proxy->removed_func) |
| proxy->removed_func(proxy, proxy->removed_data); |
| |
| g_dbus_proxy_unref(proxy); |
| } |
| |
| static void proxy_remove(GDBusClient *client, const char *path, |
| const char *interface) |
| { |
| GList *list; |
| |
| for (list = g_list_first(client->proxy_list); list; |
| list = g_list_next(list)) { |
| GDBusProxy *proxy = list->data; |
| |
| if (g_str_equal(proxy->interface, interface) == TRUE && |
| g_str_equal(proxy->obj_path, path) == TRUE) { |
| client->proxy_list = |
| g_list_delete_link(client->proxy_list, list); |
| proxy_free(proxy); |
| break; |
| } |
| } |
| } |
| |
| static void start_service(GDBusProxy *proxy) |
| { |
| GDBusClient *client = proxy->client; |
| const char *service_name = client->service_name; |
| dbus_uint32_t flags = 0; |
| DBusMessage *msg; |
| |
| msg = dbus_message_new_method_call(DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, |
| DBUS_INTERFACE_DBUS, |
| "StartServiceByName"); |
| if (msg == NULL) |
| return; |
| |
| dbus_message_append_args(msg, DBUS_TYPE_STRING, &service_name, |
| DBUS_TYPE_UINT32, &flags, |
| DBUS_TYPE_INVALID); |
| |
| g_dbus_send_message(client->dbus_conn, msg); |
| return; |
| } |
| |
| GDBusProxy *g_dbus_proxy_new(GDBusClient *client, const char *path, |
| const char *interface) |
| { |
| GDBusProxy *proxy; |
| |
| if (client == NULL) |
| return NULL; |
| |
| proxy = g_dbus_proxy_lookup(client->proxy_list, NULL, |
| path, interface); |
| if (proxy) |
| return g_dbus_proxy_ref(proxy); |
| |
| proxy = proxy_new(client, path, interface); |
| if (proxy == NULL) |
| return NULL; |
| |
| if (!client->connected) { |
| /* Force service to start */ |
| start_service(proxy); |
| return g_dbus_proxy_ref(proxy); |
| } |
| |
| if (!client->get_objects_call) |
| get_all_properties(proxy); |
| |
| return g_dbus_proxy_ref(proxy); |
| } |
| |
| GDBusProxy *g_dbus_proxy_ref(GDBusProxy *proxy) |
| { |
| if (proxy == NULL) |
| return NULL; |
| |
| __sync_fetch_and_add(&proxy->ref_count, 1); |
| |
| return proxy; |
| } |
| |
| void g_dbus_proxy_unref(GDBusProxy *proxy) |
| { |
| if (proxy == NULL) |
| return; |
| |
| if (__sync_sub_and_fetch(&proxy->ref_count, 1) > 0) |
| return; |
| |
| if (proxy->get_all_call != NULL) { |
| dbus_pending_call_cancel(proxy->get_all_call); |
| dbus_pending_call_unref(proxy->get_all_call); |
| } |
| |
| g_hash_table_destroy(proxy->prop_list); |
| |
| g_free(proxy->obj_path); |
| g_free(proxy->interface); |
| |
| g_free(proxy); |
| } |
| |
| const char *g_dbus_proxy_get_path(GDBusProxy *proxy) |
| { |
| if (proxy == NULL) |
| return NULL; |
| |
| return proxy->obj_path; |
| } |
| |
| const char *g_dbus_proxy_get_interface(GDBusProxy *proxy) |
| { |
| if (proxy == NULL) |
| return NULL; |
| |
| return proxy->interface; |
| } |
| |
| gboolean g_dbus_proxy_get_property(GDBusProxy *proxy, const char *name, |
| DBusMessageIter *iter) |
| { |
| struct prop_entry *prop; |
| |
| if (proxy == NULL || name == NULL) |
| return FALSE; |
| |
| prop = g_hash_table_lookup(proxy->prop_list, name); |
| if (prop == NULL) |
| return FALSE; |
| |
| if (prop->msg == NULL) |
| return FALSE; |
| |
| if (dbus_message_iter_init(prop->msg, iter) == FALSE) |
| return FALSE; |
| |
| return TRUE; |
| } |
| |
| struct refresh_property_data { |
| GDBusProxy *proxy; |
| char *name; |
| }; |
| |
| static void refresh_property_free(gpointer user_data) |
| { |
| struct refresh_property_data *data = user_data; |
| |
| g_free(data->name); |
| g_free(data); |
| } |
| |
| static void refresh_property_reply(DBusPendingCall *call, void *user_data) |
| { |
| struct refresh_property_data *data = user_data; |
| DBusMessage *reply = dbus_pending_call_steal_reply(call); |
| DBusError error; |
| |
| dbus_error_init(&error); |
| |
| if (dbus_set_error_from_message(&error, reply) == FALSE) { |
| DBusMessageIter iter; |
| |
| dbus_message_iter_init(reply, &iter); |
| |
| add_property(data->proxy, data->name, &iter, TRUE); |
| } else |
| dbus_error_free(&error); |
| |
| dbus_message_unref(reply); |
| } |
| |
| gboolean g_dbus_proxy_refresh_property(GDBusProxy *proxy, const char *name) |
| { |
| struct refresh_property_data *data; |
| GDBusClient *client; |
| DBusMessage *msg; |
| DBusMessageIter iter; |
| DBusPendingCall *call; |
| |
| if (proxy == NULL || name == NULL) |
| return FALSE; |
| |
| client = proxy->client; |
| if (client == NULL) |
| return FALSE; |
| |
| data = g_try_new0(struct refresh_property_data, 1); |
| if (data == NULL) |
| return FALSE; |
| |
| data->proxy = proxy; |
| data->name = g_strdup(name); |
| |
| msg = dbus_message_new_method_call(client->service_name, |
| proxy->obj_path, DBUS_INTERFACE_PROPERTIES, "Get"); |
| if (msg == NULL) { |
| refresh_property_free(data); |
| return FALSE; |
| } |
| |
| dbus_message_iter_init_append(msg, &iter); |
| dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, |
| &proxy->interface); |
| dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &name); |
| |
| if (g_dbus_send_message_with_reply(client->dbus_conn, msg, |
| &call, -1) == FALSE) { |
| dbus_message_unref(msg); |
| refresh_property_free(data); |
| return FALSE; |
| } |
| |
| dbus_pending_call_set_notify(call, refresh_property_reply, |
| data, refresh_property_free); |
| dbus_pending_call_unref(call); |
| |
| dbus_message_unref(msg); |
| |
| return TRUE; |
| } |
| |
| struct set_property_data { |
| GDBusResultFunction function; |
| void *user_data; |
| GDBusDestroyFunction destroy; |
| }; |
| |
| static void set_property_reply(DBusPendingCall *call, void *user_data) |
| { |
| struct set_property_data *data = user_data; |
| DBusMessage *reply = dbus_pending_call_steal_reply(call); |
| DBusError error; |
| |
| dbus_error_init(&error); |
| |
| dbus_set_error_from_message(&error, reply); |
| |
| if (data->function) |
| data->function(&error, data->user_data); |
| |
| if (data->destroy) |
| data->destroy(data->user_data); |
| |
| dbus_error_free(&error); |
| |
| dbus_message_unref(reply); |
| } |
| |
| gboolean g_dbus_proxy_set_property_basic(GDBusProxy *proxy, |
| const char *name, int type, const void *value, |
| GDBusResultFunction function, void *user_data, |
| GDBusDestroyFunction destroy) |
| { |
| struct set_property_data *data; |
| GDBusClient *client; |
| DBusMessage *msg; |
| DBusMessageIter iter; |
| DBusPendingCall *call; |
| |
| if (proxy == NULL || name == NULL || value == NULL) |
| return FALSE; |
| |
| if (dbus_type_is_basic(type) == FALSE) |
| return FALSE; |
| |
| client = proxy->client; |
| if (client == NULL) |
| return FALSE; |
| |
| data = g_try_new0(struct set_property_data, 1); |
| if (data == NULL) |
| return FALSE; |
| |
| data->function = function; |
| data->user_data = user_data; |
| data->destroy = destroy; |
| |
| msg = dbus_message_new_method_call(client->service_name, |
| proxy->obj_path, DBUS_INTERFACE_PROPERTIES, "Set"); |
| if (msg == NULL) { |
| g_free(data); |
| return FALSE; |
| } |
| |
| dbus_message_iter_init_append(msg, &iter); |
| dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, |
| &proxy->interface); |
| dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &name); |
| |
| append_variant(&iter, type, value); |
| |
| if (g_dbus_send_message_with_reply(client->dbus_conn, msg, |
| &call, -1) == FALSE) { |
| dbus_message_unref(msg); |
| g_free(data); |
| return FALSE; |
| } |
| |
| dbus_pending_call_set_notify(call, set_property_reply, data, g_free); |
| dbus_pending_call_unref(call); |
| |
| dbus_message_unref(msg); |
| |
| return TRUE; |
| } |
| |
| gboolean g_dbus_proxy_set_property_array(GDBusProxy *proxy, |
| const char *name, int type, const void *value, |
| size_t size, GDBusResultFunction function, |
| void *user_data, GDBusDestroyFunction destroy) |
| { |
| struct set_property_data *data; |
| GDBusClient *client; |
| DBusMessage *msg; |
| DBusMessageIter iter; |
| DBusPendingCall *call; |
| |
| if (!proxy || !name || !value) |
| return FALSE; |
| |
| if (!dbus_type_is_basic(type)) |
| return FALSE; |
| |
| client = proxy->client; |
| if (!client) |
| return FALSE; |
| |
| data = g_try_new0(struct set_property_data, 1); |
| if (!data) |
| return FALSE; |
| |
| data->function = function; |
| data->user_data = user_data; |
| data->destroy = destroy; |
| |
| msg = dbus_message_new_method_call(client->service_name, |
| proxy->obj_path, |
| DBUS_INTERFACE_PROPERTIES, |
| "Set"); |
| if (!msg) { |
| g_free(data); |
| return FALSE; |
| } |
| |
| dbus_message_iter_init_append(msg, &iter); |
| dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, |
| &proxy->interface); |
| dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &name); |
| |
| append_array_variant(&iter, type, &value, size); |
| |
| if (g_dbus_send_message_with_reply(client->dbus_conn, msg, |
| &call, -1) == FALSE) { |
| dbus_message_unref(msg); |
| g_free(data); |
| return FALSE; |
| } |
| |
| dbus_pending_call_set_notify(call, set_property_reply, data, g_free); |
| dbus_pending_call_unref(call); |
| |
| dbus_message_unref(msg); |
| |
| return TRUE; |
| } |
| |
| struct method_call_data { |
| GDBusReturnFunction function; |
| void *user_data; |
| GDBusDestroyFunction destroy; |
| }; |
| |
| static void method_call_reply(DBusPendingCall *call, void *user_data) |
| { |
| struct method_call_data *data = user_data; |
| DBusMessage *reply = dbus_pending_call_steal_reply(call); |
| |
| if (data->function) |
| data->function(reply, data->user_data); |
| |
| if (data->destroy) |
| data->destroy(data->user_data); |
| |
| dbus_message_unref(reply); |
| } |
| |
| gboolean g_dbus_proxy_method_call(GDBusProxy *proxy, const char *method, |
| GDBusSetupFunction setup, |
| GDBusReturnFunction function, void *user_data, |
| GDBusDestroyFunction destroy) |
| { |
| struct method_call_data *data; |
| GDBusClient *client; |
| DBusMessage *msg; |
| DBusPendingCall *call; |
| |
| if (proxy == NULL || method == NULL) |
| return FALSE; |
| |
| client = proxy->client; |
| if (client == NULL) |
| return FALSE; |
| |
| msg = dbus_message_new_method_call(client->service_name, |
| proxy->obj_path, proxy->interface, method); |
| if (msg == NULL) |
| return FALSE; |
| |
| if (setup) { |
| DBusMessageIter iter; |
| |
| dbus_message_iter_init_append(msg, &iter); |
| setup(&iter, user_data); |
| } |
| |
| if (!function) |
| return g_dbus_send_message(client->dbus_conn, msg); |
| |
| data = g_try_new0(struct method_call_data, 1); |
| if (data == NULL) |
| return FALSE; |
| |
| data->function = function; |
| data->user_data = user_data; |
| data->destroy = destroy; |
| |
| |
| if (g_dbus_send_message_with_reply(client->dbus_conn, msg, |
| &call, METHOD_CALL_TIMEOUT) == FALSE) { |
| dbus_message_unref(msg); |
| g_free(data); |
| return FALSE; |
| } |
| |
| dbus_pending_call_set_notify(call, method_call_reply, data, g_free); |
| dbus_pending_call_unref(call); |
| |
| dbus_message_unref(msg); |
| |
| return TRUE; |
| } |
| |
| gboolean g_dbus_proxy_set_property_watch(GDBusProxy *proxy, |
| GDBusPropertyFunction function, void *user_data) |
| { |
| if (proxy == NULL) |
| return FALSE; |
| |
| proxy->prop_func = function; |
| proxy->prop_data = user_data; |
| |
| return TRUE; |
| } |
| |
| gboolean g_dbus_proxy_set_removed_watch(GDBusProxy *proxy, |
| GDBusProxyFunction function, void *user_data) |
| { |
| if (proxy == NULL) |
| return FALSE; |
| |
| proxy->removed_func = function; |
| proxy->removed_data = user_data; |
| |
| return TRUE; |
| } |
| |
| static void refresh_properties(GList *list) |
| { |
| GList *l; |
| |
| for (l = g_list_first(list); l; l = g_list_next(l)) { |
| GDBusProxy *proxy = list->data; |
| |
| if (proxy->pending) |
| get_all_properties(proxy); |
| } |
| } |
| |
| static void parse_properties(GDBusClient *client, const char *path, |
| const char *interface, DBusMessageIter *iter) |
| { |
| GDBusProxy *proxy; |
| |
| if (g_str_equal(interface, DBUS_INTERFACE_INTROSPECTABLE) == TRUE) |
| return; |
| |
| if (g_str_equal(interface, DBUS_INTERFACE_PROPERTIES) == TRUE) |
| return; |
| |
| proxy = g_dbus_proxy_lookup(client->proxy_list, NULL, |
| path, interface); |
| if (proxy && !proxy->pending) { |
| update_properties(proxy, iter, FALSE); |
| return; |
| } |
| |
| if (!proxy) { |
| proxy = proxy_new(client, path, interface); |
| if (proxy == NULL) |
| return; |
| } |
| |
| update_properties(proxy, iter, FALSE); |
| |
| proxy_added(client, proxy); |
| } |
| |
| static void parse_interfaces(GDBusClient *client, const char *path, |
| DBusMessageIter *iter) |
| { |
| DBusMessageIter dict; |
| |
| if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY) |
| return; |
| |
| dbus_message_iter_recurse(iter, &dict); |
| |
| while (dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY) { |
| DBusMessageIter entry; |
| const char *interface; |
| |
| dbus_message_iter_recurse(&dict, &entry); |
| |
| if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_STRING) |
| break; |
| |
| dbus_message_iter_get_basic(&entry, &interface); |
| dbus_message_iter_next(&entry); |
| |
| parse_properties(client, path, interface, &entry); |
| |
| dbus_message_iter_next(&dict); |
| } |
| } |
| |
| static gboolean interfaces_added(DBusConnection *conn, DBusMessage *msg, |
| void *user_data) |
| { |
| GDBusClient *client = user_data; |
| DBusMessageIter iter; |
| const char *path; |
| |
| if (dbus_message_iter_init(msg, &iter) == FALSE) |
| return TRUE; |
| |
| if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_OBJECT_PATH) |
| return TRUE; |
| |
| dbus_message_iter_get_basic(&iter, &path); |
| dbus_message_iter_next(&iter); |
| |
| g_dbus_client_ref(client); |
| |
| parse_interfaces(client, path, &iter); |
| |
| g_dbus_client_unref(client); |
| |
| return TRUE; |
| } |
| |
| static gboolean interfaces_removed(DBusConnection *conn, DBusMessage *msg, |
| void *user_data) |
| { |
| GDBusClient *client = user_data; |
| DBusMessageIter iter, entry; |
| const char *path; |
| |
| if (dbus_message_iter_init(msg, &iter) == FALSE) |
| return TRUE; |
| |
| if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_OBJECT_PATH) |
| return TRUE; |
| |
| dbus_message_iter_get_basic(&iter, &path); |
| dbus_message_iter_next(&iter); |
| |
| if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) |
| return TRUE; |
| |
| dbus_message_iter_recurse(&iter, &entry); |
| |
| g_dbus_client_ref(client); |
| |
| while (dbus_message_iter_get_arg_type(&entry) == DBUS_TYPE_STRING) { |
| const char *interface; |
| |
| dbus_message_iter_get_basic(&entry, &interface); |
| proxy_remove(client, path, interface); |
| dbus_message_iter_next(&entry); |
| } |
| |
| g_dbus_client_unref(client); |
| |
| return TRUE; |
| } |
| |
| static void parse_managed_objects(GDBusClient *client, DBusMessage *msg) |
| { |
| DBusMessageIter iter, dict; |
| |
| if (dbus_message_iter_init(msg, &iter) == FALSE) |
| return; |
| |
| if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) |
| return; |
| |
| dbus_message_iter_recurse(&iter, &dict); |
| |
| while (dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY) { |
| DBusMessageIter entry; |
| const char *path; |
| |
| dbus_message_iter_recurse(&dict, &entry); |
| |
| if (dbus_message_iter_get_arg_type(&entry) != |
| DBUS_TYPE_OBJECT_PATH) |
| break; |
| |
| dbus_message_iter_get_basic(&entry, &path); |
| dbus_message_iter_next(&entry); |
| |
| parse_interfaces(client, path, &entry); |
| |
| dbus_message_iter_next(&dict); |
| } |
| } |
| |
| static void get_managed_objects_reply(DBusPendingCall *call, void *user_data) |
| { |
| GDBusClient *client = user_data; |
| DBusMessage *reply = dbus_pending_call_steal_reply(call); |
| DBusError error; |
| |
| g_dbus_client_ref(client); |
| |
| dbus_error_init(&error); |
| |
| if (dbus_set_error_from_message(&error, reply) == TRUE) { |
| dbus_error_free(&error); |
| goto done; |
| } |
| |
| parse_managed_objects(client, reply); |
| |
| done: |
| if (client->ready) |
| client->ready(client, client->ready_data); |
| |
| dbus_message_unref(reply); |
| |
| dbus_pending_call_unref(client->get_objects_call); |
| client->get_objects_call = NULL; |
| |
| refresh_properties(client->proxy_list); |
| |
| g_dbus_client_unref(client); |
| } |
| |
| static void get_managed_objects(GDBusClient *client) |
| { |
| DBusMessage *msg; |
| |
| if (!client->connected) |
| return; |
| |
| if ((!client->proxy_added && !client->proxy_removed) || |
| !client->root_path) { |
| refresh_properties(client->proxy_list); |
| return; |
| } |
| |
| if (client->get_objects_call != NULL) |
| return; |
| |
| msg = dbus_message_new_method_call(client->service_name, |
| client->root_path, |
| DBUS_INTERFACE_OBJECT_MANAGER, |
| "GetManagedObjects"); |
| if (msg == NULL) |
| return; |
| |
| dbus_message_append_args(msg, DBUS_TYPE_INVALID); |
| |
| if (g_dbus_send_message_with_reply(client->dbus_conn, msg, |
| &client->get_objects_call, -1) == FALSE) { |
| dbus_message_unref(msg); |
| return; |
| } |
| |
| dbus_pending_call_set_notify(client->get_objects_call, |
| get_managed_objects_reply, |
| client, NULL); |
| |
| dbus_message_unref(msg); |
| } |
| |
| static void service_connect(DBusConnection *conn, void *user_data) |
| { |
| GDBusClient *client = user_data; |
| |
| g_dbus_client_ref(client); |
| |
| client->connected = TRUE; |
| |
| get_managed_objects(client); |
| |
| if (client->connect_func) |
| client->connect_func(conn, client->connect_data); |
| |
| g_dbus_client_unref(client); |
| } |
| |
| static void service_disconnect(DBusConnection *conn, void *user_data) |
| { |
| GDBusClient *client = user_data; |
| |
| client->connected = FALSE; |
| |
| g_list_free_full(client->proxy_list, proxy_free); |
| client->proxy_list = NULL; |
| |
| if (client->disconn_func) |
| client->disconn_func(conn, client->disconn_data); |
| } |
| |
| static DBusHandlerResult message_filter(DBusConnection *connection, |
| DBusMessage *message, void *user_data) |
| { |
| GDBusClient *client = user_data; |
| const char *sender, *path, *interface; |
| |
| if (dbus_message_get_type(message) != DBUS_MESSAGE_TYPE_SIGNAL) |
| return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; |
| |
| sender = dbus_message_get_sender(message); |
| if (sender == NULL) |
| return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; |
| |
| path = dbus_message_get_path(message); |
| interface = dbus_message_get_interface(message); |
| |
| if (g_str_has_prefix(path, client->base_path) == FALSE) |
| return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; |
| |
| if (g_str_equal(interface, DBUS_INTERFACE_PROPERTIES) == TRUE) |
| return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; |
| |
| if (client->signal_func) |
| client->signal_func(connection, message, client->signal_data); |
| |
| return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; |
| } |
| |
| GDBusClient *g_dbus_client_new(DBusConnection *connection, |
| const char *service, const char *path) |
| { |
| return g_dbus_client_new_full(connection, service, path, "/"); |
| } |
| |
| GDBusClient *g_dbus_client_new_full(DBusConnection *connection, |
| const char *service, |
| const char *path, |
| const char *root_path) |
| { |
| GDBusClient *client; |
| unsigned int i; |
| |
| if (!connection || !service) |
| return NULL; |
| |
| client = g_try_new0(GDBusClient, 1); |
| if (client == NULL) |
| return NULL; |
| |
| if (dbus_connection_add_filter(connection, message_filter, |
| client, NULL) == FALSE) { |
| g_free(client); |
| return NULL; |
| } |
| |
| client->dbus_conn = dbus_connection_ref(connection); |
| client->service_name = g_strdup(service); |
| client->base_path = g_strdup(path); |
| client->root_path = g_strdup(root_path); |
| client->connected = FALSE; |
| |
| client->match_rules = g_ptr_array_sized_new(1); |
| g_ptr_array_set_free_func(client->match_rules, g_free); |
| |
| client->watch = g_dbus_add_service_watch(connection, service, |
| service_connect, |
| service_disconnect, |
| client, NULL); |
| |
| if (!root_path) |
| return g_dbus_client_ref(client); |
| |
| client->added_watch = g_dbus_add_signal_watch(connection, service, |
| client->root_path, |
| DBUS_INTERFACE_OBJECT_MANAGER, |
| "InterfacesAdded", |
| interfaces_added, |
| client, NULL); |
| client->removed_watch = g_dbus_add_signal_watch(connection, service, |
| client->root_path, |
| DBUS_INTERFACE_OBJECT_MANAGER, |
| "InterfacesRemoved", |
| interfaces_removed, |
| client, NULL); |
| g_ptr_array_add(client->match_rules, g_strdup_printf("type='signal'," |
| "sender='%s',path_namespace='%s'", |
| client->service_name, client->base_path)); |
| |
| for (i = 0; i < client->match_rules->len; i++) { |
| modify_match(client->dbus_conn, "AddMatch", |
| g_ptr_array_index(client->match_rules, i)); |
| } |
| |
| return g_dbus_client_ref(client); |
| } |
| |
| GDBusClient *g_dbus_client_ref(GDBusClient *client) |
| { |
| if (client == NULL) |
| return NULL; |
| |
| __sync_fetch_and_add(&client->ref_count, 1); |
| |
| return client; |
| } |
| |
| void g_dbus_client_unref(GDBusClient *client) |
| { |
| unsigned int i; |
| |
| if (client == NULL) |
| return; |
| |
| if (__sync_sub_and_fetch(&client->ref_count, 1) > 0) |
| return; |
| |
| if (client->pending_call != NULL) { |
| dbus_pending_call_cancel(client->pending_call); |
| dbus_pending_call_unref(client->pending_call); |
| } |
| |
| if (client->get_objects_call != NULL) { |
| dbus_pending_call_cancel(client->get_objects_call); |
| dbus_pending_call_unref(client->get_objects_call); |
| } |
| |
| for (i = 0; i < client->match_rules->len; i++) { |
| modify_match(client->dbus_conn, "RemoveMatch", |
| g_ptr_array_index(client->match_rules, i)); |
| } |
| |
| g_ptr_array_free(client->match_rules, TRUE); |
| |
| dbus_connection_remove_filter(client->dbus_conn, |
| message_filter, client); |
| |
| g_list_free_full(client->proxy_list, proxy_free); |
| |
| /* |
| * Don't call disconn_func twice if disconnection |
| * was previously reported. |
| */ |
| if (client->disconn_func && client->connected) |
| client->disconn_func(client->dbus_conn, client->disconn_data); |
| |
| g_dbus_remove_watch(client->dbus_conn, client->watch); |
| g_dbus_remove_watch(client->dbus_conn, client->added_watch); |
| g_dbus_remove_watch(client->dbus_conn, client->removed_watch); |
| |
| dbus_connection_unref(client->dbus_conn); |
| |
| g_free(client->service_name); |
| g_free(client->base_path); |
| g_free(client->root_path); |
| |
| g_free(client); |
| } |
| |
| gboolean g_dbus_client_set_connect_watch(GDBusClient *client, |
| GDBusWatchFunction function, void *user_data) |
| { |
| if (client == NULL) |
| return FALSE; |
| |
| client->connect_func = function; |
| client->connect_data = user_data; |
| |
| return TRUE; |
| } |
| |
| gboolean g_dbus_client_set_disconnect_watch(GDBusClient *client, |
| GDBusWatchFunction function, void *user_data) |
| { |
| if (client == NULL) |
| return FALSE; |
| |
| client->disconn_func = function; |
| client->disconn_data = user_data; |
| |
| return TRUE; |
| } |
| |
| gboolean g_dbus_client_set_signal_watch(GDBusClient *client, |
| GDBusMessageFunction function, void *user_data) |
| { |
| if (client == NULL) |
| return FALSE; |
| |
| client->signal_func = function; |
| client->signal_data = user_data; |
| |
| return TRUE; |
| } |
| |
| gboolean g_dbus_client_set_ready_watch(GDBusClient *client, |
| GDBusClientFunction ready, void *user_data) |
| { |
| if (client == NULL) |
| return FALSE; |
| |
| client->ready = ready; |
| client->ready_data = user_data; |
| |
| return TRUE; |
| } |
| |
| gboolean g_dbus_client_set_proxy_handlers(GDBusClient *client, |
| GDBusProxyFunction proxy_added, |
| GDBusProxyFunction proxy_removed, |
| GDBusPropertyFunction property_changed, |
| void *user_data) |
| { |
| if (client == NULL) |
| return FALSE; |
| |
| client->proxy_added = proxy_added; |
| client->proxy_removed = proxy_removed; |
| client->property_changed = property_changed; |
| client->user_data = user_data; |
| |
| if (proxy_added || proxy_removed || property_changed) |
| get_managed_objects(client); |
| |
| return TRUE; |
| } |