/* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2006-2007 Nokia Corporation * Copyright (C) 2004-2009 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 <stdlib.h> #include <stdint.h> #include <errno.h> #include <unistd.h> #include <assert.h> #include <signal.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <netinet/in.h> #include <bluetooth/bluetooth.h> #include <bluetooth/sdp.h> #include <bluetooth/sdp_lib.h> #include <bluetooth/l2cap.h> #include <glib.h> #include <dbus/dbus.h> #include <gdbus.h> #include "logging.h" #include "error.h" #include "uinput.h" #include "adapter.h" #include "../src/device.h" #include "device.h" #include "manager.h" #include "avdtp.h" #include "control.h" #include "sdpd.h" #include "glib-helper.h" #include "btio.h" #include "dbus-common.h" #define AVCTP_PSM 23 /* Message types */ #define AVCTP_COMMAND 0 #define AVCTP_RESPONSE 1 /* Packet types */ #define AVCTP_PACKET_SINGLE 0 #define AVCTP_PACKET_START 1 #define AVCTP_PACKET_CONTINUE 2 #define AVCTP_PACKET_END 3 /* ctype entries */ #define CTYPE_CONTROL 0x0 #define CTYPE_STATUS 0x1 #define CTYPE_NOT_IMPLEMENTED 0x8 #define CTYPE_ACCEPTED 0x9 #define CTYPE_REJECTED 0xA #define CTYPE_STABLE 0xC /* opcodes */ #define OP_UNITINFO 0x30 #define OP_SUBUNITINFO 0x31 #define OP_PASSTHROUGH 0x7c /* subunits of interest */ #define SUBUNIT_PANEL 0x09 /* operands in passthrough commands */ #define VOL_UP_OP 0x41 #define VOL_DOWN_OP 0x42 #define MUTE_OP 0x43 #define PLAY_OP 0x44 #define STOP_OP 0x45 #define PAUSE_OP 0x46 #define RECORD_OP 0x47 #define REWIND_OP 0x48 #define FAST_FORWARD_OP 0x49 #define EJECT_OP 0x4a #define FORWARD_OP 0x4b #define BACKWARD_OP 0x4c static DBusConnection *connection = NULL; static gchar *input_device_name = NULL; static GSList *servers = NULL; #if __BYTE_ORDER == __LITTLE_ENDIAN struct avctp_header { uint8_t ipid:1; uint8_t cr:1; uint8_t packet_type:2; uint8_t transaction:4; uint16_t pid; } __attribute__ ((packed)); #define AVCTP_HEADER_LENGTH 3 struct avrcp_header { uint8_t code:4; uint8_t _hdr0:4; uint8_t subunit_id:3; uint8_t subunit_type:5; uint8_t opcode; } __attribute__ ((packed)); #define AVRCP_HEADER_LENGTH 3 #elif __BYTE_ORDER == __BIG_ENDIAN struct avctp_header { uint8_t transaction:4; uint8_t packet_type:2; uint8_t cr:1; uint8_t ipid:1; uint16_t pid; } __attribute__ ((packed)); #define AVCTP_HEADER_LENGTH 3 struct avrcp_header { uint8_t _hdr0:4; uint8_t code:4; uint8_t subunit_type:5; uint8_t subunit_id:3; uint8_t opcode; } __attribute__ ((packed)); #define AVRCP_HEADER_LENGTH 3 #else #error "Unknown byte order" #endif struct avctp_state_callback { avctp_state_cb cb; void *user_data; unsigned int id; }; struct avctp_server { bdaddr_t src; GIOChannel *io; uint32_t tg_record_id; #ifndef ANDROID uint32_t ct_record_id; #endif }; struct control { struct audio_device *dev; avctp_state_t state; int uinput; GIOChannel *io; guint io_id; uint16_t mtu; gboolean target; }; static struct { const char *name; uint8_t avrcp; uint16_t uinput; } key_map[] = { { "PLAY", PLAY_OP, KEY_PLAYCD }, { "STOP", STOP_OP, KEY_STOPCD }, { "PAUSE", PAUSE_OP, KEY_PAUSECD }, { "FORWARD", FORWARD_OP, KEY_NEXTSONG }, { "BACKWARD", BACKWARD_OP, KEY_PREVIOUSSONG }, { "REWIND", REWIND_OP, KEY_REWIND }, { "FAST FORWARD", FAST_FORWARD_OP, KEY_FASTFORWARD }, { NULL } }; static GSList *avctp_callbacks = NULL; static sdp_record_t *avrcp_ct_record() { sdp_list_t *svclass_id, *pfseq, *apseq, *root; uuid_t root_uuid, l2cap, avctp, avrct; sdp_profile_desc_t profile[1]; sdp_list_t *aproto, *proto[2]; sdp_record_t *record; sdp_data_t *psm, *version, *features; uint16_t lp = AVCTP_PSM, ver = 0x0100, feat = 0x000f; record = sdp_record_alloc(); if (!record) return NULL; sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); root = sdp_list_append(0, &root_uuid); sdp_set_browse_groups(record, root); /* Service Class ID List */ sdp_uuid16_create(&avrct, AV_REMOTE_SVCLASS_ID); svclass_id = sdp_list_append(0, &avrct); sdp_set_service_classes(record, svclass_id); /* Protocol Descriptor List */ sdp_uuid16_create(&l2cap, L2CAP_UUID); proto[0] = sdp_list_append(0, &l2cap); psm = sdp_data_alloc(SDP_UINT16, &lp); proto[0] = sdp_list_append(proto[0], psm); apseq = sdp_list_append(0, proto[0]); sdp_uuid16_create(&avctp, AVCTP_UUID); proto[1] = sdp_list_append(0, &avctp); version = sdp_data_alloc(SDP_UINT16, &ver); proto[1] = sdp_list_append(proto[1], version); apseq = sdp_list_append(apseq, proto[1]); aproto = sdp_list_append(0, apseq); sdp_set_access_protos(record, aproto); /* Bluetooth Profile Descriptor List */ sdp_uuid16_create(&profile[0].uuid, AV_REMOTE_PROFILE_ID); profile[0].version = ver; pfseq = sdp_list_append(0, &profile[0]); sdp_set_profile_descs(record, pfseq); features = sdp_data_alloc(SDP_UINT16, &feat); sdp_attr_add(record, SDP_ATTR_SUPPORTED_FEATURES, features); sdp_set_info_attr(record, "AVRCP CT", 0, 0); free(psm); free(version); sdp_list_free(proto[0], 0); sdp_list_free(proto[1], 0); sdp_list_free(apseq, 0); sdp_list_free(pfseq, 0); sdp_list_free(aproto, 0); sdp_list_free(root, 0); sdp_list_free(svclass_id, 0); return record; } static sdp_record_t *avrcp_tg_record() { sdp_list_t *svclass_id, *pfseq, *apseq, *root; uuid_t root_uuid, l2cap, avctp, avrtg; sdp_profile_desc_t profile[1]; sdp_list_t *aproto, *proto[2]; sdp_record_t *record; sdp_data_t *psm, *version, *features; uint16_t lp = AVCTP_PSM, ver = 0x0100, feat = 0x000f; record = sdp_record_alloc(); if (!record) return NULL; sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); root = sdp_list_append(0, &root_uuid); sdp_set_browse_groups(record, root); /* Service Class ID List */ sdp_uuid16_create(&avrtg, AV_REMOTE_TARGET_SVCLASS_ID); svclass_id = sdp_list_append(0, &avrtg); sdp_set_service_classes(record, svclass_id); /* Protocol Descriptor List */ sdp_uuid16_create(&l2cap, L2CAP_UUID); proto[0] = sdp_list_append(0, &l2cap); psm = sdp_data_alloc(SDP_UINT16, &lp); proto[0] = sdp_list_append(proto[0], psm); apseq = sdp_list_append(0, proto[0]); sdp_uuid16_create(&avctp, AVCTP_UUID); proto[1] = sdp_list_append(0, &avctp); version = sdp_data_alloc(SDP_UINT16, &ver); proto[1] = sdp_list_append(proto[1], version); apseq = sdp_list_append(apseq, proto[1]); aproto = sdp_list_append(0, apseq); sdp_set_access_protos(record, aproto); /* Bluetooth Profile Descriptor List */ sdp_uuid16_create(&profile[0].uuid, AV_REMOTE_PROFILE_ID); profile[0].version = ver; pfseq = sdp_list_append(0, &profile[0]); sdp_set_profile_descs(record, pfseq); features = sdp_data_alloc(SDP_UINT16, &feat); sdp_attr_add(record, SDP_ATTR_SUPPORTED_FEATURES, features); sdp_set_info_attr(record, "AVRCP TG", 0, 0); free(psm); free(version); sdp_list_free(proto[0], 0); sdp_list_free(proto[1], 0); sdp_list_free(apseq, 0); sdp_list_free(aproto, 0); sdp_list_free(pfseq, 0); sdp_list_free(root, 0); sdp_list_free(svclass_id, 0); return record; } static int send_event(int fd, uint16_t type, uint16_t code, int32_t value) { struct uinput_event event; memset(&event, 0, sizeof(event)); event.type = type; event.code = code; event.value = value; return write(fd, &event, sizeof(event)); } static void send_key(int fd, uint16_t key, int pressed) { if (fd < 0) return; send_event(fd, EV_KEY, key, pressed); send_event(fd, EV_SYN, SYN_REPORT, 0); } static void handle_panel_passthrough(struct control *control, const unsigned char *operands, int operand_count) { const char *status; int pressed, i; if (operand_count == 0) return; if (operands[0] & 0x80) { status = "released"; pressed = 0; } else { status = "pressed"; pressed = 1; } for (i = 0; key_map[i].name != NULL; i++) { if ((operands[0] & 0x7F) == key_map[i].avrcp) { debug("AVRCP: %s %s", key_map[i].name, status); send_key(control->uinput, key_map[i].uinput, pressed); break; } } if (key_map[i].name == NULL) debug("AVRCP: unknown button 0x%02X %s", operands[0] & 0x7F, status); } static void avctp_disconnected(struct audio_device *dev) { struct control *control = dev->control; if (!control) return; if (control->io) { g_io_channel_shutdown(control->io, TRUE, NULL); g_io_channel_unref(control->io); control->io = NULL; } if (control->io_id) { g_source_remove(control->io_id); control->io_id = 0; } if (control->uinput >= 0) { ioctl(control->uinput, UI_DEV_DESTROY); close(control->uinput); control->uinput = -1; } } static void avctp_set_state(struct control *control, avctp_state_t new_state) { GSList *l; struct audio_device *dev = control->dev; avdtp_session_state_t old_state = control->state; gboolean value; switch (new_state) { case AVCTP_STATE_DISCONNECTED: avctp_disconnected(control->dev); if (old_state != AVCTP_STATE_CONNECTED) break; value = FALSE; g_dbus_emit_signal(dev->conn, dev->path, AUDIO_CONTROL_INTERFACE, "Disconnected", DBUS_TYPE_INVALID); emit_property_changed(dev->conn, dev->path, AUDIO_CONTROL_INTERFACE, "Connected", DBUS_TYPE_BOOLEAN, &value); break; case AVCTP_STATE_CONNECTING: break; case AVCTP_STATE_CONNECTED: value = TRUE; g_dbus_emit_signal(control->dev->conn, control->dev->path, AUDIO_CONTROL_INTERFACE, "Connected", DBUS_TYPE_INVALID); emit_property_changed(control->dev->conn, control->dev->path, AUDIO_CONTROL_INTERFACE, "Connected", DBUS_TYPE_BOOLEAN, &value); break; default: error("Invalid AVCTP state %d", new_state); return; } control->state = new_state; for (l = avctp_callbacks; l != NULL; l = l->next) { struct avctp_state_callback *cb = l->data; cb->cb(control->dev, old_state, new_state, cb->user_data); } } static gboolean control_cb(GIOChannel *chan, GIOCondition cond, gpointer data) { struct control *control = data; unsigned char buf[1024], *operands; struct avctp_header *avctp; struct avrcp_header *avrcp; int ret, packet_size, operand_count, sock; if (!(cond | G_IO_IN)) goto failed; sock = g_io_channel_unix_get_fd(control->io); ret = read(sock, buf, sizeof(buf)); if (ret <= 0) goto failed; debug("Got %d bytes of data for AVCTP session %p", ret, control); if ((unsigned int) ret < sizeof(struct avctp_header)) { error("Too small AVCTP packet"); goto failed; } packet_size = ret; avctp = (struct avctp_header *) buf; debug("AVCTP transaction %u, packet type %u, C/R %u, IPID %u, " "PID 0x%04X", avctp->transaction, avctp->packet_type, avctp->cr, avctp->ipid, ntohs(avctp->pid)); ret -= sizeof(struct avctp_header); if ((unsigned int) ret < sizeof(struct avrcp_header)) { error("Too small AVRCP packet"); goto failed; } avrcp = (struct avrcp_header *) (buf + sizeof(struct avctp_header)); ret -= sizeof(struct avrcp_header); operands = buf + sizeof(struct avctp_header) + sizeof(struct avrcp_header); operand_count = ret; debug("AVRCP %s 0x%01X, subunit_type 0x%02X, subunit_id 0x%01X, " "opcode 0x%02X, %d operands", avctp->cr ? "response" : "command", avrcp->code, avrcp->subunit_type, avrcp->subunit_id, avrcp->opcode, operand_count); if (avctp->packet_type != AVCTP_PACKET_SINGLE) { avctp->cr = AVCTP_RESPONSE; avrcp->code = CTYPE_NOT_IMPLEMENTED; } else if (avctp->pid != htons(AV_REMOTE_SVCLASS_ID)) { avctp->ipid = 1; avctp->cr = AVCTP_RESPONSE; avrcp->code = CTYPE_REJECTED; } else if (avctp->cr == AVCTP_COMMAND && avrcp->code == CTYPE_CONTROL && avrcp->subunit_type == SUBUNIT_PANEL && avrcp->opcode == OP_PASSTHROUGH) { handle_panel_passthrough(control, operands, operand_count); avctp->cr = AVCTP_RESPONSE; avrcp->code = CTYPE_ACCEPTED; } else if (avctp->cr == AVCTP_COMMAND && avrcp->code == CTYPE_STATUS && (avrcp->opcode == OP_UNITINFO || avrcp->opcode == OP_SUBUNITINFO)) { avctp->cr = AVCTP_RESPONSE; avrcp->code = CTYPE_STABLE; /* The first operand should be 0x07 for the UNITINFO response. * Neither AVRCP (section 22.1, page 117) nor AVC Digital * Interface Command Set (section 9.2.1, page 45) specs * explain this value but both use it */ if (operand_count >= 1 && avrcp->opcode == OP_UNITINFO) operands[0] = 0x07; if (operand_count >= 2) operands[1] = SUBUNIT_PANEL << 3; debug("reply to %s", avrcp->opcode == OP_UNITINFO ? "OP_UNITINFO" : "OP_SUBUNITINFO"); } else { avctp->cr = AVCTP_RESPONSE; avrcp->code = CTYPE_REJECTED; } ret = write(sock, buf, packet_size); return TRUE; failed: debug("AVCTP session %p got disconnected", control); avctp_set_state(control, AVCTP_STATE_DISCONNECTED); return FALSE; } static int uinput_create(char *name) { struct uinput_dev dev; int fd, err, i; fd = open("/dev/uinput", O_RDWR); if (fd < 0) { fd = open("/dev/input/uinput", O_RDWR); if (fd < 0) { fd = open("/dev/misc/uinput", O_RDWR); if (fd < 0) { err = errno; error("Can't open input device: %s (%d)", strerror(err), err); return -err; } } } memset(&dev, 0, sizeof(dev)); if (name) strncpy(dev.name, name, UINPUT_MAX_NAME_SIZE - 1); dev.id.bustype = BUS_BLUETOOTH; dev.id.vendor = 0x0000; dev.id.product = 0x0000; dev.id.version = 0x0000; if (write(fd, &dev, sizeof(dev)) < 0) { err = errno; error("Can't write device information: %s (%d)", strerror(err), err); close(fd); errno = err; return -err; } ioctl(fd, UI_SET_EVBIT, EV_KEY); ioctl(fd, UI_SET_EVBIT, EV_REL); ioctl(fd, UI_SET_EVBIT, EV_REP); ioctl(fd, UI_SET_EVBIT, EV_SYN); for (i = 0; key_map[i].name != NULL; i++) ioctl(fd, UI_SET_KEYBIT, key_map[i].uinput); if (ioctl(fd, UI_DEV_CREATE, NULL) < 0) { err = errno; error("Can't create uinput device: %s (%d)", strerror(err), err); close(fd); errno = err; return -err; } return fd; } static void init_uinput(struct control *control) { char address[18], *name; ba2str(&control->dev->dst, address); /* Use device name from config file if specified */ name = input_device_name; if (!name) name = address; control->uinput = uinput_create(name); if (control->uinput < 0) error("AVRCP: failed to init uinput for %s", address); else debug("AVRCP: uinput initialized for %s", address); } static void avctp_connect_cb(GIOChannel *chan, GError *err, gpointer data) { struct control *control = data; char address[18]; uint16_t imtu; GError *gerr = NULL; if (err) { avctp_set_state(control, AVCTP_STATE_DISCONNECTED); error("%s", err->message); return; } bt_io_get(chan, BT_IO_L2CAP, &gerr, BT_IO_OPT_DEST, &address, BT_IO_OPT_IMTU, &imtu, BT_IO_OPT_INVALID); if (gerr) { avctp_set_state(control, AVCTP_STATE_DISCONNECTED); error("%s", gerr->message); g_error_free(gerr); return; } debug("AVCTP: connected to %s", address); if (!control->io) control->io = g_io_channel_ref(chan); init_uinput(control); avctp_set_state(control, AVCTP_STATE_CONNECTED); control->mtu = imtu; control->io_id = g_io_add_watch(chan, G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL, (GIOFunc) control_cb, control); } static void auth_cb(DBusError *derr, void *user_data) { struct control *control = user_data; GError *err = NULL; if (derr && dbus_error_is_set(derr)) { error("Access denied: %s", derr->message); avctp_set_state(control, AVCTP_STATE_DISCONNECTED); return; } if (!bt_io_accept(control->io, avctp_connect_cb, control, NULL, &err)) { error("bt_io_accept: %s", err->message); g_error_free(err); avctp_set_state(control, AVCTP_STATE_DISCONNECTED); } } static void avctp_confirm_cb(GIOChannel *chan, gpointer data) { struct control *control = NULL; struct audio_device *dev; char address[18]; bdaddr_t src, dst; GError *err = NULL; bt_io_get(chan, BT_IO_L2CAP, &err, BT_IO_OPT_SOURCE_BDADDR, &src, BT_IO_OPT_DEST_BDADDR, &dst, BT_IO_OPT_DEST, address, BT_IO_OPT_INVALID); if (err) { error("%s", err->message); g_error_free(err); g_io_channel_shutdown(chan, TRUE, NULL); return; } dev = manager_get_device(&src, &dst, TRUE); if (!dev) { error("Unable to get audio device object for %s", address); goto drop; } if (!dev->control) { btd_device_add_uuid(dev->btd_dev, AVRCP_REMOTE_UUID); if (!dev->control) goto drop; } control = dev->control; if (control->io) { error("Refusing unexpected connect from %s", address); goto drop; } avctp_set_state(control, AVCTP_STATE_CONNECTING); control->io = g_io_channel_ref(chan); if (audio_device_request_authorization(dev, AVRCP_TARGET_UUID, auth_cb, dev->control) < 0) goto drop; return; drop: if (!control || !control->io) g_io_channel_shutdown(chan, TRUE, NULL); if (control) avctp_set_state(control, AVCTP_STATE_DISCONNECTED); } static GIOChannel *avctp_server_socket(const bdaddr_t *src, gboolean master) { GError *err = NULL; GIOChannel *io; io = bt_io_listen(BT_IO_L2CAP, NULL, avctp_confirm_cb, NULL, NULL, &err, BT_IO_OPT_SOURCE_BDADDR, src, BT_IO_OPT_PSM, AVCTP_PSM, BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM, BT_IO_OPT_MASTER, master, BT_IO_OPT_INVALID); if (!io) { error("%s", err->message); g_error_free(err); } return io; } gboolean avrcp_connect(struct audio_device *dev) { struct control *control = dev->control; GError *err = NULL; GIOChannel *io; if (control->state > AVCTP_STATE_DISCONNECTED) return TRUE; avctp_set_state(control, AVCTP_STATE_CONNECTING); io = bt_io_connect(BT_IO_L2CAP, avctp_connect_cb, control, NULL, &err, BT_IO_OPT_SOURCE_BDADDR, &dev->src, BT_IO_OPT_DEST_BDADDR, &dev->dst, BT_IO_OPT_PSM, AVCTP_PSM, BT_IO_OPT_INVALID); if (err) { avctp_set_state(control, AVCTP_STATE_DISCONNECTED); error("%s", err->message); g_error_free(err); return FALSE; } control->io = io; return TRUE; } void avrcp_disconnect(struct audio_device *dev) { struct control *control = dev->control; if (!(control && control->io)) return; avctp_set_state(control, AVCTP_STATE_DISCONNECTED); } int avrcp_register(DBusConnection *conn, const bdaddr_t *src, GKeyFile *config) { sdp_record_t *record; gboolean tmp, master = TRUE; GError *err = NULL; struct avctp_server *server; if (config) { tmp = g_key_file_get_boolean(config, "General", "Master", &err); if (err) { debug("audio.conf: %s", err->message); g_error_free(err); } else master = tmp; err = NULL; input_device_name = g_key_file_get_string(config, "AVRCP", "InputDeviceName", &err); if (err) { debug("audio.conf: %s", err->message); input_device_name = NULL; g_error_free(err); } } server = g_new0(struct avctp_server, 1); if (!server) return -ENOMEM; if (!connection) connection = dbus_connection_ref(conn); record = avrcp_tg_record(); if (!record) { error("Unable to allocate new service record"); return -1; } if (add_record_to_server(src, record) < 0) { error("Unable to register AVRCP target service record"); sdp_record_free(record); return -1; } server->tg_record_id = record->handle; #ifndef ANDROID record = avrcp_ct_record(); if (!record) { error("Unable to allocate new service record"); return -1; } if (add_record_to_server(src, record) < 0) { error("Unable to register AVRCP controller service record"); sdp_record_free(record); return -1; } server->ct_record_id = record->handle; #endif server->io = avctp_server_socket(src, master); if (!server->io) { #ifndef ANDROID remove_record_from_server(server->ct_record_id); #endif remove_record_from_server(server->tg_record_id); g_free(server); return -1; } bacpy(&server->src, src); servers = g_slist_append(servers, server); return 0; } static struct avctp_server *find_server(GSList *list, const bdaddr_t *src) { GSList *l; for (l = list; l; l = l->next) { struct avctp_server *server = l->data; if (bacmp(&server->src, src) == 0) return server; } return NULL; } void avrcp_unregister(const bdaddr_t *src) { struct avctp_server *server; server = find_server(servers, src); if (!server) return; servers = g_slist_remove(servers, server); #ifndef ANDROID remove_record_from_server(server->ct_record_id); #endif remove_record_from_server(server->tg_record_id); g_io_channel_shutdown(server->io, TRUE, NULL); g_io_channel_unref(server->io); g_free(server); if (servers) return; dbus_connection_unref(connection); connection = NULL; } static DBusMessage *control_is_connected(DBusConnection *conn, DBusMessage *msg, void *data) { struct audio_device *device = data; struct control *control = device->control; DBusMessage *reply; dbus_bool_t connected; reply = dbus_message_new_method_return(msg); if (!reply) return NULL; connected = (control->state == AVCTP_STATE_CONNECTED); dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &connected, DBUS_TYPE_INVALID); return reply; } static int avctp_send_passthrough(struct control *control, uint8_t op) { unsigned char buf[AVCTP_HEADER_LENGTH + AVRCP_HEADER_LENGTH + 2]; struct avctp_header *avctp = (void *) buf; struct avrcp_header *avrcp = (void *) &buf[AVCTP_HEADER_LENGTH]; uint8_t *operands = &buf[AVCTP_HEADER_LENGTH + AVRCP_HEADER_LENGTH]; int err, sk = g_io_channel_unix_get_fd(control->io); static uint8_t transaction = 0; memset(buf, 0, sizeof(buf)); avctp->transaction = transaction++; avctp->packet_type = AVCTP_PACKET_SINGLE; avctp->cr = AVCTP_COMMAND; avctp->pid = htons(AV_REMOTE_SVCLASS_ID); avrcp->code = CTYPE_CONTROL; avrcp->subunit_type = SUBUNIT_PANEL; avrcp->opcode = OP_PASSTHROUGH; operands[0] = op & 0x7f; operands[1] = 0; err = write(sk, buf, sizeof(buf)); if (err < 0) return err; /* Button release */ avctp->transaction = transaction++; operands[0] |= 0x80; return write(sk, buf, sizeof(buf)); } static DBusMessage *volume_up(DBusConnection *conn, DBusMessage *msg, void *data) { struct audio_device *device = data; struct control *control = device->control; DBusMessage *reply; int err; reply = dbus_message_new_method_return(msg); if (!reply) return NULL; if (control->state != AVCTP_STATE_CONNECTED) return g_dbus_create_error(msg, ERROR_INTERFACE ".NotConnected", "Device not Connected"); if (!control->target) return g_dbus_create_error(msg, ERROR_INTERFACE ".NotSupported", "AVRCP Target role not supported"); err = avctp_send_passthrough(control, VOL_UP_OP); if (err < 0) return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed", strerror(-err)); return dbus_message_new_method_return(msg); } static DBusMessage *volume_down(DBusConnection *conn, DBusMessage *msg, void *data) { struct audio_device *device = data; struct control *control = device->control; DBusMessage *reply; int err; reply = dbus_message_new_method_return(msg); if (!reply) return NULL; if (control->state != AVCTP_STATE_CONNECTED) return g_dbus_create_error(msg, ERROR_INTERFACE ".NotConnected", "Device not Connected"); if (!control->target) return g_dbus_create_error(msg, ERROR_INTERFACE ".NotSupported", "AVRCP Target role not supported"); err = avctp_send_passthrough(control, VOL_DOWN_OP); if (err < 0) return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed", strerror(-err)); return dbus_message_new_method_return(msg); } static DBusMessage *control_get_properties(DBusConnection *conn, DBusMessage *msg, void *data) { struct audio_device *device = data; DBusMessage *reply; DBusMessageIter iter; DBusMessageIter dict; gboolean value; reply = dbus_message_new_method_return(msg); if (!reply) return NULL; dbus_message_iter_init_append(reply, &iter); 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); /* Connected */ value = (device->control->state == AVCTP_STATE_CONNECTED); dict_append_entry(&dict, "Connected", DBUS_TYPE_BOOLEAN, &value); dbus_message_iter_close_container(&iter, &dict); return reply; } static GDBusMethodTable control_methods[] = { { "IsConnected", "", "b", control_is_connected, G_DBUS_METHOD_FLAG_DEPRECATED }, { "GetProperties", "", "a{sv}",control_get_properties }, { "VolumeUp", "", "", volume_up }, { "VolumeDown", "", "", volume_down }, { NULL, NULL, NULL, NULL } }; static GDBusSignalTable control_signals[] = { { "Connected", "", G_DBUS_SIGNAL_FLAG_DEPRECATED}, { "Disconnected", "", G_DBUS_SIGNAL_FLAG_DEPRECATED}, { "PropertyChanged", "sv" }, { NULL, NULL } }; static void path_unregister(void *data) { struct audio_device *dev = data; struct control *control = dev->control; debug("Unregistered interface %s on path %s", AUDIO_CONTROL_INTERFACE, dev->path); if (control->state != AVCTP_STATE_DISCONNECTED) avctp_disconnected(dev); g_free(control); dev->control = NULL; } void control_unregister(struct audio_device *dev) { g_dbus_unregister_interface(dev->conn, dev->path, AUDIO_CONTROL_INTERFACE); } void control_update(struct audio_device *dev, uint16_t uuid16) { struct control *control = dev->control; if (uuid16 == AV_REMOTE_TARGET_SVCLASS_ID) control->target = TRUE; } struct control *control_init(struct audio_device *dev, uint16_t uuid16) { struct control *control; if (!g_dbus_register_interface(dev->conn, dev->path, AUDIO_CONTROL_INTERFACE, control_methods, control_signals, NULL, dev, path_unregister)) return NULL; debug("Registered interface %s on path %s", AUDIO_CONTROL_INTERFACE, dev->path); control = g_new0(struct control, 1); control->dev = dev; control->state = AVCTP_STATE_DISCONNECTED; control->uinput = -1; if (uuid16 == AV_REMOTE_TARGET_SVCLASS_ID) control->target = TRUE; return control; } gboolean control_is_active(struct audio_device *dev) { struct control *control = dev->control; if (control && control->state != AVCTP_STATE_DISCONNECTED) return TRUE; return FALSE; } unsigned int avctp_add_state_cb(avctp_state_cb cb, void *user_data) { struct avctp_state_callback *state_cb; static unsigned int id = 0; state_cb = g_new(struct avctp_state_callback, 1); state_cb->cb = cb; state_cb->user_data = user_data; state_cb->id = ++id; avctp_callbacks = g_slist_append(avctp_callbacks, state_cb); return state_cb->id; } gboolean avctp_remove_state_cb(unsigned int id) { GSList *l; for (l = avctp_callbacks; l != NULL; l = l->next) { struct avctp_state_callback *cb = l->data; if (cb && cb->id == id) { avctp_callbacks = g_slist_remove(avctp_callbacks, cb); g_free(cb); return TRUE; } } return FALSE; }