/* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2006-2007 Nokia Corporation * Copyright (C) 2004-2009 Marcel Holtmann * Copyright (C) 2011 BMW Car IT GmbH. All rights reserved. * * * 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 #endif #include #include #include #include #include "lib/uuid.h" #include "src/plugin.h" #include "src/adapter.h" #include "src/device.h" #include "src/dbus-common.h" #include "src/profile.h" #include "src/uuid-helper.h" #include "src/log.h" #include "src/error.h" #include "avdtp.h" #include "media.h" #include "transport.h" #include "a2dp.h" #include "avrcp.h" #define MEDIA_INTERFACE "org.bluez.Media1" #define MEDIA_ENDPOINT_INTERFACE "org.bluez.MediaEndpoint1" #define MEDIA_PLAYER_INTERFACE "org.mpris.MediaPlayer2.Player" #define REQUEST_TIMEOUT (3 * 1000) /* 3 seconds */ struct media_adapter { struct btd_adapter *btd_adapter; GSList *endpoints; /* Endpoints list */ GSList *players; /* Players list */ }; struct endpoint_request { struct media_endpoint *endpoint; DBusMessage *msg; DBusPendingCall *call; media_endpoint_cb_t cb; GDestroyNotify destroy; void *user_data; }; struct media_endpoint { struct a2dp_sep *sep; char *sender; /* Endpoint DBus bus id */ char *path; /* Endpoint object path */ char *uuid; /* Endpoint property UUID */ uint8_t codec; /* Endpoint codec */ uint8_t *capabilities; /* Endpoint property capabilities */ size_t size; /* Endpoint capabilities size */ guint hs_watch; guint ag_watch; guint watch; GSList *requests; struct media_adapter *adapter; GSList *transports; }; struct media_player { struct media_adapter *adapter; struct avrcp_player *player; char *sender; /* Player DBus bus id */ char *path; /* Player object path */ GHashTable *settings; /* Player settings */ GHashTable *track; /* Player current track */ guint watch; guint properties_watch; guint seek_watch; char *status; uint32_t position; uint32_t duration; uint8_t volume; GTimer *timer; bool play; bool pause; bool next; bool previous; bool control; }; static GSList *adapters = NULL; static void endpoint_request_free(struct endpoint_request *request) { if (request->call) dbus_pending_call_unref(request->call); if (request->destroy) request->destroy(request->user_data); dbus_message_unref(request->msg); g_free(request); } static void media_endpoint_cancel(struct endpoint_request *request) { struct media_endpoint *endpoint = request->endpoint; if (request->call) dbus_pending_call_cancel(request->call); endpoint->requests = g_slist_remove(endpoint->requests, request); if (request->cb) request->cb(endpoint, NULL, -1, request->user_data); endpoint_request_free(request); } static void media_endpoint_cancel_all(struct media_endpoint *endpoint) { while (endpoint->requests != NULL) media_endpoint_cancel(endpoint->requests->data); } static void media_endpoint_destroy(struct media_endpoint *endpoint) { DBG("sender=%s path=%s", endpoint->sender, endpoint->path); media_endpoint_cancel_all(endpoint); g_slist_free_full(endpoint->transports, (GDestroyNotify) media_transport_destroy); g_dbus_remove_watch(btd_get_dbus_connection(), endpoint->watch); g_free(endpoint->capabilities); g_free(endpoint->sender); g_free(endpoint->path); g_free(endpoint->uuid); g_free(endpoint); } static struct media_endpoint *media_adapter_find_endpoint( struct media_adapter *adapter, const char *sender, const char *path, const char *uuid) { GSList *l; for (l = adapter->endpoints; l; l = l->next) { struct media_endpoint *endpoint = l->data; if (sender && g_strcmp0(endpoint->sender, sender) != 0) continue; if (path && g_strcmp0(endpoint->path, path) != 0) continue; if (uuid && strcasecmp(endpoint->uuid, uuid) != 0) continue; return endpoint; } return NULL; } static void media_endpoint_remove(struct media_endpoint *endpoint) { struct media_adapter *adapter = endpoint->adapter; if (endpoint->sep) { a2dp_remove_sep(endpoint->sep); return; } info("Endpoint unregistered: sender=%s path=%s", endpoint->sender, endpoint->path); adapter->endpoints = g_slist_remove(adapter->endpoints, endpoint); if (media_adapter_find_endpoint(adapter, NULL, NULL, endpoint->uuid) == NULL) btd_profile_remove_custom_prop(endpoint->uuid, "MediaEndpoints"); media_endpoint_destroy(endpoint); } static void media_endpoint_exit(DBusConnection *connection, void *user_data) { struct media_endpoint *endpoint = user_data; endpoint->watch = 0; media_endpoint_remove(endpoint); } static void clear_configuration(struct media_endpoint *endpoint, struct media_transport *transport) { DBusMessage *msg; const char *path; msg = dbus_message_new_method_call(endpoint->sender, endpoint->path, MEDIA_ENDPOINT_INTERFACE, "ClearConfiguration"); if (msg == NULL) { error("Couldn't allocate D-Bus message"); goto done; } path = media_transport_get_path(transport); dbus_message_append_args(msg, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID); g_dbus_send_message(btd_get_dbus_connection(), msg); done: endpoint->transports = g_slist_remove(endpoint->transports, transport); media_transport_destroy(transport); } static void clear_endpoint(struct media_endpoint *endpoint) { media_endpoint_cancel_all(endpoint); while (endpoint->transports != NULL) clear_configuration(endpoint, endpoint->transports->data); } static void endpoint_reply(DBusPendingCall *call, void *user_data) { struct endpoint_request *request = user_data; struct media_endpoint *endpoint = request->endpoint; DBusMessage *reply; DBusError err; gboolean value; void *ret = NULL; int size = -1; /* steal_reply will always return non-NULL since the callback * is only called after a reply has been received */ reply = dbus_pending_call_steal_reply(call); dbus_error_init(&err); if (dbus_set_error_from_message(&err, reply)) { error("Endpoint replied with an error: %s", err.name); /* Clear endpoint configuration in case of NO_REPLY error */ if (dbus_error_has_name(&err, DBUS_ERROR_NO_REPLY)) { if (request->cb) request->cb(endpoint, NULL, size, request->user_data); clear_endpoint(endpoint); dbus_message_unref(reply); dbus_error_free(&err); return; } dbus_error_free(&err); goto done; } if (dbus_message_is_method_call(request->msg, MEDIA_ENDPOINT_INTERFACE, "SelectConfiguration")) { DBusMessageIter args, array; uint8_t *configuration; dbus_message_iter_init(reply, &args); dbus_message_iter_recurse(&args, &array); dbus_message_iter_get_fixed_array(&array, &configuration, &size); ret = configuration; goto done; } else if (!dbus_message_get_args(reply, &err, DBUS_TYPE_INVALID)) { error("Wrong reply signature: %s", err.message); dbus_error_free(&err); goto done; } size = 1; value = TRUE; ret = &value; done: dbus_message_unref(reply); if (request->cb) request->cb(endpoint, ret, size, request->user_data); endpoint->requests = g_slist_remove(endpoint->requests, request); endpoint_request_free(request); } static gboolean media_endpoint_async_call(DBusMessage *msg, struct media_endpoint *endpoint, media_endpoint_cb_t cb, void *user_data, GDestroyNotify destroy) { struct endpoint_request *request; request = g_new0(struct endpoint_request, 1); /* Timeout should be less than avdtp request timeout (4 seconds) */ if (g_dbus_send_message_with_reply(btd_get_dbus_connection(), msg, &request->call, REQUEST_TIMEOUT) == FALSE) { error("D-Bus send failed"); g_free(request); return FALSE; } dbus_pending_call_set_notify(request->call, endpoint_reply, request, NULL); request->endpoint = endpoint; request->msg = msg; request->cb = cb; request->destroy = destroy; request->user_data = user_data; endpoint->requests = g_slist_append(endpoint->requests, request); DBG("Calling %s: name = %s path = %s", dbus_message_get_member(msg), dbus_message_get_destination(msg), dbus_message_get_path(msg)); return TRUE; } static gboolean select_configuration(struct media_endpoint *endpoint, uint8_t *capabilities, size_t length, media_endpoint_cb_t cb, void *user_data, GDestroyNotify destroy) { DBusMessage *msg; msg = dbus_message_new_method_call(endpoint->sender, endpoint->path, MEDIA_ENDPOINT_INTERFACE, "SelectConfiguration"); if (msg == NULL) { error("Couldn't allocate D-Bus message"); return FALSE; } dbus_message_append_args(msg, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &capabilities, length, DBUS_TYPE_INVALID); return media_endpoint_async_call(msg, endpoint, cb, user_data, destroy); } static int transport_device_cmp(gconstpointer data, gconstpointer user_data) { struct media_transport *transport = (struct media_transport *) data; const struct btd_device *device = user_data; const struct btd_device *dev = media_transport_get_dev(transport); if (device == dev) return 0; return -1; } static struct media_transport *find_device_transport( struct media_endpoint *endpoint, struct btd_device *device) { GSList *match; match = g_slist_find_custom(endpoint->transports, device, transport_device_cmp); if (match == NULL) return NULL; return match->data; } struct a2dp_config_data { struct a2dp_setup *setup; a2dp_endpoint_config_t cb; }; static gboolean set_configuration(struct media_endpoint *endpoint, uint8_t *configuration, size_t size, media_endpoint_cb_t cb, void *user_data, GDestroyNotify destroy) { struct a2dp_config_data *data = user_data; struct btd_device *device = a2dp_setup_get_device(data->setup); DBusConnection *conn = btd_get_dbus_connection(); DBusMessage *msg; const char *path; DBusMessageIter iter; struct media_transport *transport; transport = find_device_transport(endpoint, device); if (transport != NULL) return FALSE; transport = media_transport_create(device, configuration, size, endpoint); if (transport == NULL) return FALSE; msg = dbus_message_new_method_call(endpoint->sender, endpoint->path, MEDIA_ENDPOINT_INTERFACE, "SetConfiguration"); if (msg == NULL) { error("Couldn't allocate D-Bus message"); media_transport_destroy(transport); return FALSE; } endpoint->transports = g_slist_append(endpoint->transports, transport); dbus_message_iter_init_append(msg, &iter); path = media_transport_get_path(transport); dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &path); g_dbus_get_properties(conn, path, "org.bluez.MediaTransport1", &iter); return media_endpoint_async_call(msg, endpoint, cb, user_data, destroy); } static void release_endpoint(struct media_endpoint *endpoint) { DBusMessage *msg; DBG("sender=%s path=%s", endpoint->sender, endpoint->path); /* already exit */ if (endpoint->watch == 0) goto done; clear_endpoint(endpoint); msg = dbus_message_new_method_call(endpoint->sender, endpoint->path, MEDIA_ENDPOINT_INTERFACE, "Release"); if (msg == NULL) { error("Couldn't allocate D-Bus message"); return; } g_dbus_send_message(btd_get_dbus_connection(), msg); done: media_endpoint_remove(endpoint); } static const char *get_name(struct a2dp_sep *sep, void *user_data) { struct media_endpoint *endpoint = user_data; return endpoint->sender; } static size_t get_capabilities(struct a2dp_sep *sep, uint8_t **capabilities, void *user_data) { struct media_endpoint *endpoint = user_data; *capabilities = endpoint->capabilities; return endpoint->size; } struct a2dp_select_data { struct a2dp_setup *setup; a2dp_endpoint_select_t cb; }; static void select_cb(struct media_endpoint *endpoint, void *ret, int size, void *user_data) { struct a2dp_select_data *data = user_data; data->cb(data->setup, ret, size); } static int select_config(struct a2dp_sep *sep, uint8_t *capabilities, size_t length, struct a2dp_setup *setup, a2dp_endpoint_select_t cb, void *user_data) { struct media_endpoint *endpoint = user_data; struct a2dp_select_data *data; data = g_new0(struct a2dp_select_data, 1); data->setup = setup; data->cb = cb; if (select_configuration(endpoint, capabilities, length, select_cb, data, g_free) == TRUE) return 0; g_free(data); return -ENOMEM; } static void config_cb(struct media_endpoint *endpoint, void *ret, int size, void *user_data) { struct a2dp_config_data *data = user_data; data->cb(data->setup, ret ? TRUE : FALSE); } static int set_config(struct a2dp_sep *sep, uint8_t *configuration, size_t length, struct a2dp_setup *setup, a2dp_endpoint_config_t cb, void *user_data) { struct media_endpoint *endpoint = user_data; struct a2dp_config_data *data; data = g_new0(struct a2dp_config_data, 1); data->setup = setup; data->cb = cb; if (set_configuration(endpoint, configuration, length, config_cb, data, g_free) == TRUE) return 0; g_free(data); return -ENOMEM; } static void clear_config(struct a2dp_sep *sep, void *user_data) { struct media_endpoint *endpoint = user_data; clear_endpoint(endpoint); } static void set_delay(struct a2dp_sep *sep, uint16_t delay, void *user_data) { struct media_endpoint *endpoint = user_data; if (endpoint->transports == NULL) return; media_transport_update_delay(endpoint->transports->data, delay); } static struct a2dp_endpoint a2dp_endpoint = { .get_name = get_name, .get_capabilities = get_capabilities, .select_configuration = select_config, .set_configuration = set_config, .clear_configuration = clear_config, .set_delay = set_delay }; static void a2dp_destroy_endpoint(void *user_data) { struct media_endpoint *endpoint = user_data; endpoint->sep = NULL; release_endpoint(endpoint); } static gboolean endpoint_init_a2dp_source(struct media_endpoint *endpoint, gboolean delay_reporting, int *err) { endpoint->sep = a2dp_add_sep(endpoint->adapter->btd_adapter, AVDTP_SEP_TYPE_SOURCE, endpoint->codec, delay_reporting, &a2dp_endpoint, endpoint, a2dp_destroy_endpoint, err); if (endpoint->sep == NULL) return FALSE; return TRUE; } static gboolean endpoint_init_a2dp_sink(struct media_endpoint *endpoint, gboolean delay_reporting, int *err) { endpoint->sep = a2dp_add_sep(endpoint->adapter->btd_adapter, AVDTP_SEP_TYPE_SINK, endpoint->codec, delay_reporting, &a2dp_endpoint, endpoint, a2dp_destroy_endpoint, err); if (endpoint->sep == NULL) return FALSE; return TRUE; } static struct media_adapter *find_adapter(struct btd_device *device) { GSList *l; for (l = adapters; l; l = l->next) { struct media_adapter *adapter = l->data; if (adapter->btd_adapter == device_get_adapter(device)) return adapter; } return NULL; } static bool endpoint_properties_exists(const char *uuid, struct btd_device *dev, void *user_data) { struct media_adapter *adapter; adapter = find_adapter(dev); if (adapter == NULL) return false; if (media_adapter_find_endpoint(adapter, NULL, NULL, uuid) == NULL) return false; return true; } static void append_endpoint(struct media_endpoint *endpoint, DBusMessageIter *dict) { DBusMessageIter entry, var, props; dbus_message_iter_open_container(dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry); dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &endpoint->sender); dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, "a{sv}", &var); dbus_message_iter_open_container(&var, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &props); dict_append_entry(&props, "Path", DBUS_TYPE_OBJECT_PATH, &endpoint->path); dict_append_entry(&props, "Codec", DBUS_TYPE_BYTE, &endpoint->codec); dict_append_array(&props, "Capabilities", DBUS_TYPE_BYTE, &endpoint->capabilities, endpoint->size); dbus_message_iter_close_container(&var, &props); dbus_message_iter_close_container(&entry, &var); dbus_message_iter_close_container(dict, &entry); } static bool endpoint_properties_get(const char *uuid, struct btd_device *dev, DBusMessageIter *iter, void *user_data) { struct media_adapter *adapter; DBusMessageIter dict; GSList *l; adapter = find_adapter(dev); if (adapter == NULL) return false; dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); for (l = adapter->endpoints; l; l = l->next) { struct media_endpoint *endpoint = l->data; if (strcasecmp(endpoint->uuid, uuid) != 0) continue; append_endpoint(endpoint, &dict); } dbus_message_iter_close_container(iter, &dict); return true; } static struct media_endpoint *media_endpoint_create(struct media_adapter *adapter, const char *sender, const char *path, const char *uuid, gboolean delay_reporting, uint8_t codec, uint8_t *capabilities, int size, int *err) { struct media_endpoint *endpoint; gboolean succeeded; endpoint = g_new0(struct media_endpoint, 1); endpoint->sender = g_strdup(sender); endpoint->path = g_strdup(path); endpoint->uuid = g_strdup(uuid); endpoint->codec = codec; if (size > 0) { endpoint->capabilities = g_new(uint8_t, size); memcpy(endpoint->capabilities, capabilities, size); endpoint->size = size; } endpoint->adapter = adapter; if (strcasecmp(uuid, A2DP_SOURCE_UUID) == 0) succeeded = endpoint_init_a2dp_source(endpoint, delay_reporting, err); else if (strcasecmp(uuid, A2DP_SINK_UUID) == 0) succeeded = endpoint_init_a2dp_sink(endpoint, delay_reporting, err); else if (strcasecmp(uuid, HFP_AG_UUID) == 0 || strcasecmp(uuid, HSP_AG_UUID) == 0) succeeded = TRUE; else if (strcasecmp(uuid, HFP_HS_UUID) == 0 || strcasecmp(uuid, HSP_HS_UUID) == 0) succeeded = TRUE; else { succeeded = FALSE; if (err) *err = -EINVAL; } if (!succeeded) { media_endpoint_destroy(endpoint); return NULL; } endpoint->watch = g_dbus_add_disconnect_watch(btd_get_dbus_connection(), sender, media_endpoint_exit, endpoint, NULL); if (media_adapter_find_endpoint(adapter, NULL, NULL, uuid) == NULL) { btd_profile_add_custom_prop(uuid, "a{sv}", "MediaEndpoints", endpoint_properties_exists, endpoint_properties_get, NULL); } adapter->endpoints = g_slist_append(adapter->endpoints, endpoint); info("Endpoint registered: sender=%s path=%s", sender, path); if (err) *err = 0; return endpoint; } static int parse_properties(DBusMessageIter *props, const char **uuid, gboolean *delay_reporting, uint8_t *codec, uint8_t **capabilities, int *size) { gboolean has_uuid = FALSE; gboolean has_codec = FALSE; while (dbus_message_iter_get_arg_type(props) == DBUS_TYPE_DICT_ENTRY) { const char *key; DBusMessageIter value, entry; int var; dbus_message_iter_recurse(props, &entry); dbus_message_iter_get_basic(&entry, &key); dbus_message_iter_next(&entry); dbus_message_iter_recurse(&entry, &value); var = dbus_message_iter_get_arg_type(&value); if (strcasecmp(key, "UUID") == 0) { if (var != DBUS_TYPE_STRING) return -EINVAL; dbus_message_iter_get_basic(&value, uuid); has_uuid = TRUE; } else if (strcasecmp(key, "Codec") == 0) { if (var != DBUS_TYPE_BYTE) return -EINVAL; dbus_message_iter_get_basic(&value, codec); has_codec = TRUE; } else if (strcasecmp(key, "DelayReporting") == 0) { if (var != DBUS_TYPE_BOOLEAN) return -EINVAL; dbus_message_iter_get_basic(&value, delay_reporting); } else if (strcasecmp(key, "Capabilities") == 0) { DBusMessageIter array; if (var != DBUS_TYPE_ARRAY) return -EINVAL; dbus_message_iter_recurse(&value, &array); dbus_message_iter_get_fixed_array(&array, capabilities, size); } dbus_message_iter_next(props); } return (has_uuid && has_codec) ? 0 : -EINVAL; } static DBusMessage *register_endpoint(DBusConnection *conn, DBusMessage *msg, void *data) { struct media_adapter *adapter = data; DBusMessageIter args, props; const char *sender, *path, *uuid; gboolean delay_reporting = FALSE; uint8_t codec; uint8_t *capabilities; int size = 0; int err; sender = dbus_message_get_sender(msg); dbus_message_iter_init(msg, &args); dbus_message_iter_get_basic(&args, &path); dbus_message_iter_next(&args); if (media_adapter_find_endpoint(adapter, sender, path, NULL) != NULL) return btd_error_already_exists(msg); dbus_message_iter_recurse(&args, &props); if (dbus_message_iter_get_arg_type(&props) != DBUS_TYPE_DICT_ENTRY) return btd_error_invalid_args(msg); if (parse_properties(&props, &uuid, &delay_reporting, &codec, &capabilities, &size) < 0) return btd_error_invalid_args(msg); if (media_endpoint_create(adapter, sender, path, uuid, delay_reporting, codec, capabilities, size, &err) == NULL) { if (err == -EPROTONOSUPPORT) return btd_error_not_supported(msg); else return btd_error_invalid_args(msg); } return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); } static DBusMessage *unregister_endpoint(DBusConnection *conn, DBusMessage *msg, void *data) { struct media_adapter *adapter = data; struct media_endpoint *endpoint; const char *sender, *path; if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) return btd_error_invalid_args(msg); sender = dbus_message_get_sender(msg); endpoint = media_adapter_find_endpoint(adapter, sender, path, NULL); if (endpoint == NULL) return btd_error_does_not_exist(msg); media_endpoint_remove(endpoint); return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); } static struct media_player *media_adapter_find_player( struct media_adapter *adapter, const char *sender, const char *path) { GSList *l; for (l = adapter->players; l; l = l->next) { struct media_player *mp = l->data; if (sender && g_strcmp0(mp->sender, sender) != 0) continue; if (path && g_strcmp0(mp->path, path) != 0) continue; return mp; } return NULL; } static void release_player(struct media_player *mp) { DBusMessage *msg; DBG("sender=%s path=%s", mp->sender, mp->path); msg = dbus_message_new_method_call(mp->sender, mp->path, MEDIA_PLAYER_INTERFACE, "Release"); if (msg == NULL) { error("Couldn't allocate D-Bus message"); return; } g_dbus_send_message(btd_get_dbus_connection(), msg); } static void media_player_free(gpointer data) { DBusConnection *conn = btd_get_dbus_connection(); struct media_player *mp = data; struct media_adapter *adapter = mp->adapter; if (mp->player) { adapter->players = g_slist_remove(adapter->players, mp); release_player(mp); } g_dbus_remove_watch(conn, mp->watch); g_dbus_remove_watch(conn, mp->properties_watch); g_dbus_remove_watch(conn, mp->seek_watch); if (mp->track) g_hash_table_unref(mp->track); if (mp->settings) g_hash_table_unref(mp->settings); g_timer_destroy(mp->timer); g_free(mp->sender); g_free(mp->path); g_free(mp->status); g_free(mp); } static void media_player_destroy(struct media_player *mp) { struct media_adapter *adapter = mp->adapter; DBG("sender=%s path=%s", mp->sender, mp->path); if (mp->player) { struct avrcp_player *player = mp->player; mp->player = NULL; adapter->players = g_slist_remove(adapter->players, mp); avrcp_unregister_player(player); return; } media_player_free(mp); } static void media_player_remove(struct media_player *mp) { info("Player unregistered: sender=%s path=%s", mp->sender, mp->path); media_player_destroy(mp); } static GList *list_settings(void *user_data) { struct media_player *mp = user_data; DBG(""); if (mp->settings == NULL) return NULL; return g_hash_table_get_keys(mp->settings); } static const char *get_setting(const char *key, void *user_data) { struct media_player *mp = user_data; DBG("%s", key); return g_hash_table_lookup(mp->settings, key); } static void set_shuffle_setting(DBusMessageIter *iter, const char *value) { const char *key = "Shuffle"; dbus_bool_t val; DBusMessageIter var; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &key); dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, DBUS_TYPE_BOOLEAN_AS_STRING, &var); val = strcasecmp(value, "off") != 0; dbus_message_iter_append_basic(&var, DBUS_TYPE_BOOLEAN, &val); dbus_message_iter_close_container(iter, &var); } static const char *repeat_to_loop_status(const char *value) { if (strcasecmp(value, "off") == 0) return "None"; else if (strcasecmp(value, "singletrack") == 0) return "Track"; else if (strcasecmp(value, "alltracks") == 0) return "Playlist"; return NULL; } static void set_repeat_setting(DBusMessageIter *iter, const char *value) { const char *key = "LoopStatus"; const char *val; DBusMessageIter var; dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &key); dbus_message_iter_open_container(iter, DBUS_TYPE_VARIANT, DBUS_TYPE_STRING_AS_STRING, &var); val = repeat_to_loop_status(value); dbus_message_iter_append_basic(&var, DBUS_TYPE_STRING, &val); dbus_message_iter_close_container(iter, &var); } static int set_setting(const char *key, const char *value, void *user_data) { struct media_player *mp = user_data; const char *iface = MEDIA_PLAYER_INTERFACE; DBusMessage *msg; DBusMessageIter iter; DBG("%s = %s", key, value); if (!g_hash_table_lookup(mp->settings, key)) return -EINVAL; msg = dbus_message_new_method_call(mp->sender, mp->path, DBUS_INTERFACE_PROPERTIES, "Set"); if (msg == NULL) { error("Couldn't allocate D-Bus message"); return -ENOMEM; } dbus_message_iter_init_append(msg, &iter); dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &iface); if (strcasecmp(key, "Shuffle") == 0) set_shuffle_setting(&iter, value); else if (strcasecmp(key, "Repeat") == 0) set_repeat_setting(&iter, value); g_dbus_send_message(btd_get_dbus_connection(), msg); return 0; } static GList *list_metadata(void *user_data) { struct media_player *mp = user_data; DBG(""); if (mp->track == NULL) return NULL; return g_hash_table_get_keys(mp->track); } static uint64_t get_uid(void *user_data) { struct media_player *mp = user_data; DBG("%p", mp->track); if (mp->track == NULL) return UINT64_MAX; return 0; } static const char *get_metadata(const char *key, void *user_data) { struct media_player *mp = user_data; DBG("%s", key); if (mp->track == NULL) return NULL; return g_hash_table_lookup(mp->track, key); } static const char *get_status(void *user_data) { struct media_player *mp = user_data; return mp->status; } static uint32_t get_position(void *user_data) { struct media_player *mp = user_data; double timedelta; uint32_t sec, msec; if (mp->status == NULL || strcasecmp(mp->status, "Playing") != 0) return mp->position; timedelta = g_timer_elapsed(mp->timer, NULL); sec = (uint32_t) timedelta; msec = (uint32_t) ((timedelta - sec) * 1000); return mp->position + sec * 1000 + msec; } static uint32_t get_duration(void *user_data) { struct media_player *mp = user_data; return mp->duration; } static void set_volume(uint8_t volume, struct btd_device *dev, void *user_data) { struct media_player *mp = user_data; GSList *l; if (mp->volume == volume) return; mp->volume = volume; for (l = mp->adapter->endpoints; l; l = l->next) { struct media_endpoint *endpoint = l->data; struct media_transport *transport; /* Volume is A2DP only */ if (endpoint->sep == NULL) continue; transport = find_device_transport(endpoint, dev); if (transport == NULL) continue; media_transport_update_volume(transport, volume); } } static bool media_player_send(struct media_player *mp, const char *name) { DBusMessage *msg; msg = dbus_message_new_method_call(mp->sender, mp->path, MEDIA_PLAYER_INTERFACE, name); if (msg == NULL) { error("Couldn't allocate D-Bus message"); return false; } g_dbus_send_message(btd_get_dbus_connection(), msg); return true; } static bool play(void *user_data) { struct media_player *mp = user_data; DBG(""); if (!mp->play || !mp->control) return false; return media_player_send(mp, "Play"); } static bool stop(void *user_data) { struct media_player *mp = user_data; DBG(""); if (!mp->control) return false; return media_player_send(mp, "Stop"); } static bool pause(void *user_data) { struct media_player *mp = user_data; DBG(""); if (!mp->pause || !mp->control) return false; return media_player_send(mp, "Pause"); } static bool next(void *user_data) { struct media_player *mp = user_data; DBG(""); if (!mp->next || !mp->control) return false; return media_player_send(mp, "Next"); } static bool previous(void *user_data) { struct media_player *mp = user_data; DBG(""); if (!mp->previous || !mp->control) return false; return media_player_send(mp, "Previous"); } static struct avrcp_player_cb player_cb = { .list_settings = list_settings, .get_setting = get_setting, .set_setting = set_setting, .list_metadata = list_metadata, .get_uid = get_uid, .get_metadata = get_metadata, .get_position = get_position, .get_duration = get_duration, .get_status = get_status, .set_volume = set_volume, .play = play, .stop = stop, .pause = pause, .next = next, .previous = previous, }; static void media_player_exit(DBusConnection *connection, void *user_data) { struct media_player *mp = user_data; mp->watch = 0; media_player_remove(mp); } static gboolean set_status(struct media_player *mp, DBusMessageIter *iter) { const char *value; if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING) return FALSE; dbus_message_iter_get_basic(iter, &value); DBG("Status=%s", value); if (g_strcmp0(mp->status, value) == 0) return TRUE; mp->position = get_position(mp); g_timer_start(mp->timer); g_free(mp->status); mp->status = g_strdup(value); avrcp_player_event(mp->player, AVRCP_EVENT_STATUS_CHANGED, mp->status); return TRUE; } static gboolean set_position(struct media_player *mp, DBusMessageIter *iter) { uint64_t value; const char *status; if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_INT64) return FALSE; dbus_message_iter_get_basic(iter, &value); value /= 1000; if (value > get_position(mp)) status = "forward-seek"; else status = "reverse-seek"; mp->position = value; g_timer_start(mp->timer); DBG("Position=%u", mp->position); if (!mp->position) { avrcp_player_event(mp->player, AVRCP_EVENT_TRACK_REACHED_START, NULL); return TRUE; } /* * If position is the maximum value allowed or greater than track's * duration, we send a track-reached-end event. */ if (mp->position == UINT32_MAX || mp->position >= mp->duration) { avrcp_player_event(mp->player, AVRCP_EVENT_TRACK_REACHED_END, NULL); return TRUE; } /* Send a status change to force resync the position */ avrcp_player_event(mp->player, AVRCP_EVENT_STATUS_CHANGED, status); return TRUE; } static void set_metadata(struct media_player *mp, const char *key, const char *value) { DBG("%s=%s", key, value); g_hash_table_replace(mp->track, g_strdup(key), g_strdup(value)); } static gboolean parse_string_metadata(struct media_player *mp, const char *key, DBusMessageIter *iter) { const char *value; if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING) return FALSE; dbus_message_iter_get_basic(iter, &value); set_metadata(mp, key, value); return TRUE; } static gboolean parse_array_metadata(struct media_player *mp, const char *key, DBusMessageIter *iter) { DBusMessageIter array; const char *value; if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_ARRAY) return FALSE; dbus_message_iter_recurse(iter, &array); if (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_INVALID) return TRUE; if (dbus_message_iter_get_arg_type(&array) != DBUS_TYPE_STRING) return FALSE; dbus_message_iter_get_basic(&array, &value); set_metadata(mp, key, value); return TRUE; } static gboolean parse_int64_metadata(struct media_player *mp, const char *key, DBusMessageIter *iter) { uint64_t value; char valstr[20]; int type; type = dbus_message_iter_get_arg_type(iter); if (type == DBUS_TYPE_UINT64) warn("expected DBUS_TYPE_INT64 got DBUS_TYPE_UINT64"); else if (type != DBUS_TYPE_INT64) return FALSE; dbus_message_iter_get_basic(iter, &value); if (strcasecmp(key, "Duration") == 0) { value /= 1000; mp->duration = value; } snprintf(valstr, 20, "%" PRIu64, value); set_metadata(mp, key, valstr); return TRUE; } static gboolean parse_int32_metadata(struct media_player *mp, const char *key, DBusMessageIter *iter) { uint32_t value; char valstr[20]; int type; type = dbus_message_iter_get_arg_type(iter); if (type == DBUS_TYPE_UINT32) warn("expected DBUS_TYPE_INT32 got DBUS_TYPE_UINT32"); else if (type != DBUS_TYPE_INT32) return FALSE; dbus_message_iter_get_basic(iter, &value); snprintf(valstr, 20, "%u", value); set_metadata(mp, key, valstr); return TRUE; } static gboolean parse_player_metadata(struct media_player *mp, DBusMessageIter *iter) { DBusMessageIter dict; DBusMessageIter var; int ctype; gboolean title = FALSE; uint64_t uid; ctype = dbus_message_iter_get_arg_type(iter); if (ctype != DBUS_TYPE_ARRAY) return FALSE; dbus_message_iter_recurse(iter, &dict); if (mp->track != NULL) g_hash_table_unref(mp->track); mp->track = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); while ((ctype = dbus_message_iter_get_arg_type(&dict)) != DBUS_TYPE_INVALID) { DBusMessageIter entry; const char *key; if (ctype != DBUS_TYPE_DICT_ENTRY) return FALSE; dbus_message_iter_recurse(&dict, &entry); if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_STRING) return FALSE; dbus_message_iter_get_basic(&entry, &key); dbus_message_iter_next(&entry); if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_VARIANT) return FALSE; dbus_message_iter_recurse(&entry, &var); if (strcasecmp(key, "xesam:title") == 0) { if (!parse_string_metadata(mp, "Title", &var)) return FALSE; title = TRUE; } else if (strcasecmp(key, "xesam:artist") == 0) { if (!parse_array_metadata(mp, "Artist", &var)) return FALSE; } else if (strcasecmp(key, "xesam:album") == 0) { if (!parse_string_metadata(mp, "Album", &var)) return FALSE; } else if (strcasecmp(key, "xesam:genre") == 0) { if (!parse_array_metadata(mp, "Genre", &var)) return FALSE; } else if (strcasecmp(key, "mpris:length") == 0) { if (!parse_int64_metadata(mp, "Duration", &var)) return FALSE; } else if (strcasecmp(key, "xesam:trackNumber") == 0) { if (!parse_int32_metadata(mp, "TrackNumber", &var)) return FALSE; } else DBG("%s not supported, ignoring", key); dbus_message_iter_next(&dict); } if (title == FALSE) g_hash_table_insert(mp->track, g_strdup("Title"), g_strdup("")); mp->position = 0; g_timer_start(mp->timer); uid = get_uid(mp); avrcp_player_event(mp->player, AVRCP_EVENT_TRACK_CHANGED, &uid); avrcp_player_event(mp->player, AVRCP_EVENT_TRACK_REACHED_START, NULL); return TRUE; } static gboolean set_property(struct media_player *mp, const char *key, const char *value) { const char *curval; curval = g_hash_table_lookup(mp->settings, key); if (g_strcmp0(curval, value) == 0) return TRUE; DBG("%s=%s", key, value); g_hash_table_replace(mp->settings, g_strdup(key), g_strdup(value)); avrcp_player_event(mp->player, AVRCP_EVENT_SETTINGS_CHANGED, key); return TRUE; } static gboolean set_shuffle(struct media_player *mp, DBusMessageIter *iter) { dbus_bool_t value; const char *strvalue; if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_BOOLEAN) return FALSE; dbus_message_iter_get_basic(iter, &value); strvalue = value ? "alltracks" : "off"; return set_property(mp, "Shuffle", strvalue); } static const char *loop_status_to_repeat(const char *value) { if (strcasecmp(value, "None") == 0) return "off"; else if (strcasecmp(value, "Track") == 0) return "singletrack"; else if (strcasecmp(value, "Playlist") == 0) return "alltracks"; return NULL; } static gboolean set_repeat(struct media_player *mp, DBusMessageIter *iter) { const char *value; if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING) return FALSE; dbus_message_iter_get_basic(iter, &value); value = loop_status_to_repeat(value); if (value == NULL) return FALSE; return set_property(mp, "Repeat", value); } static gboolean set_flag(struct media_player *mp, DBusMessageIter *iter, bool *var) { dbus_bool_t value; if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_BOOLEAN) return FALSE; dbus_message_iter_get_basic(iter, &value); *var = value; return TRUE; } static gboolean set_player_property(struct media_player *mp, const char *key, DBusMessageIter *entry) { DBusMessageIter var; if (dbus_message_iter_get_arg_type(entry) != DBUS_TYPE_VARIANT) return FALSE; dbus_message_iter_recurse(entry, &var); if (strcasecmp(key, "PlaybackStatus") == 0) return set_status(mp, &var); if (strcasecmp(key, "Position") == 0) return set_position(mp, &var); if (strcasecmp(key, "Metadata") == 0) return parse_player_metadata(mp, &var); if (strcasecmp(key, "Shuffle") == 0) return set_shuffle(mp, &var); if (strcasecmp(key, "LoopStatus") == 0) return set_repeat(mp, &var); if (strcasecmp(key, "CanPlay") == 0) return set_flag(mp, &var, &mp->play); if (strcasecmp(key, "CanPause") == 0) return set_flag(mp, &var, &mp->pause); if (strcasecmp(key, "CanGoNext") == 0) return set_flag(mp, &var, &mp->next); if (strcasecmp(key, "CanGoPrevious") == 0) return set_flag(mp, &var, &mp->previous); if (strcasecmp(key, "CanControl") == 0) return set_flag(mp, &var, &mp->control); DBG("%s not supported, ignoring", key); return TRUE; } static gboolean parse_player_properties(struct media_player *mp, DBusMessageIter *iter) { DBusMessageIter dict; int ctype; ctype = dbus_message_iter_get_arg_type(iter); if (ctype != DBUS_TYPE_ARRAY) return FALSE; dbus_message_iter_recurse(iter, &dict); while ((ctype = dbus_message_iter_get_arg_type(&dict)) != DBUS_TYPE_INVALID) { DBusMessageIter entry; const char *key; if (ctype != DBUS_TYPE_DICT_ENTRY) return FALSE; dbus_message_iter_recurse(&dict, &entry); if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_STRING) return FALSE; dbus_message_iter_get_basic(&entry, &key); dbus_message_iter_next(&entry); if (set_player_property(mp, key, &entry) == FALSE) return FALSE; dbus_message_iter_next(&dict); } return TRUE; } static gboolean properties_changed(DBusConnection *connection, DBusMessage *msg, void *user_data) { struct media_player *mp = user_data; DBusMessageIter iter; DBG("sender=%s path=%s", mp->sender, mp->path); dbus_message_iter_init(msg, &iter); dbus_message_iter_next(&iter); parse_player_properties(mp, &iter); return TRUE; } static gboolean position_changed(DBusConnection *connection, DBusMessage *msg, void *user_data) { struct media_player *mp = user_data; DBusMessageIter iter; DBG("sender=%s path=%s", mp->sender, mp->path); dbus_message_iter_init(msg, &iter); set_position(mp, &iter); return TRUE; } static struct media_player *media_player_create(struct media_adapter *adapter, const char *sender, const char *path, int *err) { DBusConnection *conn = btd_get_dbus_connection(); struct media_player *mp; mp = g_new0(struct media_player, 1); mp->adapter = adapter; mp->sender = g_strdup(sender); mp->path = g_strdup(path); mp->timer = g_timer_new(); mp->watch = g_dbus_add_disconnect_watch(conn, sender, media_player_exit, mp, NULL); mp->properties_watch = g_dbus_add_properties_watch(conn, sender, path, MEDIA_PLAYER_INTERFACE, properties_changed, mp, NULL); mp->seek_watch = g_dbus_add_signal_watch(conn, sender, path, MEDIA_PLAYER_INTERFACE, "Seeked", position_changed, mp, NULL); mp->player = avrcp_register_player(adapter->btd_adapter, &player_cb, mp, media_player_free); if (!mp->player) { if (err) *err = -EPROTONOSUPPORT; media_player_destroy(mp); return NULL; } mp->settings = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); adapter->players = g_slist_append(adapter->players, mp); info("Player registered: sender=%s path=%s", sender, path); if (err) *err = 0; return mp; } static DBusMessage *register_player(DBusConnection *conn, DBusMessage *msg, void *data) { struct media_adapter *adapter = data; struct media_player *mp; DBusMessageIter args; const char *sender, *path; int err; sender = dbus_message_get_sender(msg); dbus_message_iter_init(msg, &args); dbus_message_iter_get_basic(&args, &path); dbus_message_iter_next(&args); if (media_adapter_find_player(adapter, sender, path) != NULL) return btd_error_already_exists(msg); mp = media_player_create(adapter, sender, path, &err); if (mp == NULL) { if (err == -EPROTONOSUPPORT) return btd_error_not_supported(msg); else return btd_error_invalid_args(msg); } if (parse_player_properties(mp, &args) == FALSE) { media_player_destroy(mp); return btd_error_invalid_args(msg); } return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); } static DBusMessage *unregister_player(DBusConnection *conn, DBusMessage *msg, void *data) { struct media_adapter *adapter = data; struct media_player *player; const char *sender, *path; if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) return btd_error_invalid_args(msg); sender = dbus_message_get_sender(msg); player = media_adapter_find_player(adapter, sender, path); if (player == NULL) return btd_error_does_not_exist(msg); media_player_remove(player); return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); } static const GDBusMethodTable media_methods[] = { { GDBUS_METHOD("RegisterEndpoint", GDBUS_ARGS({ "endpoint", "o" }, { "properties", "a{sv}" }), NULL, register_endpoint) }, { GDBUS_METHOD("UnregisterEndpoint", GDBUS_ARGS({ "endpoint", "o" }), NULL, unregister_endpoint) }, { GDBUS_EXPERIMENTAL_METHOD("RegisterPlayer", GDBUS_ARGS({ "player", "o" }, { "properties", "a{sv}" }), NULL, register_player) }, { GDBUS_EXPERIMENTAL_METHOD("UnregisterPlayer", GDBUS_ARGS({ "player", "o" }), NULL, unregister_player) }, { }, }; static void path_free(void *data) { struct media_adapter *adapter = data; while (adapter->endpoints) release_endpoint(adapter->endpoints->data); while (adapter->players) media_player_destroy(adapter->players->data); adapters = g_slist_remove(adapters, adapter); btd_adapter_unref(adapter->btd_adapter); g_free(adapter); } int media_register(struct btd_adapter *btd_adapter) { struct media_adapter *adapter; adapter = g_new0(struct media_adapter, 1); adapter->btd_adapter = btd_adapter_ref(btd_adapter); if (!g_dbus_register_interface(btd_get_dbus_connection(), adapter_get_path(btd_adapter), MEDIA_INTERFACE, media_methods, NULL, NULL, adapter, path_free)) { error("D-Bus failed to register %s path", adapter_get_path(btd_adapter)); path_free(adapter); return -1; } adapters = g_slist_append(adapters, adapter); return 0; } void media_unregister(struct btd_adapter *btd_adapter) { GSList *l; for (l = adapters; l; l = l->next) { struct media_adapter *adapter = l->data; if (adapter->btd_adapter == btd_adapter) { g_dbus_unregister_interface(btd_get_dbus_connection(), adapter_get_path(btd_adapter), MEDIA_INTERFACE); return; } } } struct a2dp_sep *media_endpoint_get_sep(struct media_endpoint *endpoint) { return endpoint->sep; } const char *media_endpoint_get_uuid(struct media_endpoint *endpoint) { return endpoint->uuid; } uint8_t media_endpoint_get_codec(struct media_endpoint *endpoint) { return endpoint->codec; }