// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2006-2010 Nokia Corporation * Copyright (C) 2004-2010 Marcel Holtmann * Copyright (C) 2011 Texas Instruments, Inc. * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include "lib/bluetooth.h" #include "lib/sdp.h" #include "lib/sdp_lib.h" #include "lib/uuid.h" #include "gdbus/gdbus.h" #include "src/adapter.h" #include "src/device.h" #include "src/profile.h" #include "src/service.h" #include "src/log.h" #include "src/error.h" #include "src/sdpd.h" #include "src/uuid-helper.h" #include "src/dbus-common.h" #include "avctp.h" #include "control.h" #include "player.h" static GSList *devices = NULL; struct control { struct btd_device *dev; struct avctp *session; struct btd_service *target; struct btd_service *remote; unsigned int avctp_id; const char *player; }; static void state_changed(struct btd_device *dev, avctp_state_t old_state, avctp_state_t new_state, int err, void *user_data) { struct control *control = user_data; DBusConnection *conn = btd_get_dbus_connection(); const char *path = device_get_path(dev); switch (new_state) { case AVCTP_STATE_DISCONNECTED: control->session = NULL; control->player = NULL; g_dbus_emit_property_changed(conn, path, AUDIO_CONTROL_INTERFACE, "Connected"); g_dbus_emit_property_changed(conn, path, AUDIO_CONTROL_INTERFACE, "Player"); break; case AVCTP_STATE_CONNECTING: if (control->session) break; control->session = avctp_get(dev); break; case AVCTP_STATE_CONNECTED: g_dbus_emit_property_changed(conn, path, AUDIO_CONTROL_INTERFACE, "Connected"); break; case AVCTP_STATE_BROWSING_CONNECTING: case AVCTP_STATE_BROWSING_CONNECTED: default: return; } } int control_connect(struct btd_service *service) { struct control *control = btd_service_get_user_data(service); if (control->session) return -EALREADY; control->session = avctp_connect(control->dev); if (!control->session) return -EIO; return 0; } int control_disconnect(struct btd_service *service) { struct control *control = btd_service_get_user_data(service); if (!control || !control->session) return -ENOTCONN; avctp_disconnect(control->session); return 0; } static DBusMessage *key_pressed(DBusConnection *conn, DBusMessage *msg, uint8_t op, bool hold, void *data) { struct control *control = data; int err; if (!control->session) return btd_error_not_connected(msg); if (!control->target) return btd_error_not_supported(msg); err = avctp_send_passthrough(control->session, op, hold); if (err < 0) return btd_error_failed(msg, strerror(-err)); return dbus_message_new_method_return(msg); } static DBusMessage *control_volume_up(DBusConnection *conn, DBusMessage *msg, void *data) { return key_pressed(conn, msg, AVC_VOLUME_UP, false, data); } static DBusMessage *control_volume_down(DBusConnection *conn, DBusMessage *msg, void *data) { return key_pressed(conn, msg, AVC_VOLUME_DOWN, false, data); } static DBusMessage *control_play(DBusConnection *conn, DBusMessage *msg, void *data) { return key_pressed(conn, msg, AVC_PLAY, false, data); } static DBusMessage *control_pause(DBusConnection *conn, DBusMessage *msg, void *data) { return key_pressed(conn, msg, AVC_PAUSE, false, data); } static DBusMessage *control_stop(DBusConnection *conn, DBusMessage *msg, void *data) { return key_pressed(conn, msg, AVC_STOP, false, data); } static DBusMessage *control_next(DBusConnection *conn, DBusMessage *msg, void *data) { return key_pressed(conn, msg, AVC_FORWARD, false, data); } static DBusMessage *control_previous(DBusConnection *conn, DBusMessage *msg, void *data) { return key_pressed(conn, msg, AVC_BACKWARD, false, data); } static DBusMessage *control_fast_forward(DBusConnection *conn, DBusMessage *msg, void *data) { return key_pressed(conn, msg, AVC_FAST_FORWARD, true, data); } static DBusMessage *control_rewind(DBusConnection *conn, DBusMessage *msg, void *data) { return key_pressed(conn, msg, AVC_REWIND, true, data); } static gboolean control_property_get_connected( const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct control *control = data; dbus_bool_t value = (control->session != NULL); dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &value); return TRUE; } static gboolean control_player_exists(const GDBusPropertyTable *property, void *data) { struct control *control = data; return control->player != NULL; } static gboolean control_get_player(const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) { struct control *control = data; if (!control->player) return FALSE; dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &control->player); return TRUE; } static const GDBusMethodTable control_methods[] = { { GDBUS_DEPRECATED_METHOD("Play", NULL, NULL, control_play) }, { GDBUS_DEPRECATED_METHOD("Pause", NULL, NULL, control_pause) }, { GDBUS_DEPRECATED_METHOD("Stop", NULL, NULL, control_stop) }, { GDBUS_DEPRECATED_METHOD("Next", NULL, NULL, control_next) }, { GDBUS_DEPRECATED_METHOD("Previous", NULL, NULL, control_previous) }, { GDBUS_DEPRECATED_METHOD("VolumeUp", NULL, NULL, control_volume_up) }, { GDBUS_DEPRECATED_METHOD("VolumeDown", NULL, NULL, control_volume_down) }, { GDBUS_DEPRECATED_METHOD("FastForward", NULL, NULL, control_fast_forward) }, { GDBUS_DEPRECATED_METHOD("Rewind", NULL, NULL, control_rewind) }, { } }; static const GDBusPropertyTable control_properties[] = { { "Connected", "b", control_property_get_connected }, { "Player", "o", control_get_player, NULL, control_player_exists }, { } }; static void path_unregister(void *data) { struct control *control = data; DBG("Unregistered interface %s on path %s", AUDIO_CONTROL_INTERFACE, device_get_path(control->dev)); if (control->session) avctp_disconnect(control->session); avctp_remove_state_cb(control->avctp_id); if (control->target) { btd_service_set_user_data(control->target, NULL); btd_service_unref(control->target); } if (control->remote) { btd_service_set_user_data(control->remote, NULL); btd_service_unref(control->remote); } devices = g_slist_remove(devices, control); g_free(control); } void control_unregister(struct btd_service *service) { struct btd_device *dev = btd_service_get_device(service); g_dbus_unregister_interface(btd_get_dbus_connection(), device_get_path(dev), AUDIO_CONTROL_INTERFACE); } static struct control *find_control(struct btd_device *dev) { GSList *l; for (l = devices; l; l = l->next) { struct control *control = l->data; if (control->dev == dev) return control; } return NULL; } static struct control *control_init(struct btd_service *service) { struct control *control; struct btd_device *dev = btd_service_get_device(service); control = find_control(dev); if (control != NULL) return control; control = g_new0(struct control, 1); if (!g_dbus_register_interface(btd_get_dbus_connection(), device_get_path(dev), AUDIO_CONTROL_INTERFACE, control_methods, NULL, control_properties, control, path_unregister)) { g_free(control); return NULL; } DBG("Registered interface %s on path %s", AUDIO_CONTROL_INTERFACE, device_get_path(dev)); control->dev = dev; control->avctp_id = avctp_add_state_cb(dev, state_changed, control); devices = g_slist_prepend(devices, control); return control; } int control_init_target(struct btd_service *service) { struct control *control; control = control_init(service); if (control == NULL) return -EINVAL; control->target = btd_service_ref(service); btd_service_set_user_data(service, control); return 0; } int control_init_remote(struct btd_service *service) { struct control *control; control = control_init(service); if (control == NULL) return -EINVAL; control->remote = btd_service_ref(service); btd_service_set_user_data(service, control); return 0; } int control_set_player(struct btd_service *service, const char *path) { struct control *control = btd_service_get_user_data(service); if (!control->session) return -ENOTCONN; if (g_strcmp0(control->player, path) == 0) return -EALREADY; control->player = path; g_dbus_emit_property_changed(btd_get_dbus_connection(), device_get_path(control->dev), AUDIO_CONTROL_INTERFACE, "Player"); return 0; }