| /* |
| * |
| * BlueZ - Bluetooth protocol stack for Linux |
| * |
| * Copyright (C) 2006-2010 Nokia Corporation |
| * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org> |
| * Copyright (C) 2011 Texas Instruments, Inc. |
| * |
| * |
| * 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 <stdbool.h> |
| #include <errno.h> |
| #include <unistd.h> |
| #include <assert.h> |
| #include <signal.h> |
| #include <string.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <fcntl.h> |
| #include <netinet/in.h> |
| |
| #include <bluetooth/sdp.h> |
| |
| #include <glib.h> |
| |
| #include "src/log.h" |
| #include "src/uinput.h" |
| |
| #include "avctp.h" |
| |
| /* AV/C Panel 1.23, page 76: |
| * command with the pressed value is valid for two seconds |
| */ |
| #define AVC_PRESS_TIMEOUT 2 |
| |
| #define QUIRK_NO_RELEASE 1 << 0 |
| |
| /* 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 |
| |
| #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 avc_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 AVC_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 avc_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 AVC_HEADER_LENGTH 3 |
| |
| #else |
| #error "Unknown byte order" |
| #endif |
| |
| struct avctp_control_req { |
| struct avctp_pending_req *p; |
| uint8_t code; |
| uint8_t subunit; |
| uint8_t op; |
| uint8_t *operands; |
| uint16_t operand_count; |
| avctp_rsp_cb func; |
| void *user_data; |
| }; |
| |
| struct avctp_browsing_req { |
| struct avctp_pending_req *p; |
| uint8_t *operands; |
| uint16_t operand_count; |
| avctp_browsing_rsp_cb func; |
| void *user_data; |
| }; |
| |
| typedef int (*avctp_process_cb) (void *data); |
| |
| struct avctp_pending_req { |
| struct avctp_channel *chan; |
| uint8_t transaction; |
| guint timeout; |
| int err; |
| avctp_process_cb process; |
| void *data; |
| avctp_destroy_cb_t destroy; |
| }; |
| |
| struct avctp_channel { |
| struct avctp *session; |
| GIOChannel *io; |
| uint8_t transaction; |
| guint watch; |
| uint16_t imtu; |
| uint16_t omtu; |
| uint8_t *buffer; |
| GSList *handlers; |
| struct avctp_pending_req *p; |
| GQueue *queue; |
| GSList *processed; |
| guint process_id; |
| avctp_destroy_cb_t destroy; |
| }; |
| |
| struct key_pressed { |
| uint8_t op; |
| uint8_t *params; |
| size_t params_len; |
| guint timer; |
| }; |
| |
| struct avctp { |
| int uinput; |
| |
| unsigned int passthrough_id; |
| unsigned int unit_id; |
| unsigned int subunit_id; |
| |
| struct avctp_channel *control; |
| struct avctp_channel *browsing; |
| |
| struct avctp_passthrough_handler *handler; |
| |
| uint8_t key_quirks[256]; |
| struct key_pressed key; |
| uint16_t version; |
| |
| avctp_destroy_cb_t destroy; |
| void *data; |
| }; |
| |
| struct avctp_passthrough_handler { |
| avctp_passthrough_cb cb; |
| void *user_data; |
| unsigned int id; |
| }; |
| |
| struct avctp_pdu_handler { |
| uint8_t opcode; |
| avctp_control_pdu_cb cb; |
| void *user_data; |
| unsigned int id; |
| }; |
| |
| struct avctp_browsing_pdu_handler { |
| avctp_browsing_pdu_cb cb; |
| void *user_data; |
| unsigned int id; |
| avctp_destroy_cb_t destroy; |
| }; |
| |
| static struct { |
| const char *name; |
| uint8_t avc; |
| uint16_t uinput; |
| } key_map[] = { |
| { "SELECT", AVC_SELECT, KEY_SELECT }, |
| { "UP", AVC_UP, KEY_UP }, |
| { "DOWN", AVC_DOWN, KEY_DOWN }, |
| { "LEFT", AVC_LEFT, KEY_LEFT }, |
| { "RIGHT", AVC_RIGHT, KEY_RIGHT }, |
| { "ROOT MENU", AVC_ROOT_MENU, KEY_MENU }, |
| { "CONTENTS MENU", AVC_CONTENTS_MENU, KEY_PROGRAM }, |
| { "FAVORITE MENU", AVC_FAVORITE_MENU, KEY_FAVORITES }, |
| { "EXIT", AVC_EXIT, KEY_EXIT }, |
| { "ON DEMAND MENU", AVC_ON_DEMAND_MENU, KEY_MENU }, |
| { "APPS MENU", AVC_APPS_MENU, KEY_MENU }, |
| { "0", AVC_0, KEY_0 }, |
| { "1", AVC_1, KEY_1 }, |
| { "2", AVC_2, KEY_2 }, |
| { "3", AVC_3, KEY_3 }, |
| { "4", AVC_4, KEY_4 }, |
| { "5", AVC_5, KEY_5 }, |
| { "6", AVC_6, KEY_6 }, |
| { "7", AVC_7, KEY_7 }, |
| { "8", AVC_8, KEY_8 }, |
| { "9", AVC_9, KEY_9 }, |
| { "DOT", AVC_DOT, KEY_DOT }, |
| { "ENTER", AVC_ENTER, KEY_ENTER }, |
| { "CHANNEL UP", AVC_CHANNEL_UP, KEY_CHANNELUP }, |
| { "CHANNEL DOWN", AVC_CHANNEL_DOWN, KEY_CHANNELDOWN }, |
| { "CHANNEL PREVIOUS", AVC_CHANNEL_PREVIOUS, KEY_LAST }, |
| { "INPUT SELECT", AVC_INPUT_SELECT, KEY_CONFIG }, |
| { "INFO", AVC_INFO, KEY_INFO }, |
| { "HELP", AVC_HELP, KEY_HELP }, |
| { "POWER", AVC_POWER, KEY_POWER2 }, |
| { "VOLUME UP", AVC_VOLUME_UP, KEY_VOLUMEUP }, |
| { "VOLUME DOWN", AVC_VOLUME_DOWN, KEY_VOLUMEDOWN }, |
| { "MUTE", AVC_MUTE, KEY_MUTE }, |
| { "PLAY", AVC_PLAY, KEY_PLAYCD }, |
| { "STOP", AVC_STOP, KEY_STOPCD }, |
| { "PAUSE", AVC_PAUSE, KEY_PAUSECD }, |
| { "FORWARD", AVC_FORWARD, KEY_NEXTSONG }, |
| { "BACKWARD", AVC_BACKWARD, KEY_PREVIOUSSONG }, |
| { "RECORD", AVC_RECORD, KEY_RECORD }, |
| { "REWIND", AVC_REWIND, KEY_REWIND }, |
| { "FAST FORWARD", AVC_FAST_FORWARD, KEY_FASTFORWARD }, |
| { "LIST", AVC_LIST, KEY_LIST }, |
| { "F1", AVC_F1, KEY_F1 }, |
| { "F2", AVC_F2, KEY_F2 }, |
| { "F3", AVC_F3, KEY_F3 }, |
| { "F4", AVC_F4, KEY_F4 }, |
| { "F5", AVC_F5, KEY_F5 }, |
| { "F6", AVC_F6, KEY_F6 }, |
| { "F7", AVC_F7, KEY_F7 }, |
| { "F8", AVC_F8, KEY_F8 }, |
| { "F9", AVC_F9, KEY_F9 }, |
| { "RED", AVC_RED, KEY_RED }, |
| { "GREEN", AVC_GREEN, KEY_GREEN }, |
| { "BLUE", AVC_BLUE, KEY_BLUE }, |
| { "YELLOW", AVC_YELLOW, KEY_YELLOW }, |
| { NULL } |
| }; |
| |
| static gboolean process_queue(gpointer user_data); |
| static gboolean avctp_passthrough_rsp(struct avctp *session, uint8_t code, |
| uint8_t subunit, uint8_t *operands, |
| size_t operand_count, void *user_data); |
| |
| static int send_event(int fd, uint16_t type, uint16_t code, int32_t value) |
| { |
| struct uinput_event event; |
| int err; |
| |
| memset(&event, 0, sizeof(event)); |
| event.type = type; |
| event.code = code; |
| event.value = value; |
| |
| do { |
| err = write(fd, &event, sizeof(event)); |
| } while (err < 0 && errno == EINTR); |
| |
| if (err < 0) { |
| err = -errno; |
| error("send_event: %s (%d)", strerror(-err), -err); |
| } |
| |
| return err; |
| } |
| |
| static void send_key(int fd, uint16_t key, int pressed) |
| { |
| send_event(fd, EV_KEY, key, pressed); |
| send_event(fd, EV_SYN, SYN_REPORT, 0); |
| } |
| |
| static gboolean auto_release(gpointer user_data) |
| { |
| struct avctp *session = user_data; |
| |
| session->key.timer = 0; |
| |
| DBG("AV/C: key press timeout"); |
| |
| send_key(session->uinput, session->key.op, 0); |
| |
| return FALSE; |
| } |
| |
| static ssize_t handle_panel_passthrough(struct avctp *session, |
| uint8_t transaction, uint8_t *code, |
| uint8_t *subunit, uint8_t *operands, |
| size_t operand_count, void *user_data) |
| { |
| struct avctp_passthrough_handler *handler = session->handler; |
| const char *status; |
| int pressed, i; |
| |
| if (*code != AVC_CTYPE_CONTROL || *subunit != AVC_SUBUNIT_PANEL) { |
| *code = AVC_CTYPE_REJECTED; |
| return operand_count; |
| } |
| |
| if (operand_count == 0) |
| goto done; |
| |
| if (operands[0] & 0x80) { |
| status = "released"; |
| pressed = 0; |
| } else { |
| status = "pressed"; |
| pressed = 1; |
| } |
| |
| if (session->key.timer == 0 && handler != NULL) { |
| if (handler->cb(session, operands[0] & 0x7F, |
| pressed, handler->user_data)) |
| goto done; |
| } |
| |
| if (session->uinput < 0) { |
| DBG("AV/C: uinput not initialized"); |
| *code = AVC_CTYPE_NOT_IMPLEMENTED; |
| return 0; |
| } |
| |
| for (i = 0; key_map[i].name != NULL; i++) { |
| uint8_t key_quirks; |
| |
| if ((operands[0] & 0x7F) != key_map[i].avc) |
| continue; |
| |
| DBG("AV/C: %s %s", key_map[i].name, status); |
| |
| key_quirks = session->key_quirks[key_map[i].avc]; |
| |
| if (key_quirks & QUIRK_NO_RELEASE) { |
| if (!pressed) { |
| DBG("AV/C: Ignoring release"); |
| break; |
| } |
| |
| DBG("AV/C: treating key press as press + release"); |
| send_key(session->uinput, key_map[i].uinput, 1); |
| send_key(session->uinput, key_map[i].uinput, 0); |
| break; |
| } |
| |
| if (pressed) { |
| if (session->key.timer > 0) { |
| g_source_remove(session->key.timer); |
| send_key(session->uinput, session->key.op, 0); |
| } |
| |
| session->key.op = key_map[i].uinput; |
| session->key.timer = g_timeout_add_seconds( |
| AVC_PRESS_TIMEOUT, |
| auto_release, |
| session); |
| } else if (session->key.timer > 0) { |
| g_source_remove(session->key.timer); |
| session->key.timer = 0; |
| } |
| |
| send_key(session->uinput, key_map[i].uinput, pressed); |
| break; |
| } |
| |
| if (key_map[i].name == NULL) { |
| DBG("AV/C: unknown button 0x%02X %s", |
| operands[0] & 0x7F, status); |
| *code = AVC_CTYPE_NOT_IMPLEMENTED; |
| return operand_count; |
| } |
| |
| done: |
| *code = AVC_CTYPE_ACCEPTED; |
| return operand_count; |
| } |
| |
| static ssize_t handle_unit_info(struct avctp *session, |
| uint8_t transaction, uint8_t *code, |
| uint8_t *subunit, uint8_t *operands, |
| size_t operand_count, void *user_data) |
| { |
| if (*code != AVC_CTYPE_STATUS) { |
| *code = AVC_CTYPE_REJECTED; |
| return 0; |
| } |
| |
| *code = AVC_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) |
| operands[0] = 0x07; |
| if (operand_count >= 2) |
| operands[1] = AVC_SUBUNIT_PANEL << 3; |
| |
| DBG("reply to AVC_OP_UNITINFO"); |
| |
| return operand_count; |
| } |
| |
| static ssize_t handle_subunit_info(struct avctp *session, |
| uint8_t transaction, uint8_t *code, |
| uint8_t *subunit, uint8_t *operands, |
| size_t operand_count, void *user_data) |
| { |
| if (*code != AVC_CTYPE_STATUS) { |
| *code = AVC_CTYPE_REJECTED; |
| return 0; |
| } |
| |
| *code = AVC_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 >= 2) |
| operands[1] = AVC_SUBUNIT_PANEL << 3; |
| |
| DBG("reply to AVC_OP_SUBUNITINFO"); |
| |
| return operand_count; |
| } |
| |
| static struct avctp_pdu_handler *find_handler(GSList *list, uint8_t opcode) |
| { |
| for (; list; list = list->next) { |
| struct avctp_pdu_handler *handler = list->data; |
| |
| if (handler->opcode == opcode) |
| return handler; |
| } |
| |
| return NULL; |
| } |
| |
| static void pending_destroy(gpointer data, gpointer user_data) |
| { |
| struct avctp_pending_req *req = data; |
| |
| if (req->destroy) |
| req->destroy(req->data); |
| |
| if (req->timeout > 0) |
| g_source_remove(req->timeout); |
| |
| g_free(req); |
| } |
| |
| static void avctp_channel_destroy(struct avctp_channel *chan) |
| { |
| g_io_channel_shutdown(chan->io, TRUE, NULL); |
| g_io_channel_unref(chan->io); |
| |
| if (chan->watch) |
| g_source_remove(chan->watch); |
| |
| if (chan->p) |
| pending_destroy(chan->p, NULL); |
| |
| if (chan->process_id > 0) |
| g_source_remove(chan->process_id); |
| |
| if (chan->destroy) |
| chan->destroy(chan); |
| |
| g_free(chan->buffer); |
| g_queue_foreach(chan->queue, pending_destroy, NULL); |
| g_queue_free(chan->queue); |
| g_slist_foreach(chan->processed, pending_destroy, NULL); |
| g_slist_free(chan->processed); |
| g_slist_free_full(chan->handlers, g_free); |
| g_free(chan); |
| } |
| |
| static int avctp_send(struct avctp_channel *control, uint8_t transaction, |
| uint8_t cr, uint8_t code, |
| uint8_t subunit, uint8_t opcode, |
| uint8_t *operands, size_t operand_count) |
| { |
| struct avctp_header *avctp; |
| struct avc_header *avc; |
| struct msghdr msg; |
| struct iovec iov[2]; |
| int sk, err = 0; |
| |
| iov[0].iov_base = control->buffer; |
| iov[0].iov_len = sizeof(*avctp) + sizeof(*avc); |
| iov[1].iov_base = operands; |
| iov[1].iov_len = operand_count; |
| |
| if (control->omtu < (iov[0].iov_len + iov[1].iov_len)) |
| return -EOVERFLOW; |
| |
| sk = g_io_channel_unix_get_fd(control->io); |
| |
| memset(control->buffer, 0, iov[0].iov_len); |
| |
| avctp = (void *) control->buffer; |
| avc = (void *) avctp + sizeof(*avctp); |
| |
| avctp->transaction = transaction; |
| avctp->packet_type = AVCTP_PACKET_SINGLE; |
| avctp->cr = cr; |
| avctp->pid = htons(AV_REMOTE_SVCLASS_ID); |
| |
| avc->code = code; |
| avc->subunit_type = subunit; |
| avc->opcode = opcode; |
| |
| memset(&msg, 0, sizeof(msg)); |
| msg.msg_iov = iov; |
| msg.msg_iovlen = 2; |
| |
| if (sendmsg(sk, &msg, 0) < 0) |
| err = -errno; |
| |
| return err; |
| } |
| |
| static int avctp_browsing_send(struct avctp_channel *browsing, |
| uint8_t transaction, uint8_t cr, |
| uint8_t *operands, size_t operand_count) |
| { |
| struct avctp_header *avctp; |
| struct msghdr msg; |
| struct iovec iov[2]; |
| int sk, err = 0; |
| |
| iov[0].iov_base = browsing->buffer; |
| iov[0].iov_len = sizeof(*avctp); |
| iov[1].iov_base = operands; |
| iov[1].iov_len = operand_count; |
| |
| if (browsing->omtu < (iov[0].iov_len + iov[1].iov_len)) |
| return -EOVERFLOW; |
| |
| sk = g_io_channel_unix_get_fd(browsing->io); |
| |
| memset(browsing->buffer, 0, iov[0].iov_len); |
| |
| avctp = (void *) browsing->buffer; |
| |
| avctp->transaction = transaction; |
| avctp->packet_type = AVCTP_PACKET_SINGLE; |
| avctp->cr = cr; |
| avctp->pid = htons(AV_REMOTE_SVCLASS_ID); |
| |
| memset(&msg, 0, sizeof(msg)); |
| msg.msg_iov = iov; |
| msg.msg_iovlen = 2; |
| |
| if (sendmsg(sk, &msg, 0) < 0) |
| err = -errno; |
| |
| return err; |
| } |
| |
| static void control_req_destroy(void *data) |
| { |
| struct avctp_control_req *req = data; |
| struct avctp_pending_req *p = req->p; |
| struct avctp *session = p->chan->session; |
| |
| if (p->err == 0 || req->func == NULL) |
| goto done; |
| |
| req->func(session, AVC_CTYPE_REJECTED, req->subunit, NULL, 0, |
| req->user_data); |
| |
| done: |
| g_free(req->operands); |
| g_free(req); |
| } |
| |
| static void browsing_req_destroy(void *data) |
| { |
| struct avctp_browsing_req *req = data; |
| struct avctp_pending_req *p = req->p; |
| struct avctp *session = p->chan->session; |
| |
| if (p->err == 0 || req->func == NULL) |
| goto done; |
| |
| req->func(session, NULL, 0, req->user_data); |
| |
| done: |
| g_free(req->operands); |
| g_free(req); |
| } |
| |
| static gboolean req_timeout(gpointer user_data) |
| { |
| struct avctp_channel *chan = user_data; |
| struct avctp_pending_req *p = chan->p; |
| |
| DBG("transaction %u", p->transaction); |
| |
| p->timeout = 0; |
| p->err = -ETIMEDOUT; |
| |
| pending_destroy(p, NULL); |
| chan->p = NULL; |
| |
| if (chan->process_id == 0) |
| chan->process_id = g_idle_add(process_queue, chan); |
| |
| return FALSE; |
| } |
| |
| static int process_control(void *data) |
| { |
| struct avctp_control_req *req = data; |
| struct avctp_pending_req *p = req->p; |
| |
| return avctp_send(p->chan, p->transaction, AVCTP_COMMAND, req->code, |
| req->subunit, req->op, |
| req->operands, req->operand_count); |
| } |
| |
| static int process_browsing(void *data) |
| { |
| struct avctp_browsing_req *req = data; |
| struct avctp_pending_req *p = req->p; |
| |
| return avctp_browsing_send(p->chan, p->transaction, AVCTP_COMMAND, |
| req->operands, req->operand_count); |
| } |
| |
| static gboolean process_queue(void *user_data) |
| { |
| struct avctp_channel *chan = user_data; |
| struct avctp_pending_req *p = chan->p; |
| |
| chan->process_id = 0; |
| |
| if (p != NULL) |
| return FALSE; |
| |
| while ((p = g_queue_pop_head(chan->queue))) { |
| |
| if (p->process(p->data) == 0) |
| break; |
| |
| pending_destroy(p, NULL); |
| } |
| |
| if (p == NULL) |
| return FALSE; |
| |
| chan->p = p; |
| p->timeout = g_timeout_add_seconds(2, req_timeout, chan); |
| |
| return FALSE; |
| |
| } |
| |
| static void control_response(struct avctp_channel *control, |
| struct avctp_header *avctp, |
| struct avc_header *avc, |
| uint8_t *operands, |
| size_t operand_count) |
| { |
| struct avctp_pending_req *p = control->p; |
| struct avctp_control_req *req; |
| GSList *l; |
| |
| if (p && p->transaction == avctp->transaction) { |
| control->processed = g_slist_prepend(control->processed, p); |
| |
| if (p->timeout > 0) { |
| g_source_remove(p->timeout); |
| p->timeout = 0; |
| } |
| |
| control->p = NULL; |
| |
| if (control->process_id == 0) |
| control->process_id = g_idle_add(process_queue, |
| control); |
| } |
| |
| for (l = control->processed; l; l = l->next) { |
| p = l->data; |
| req = p->data; |
| |
| if (p->transaction != avctp->transaction) |
| continue; |
| |
| if (req->func && req->func(control->session, avc->code, |
| avc->subunit_type, |
| operands, operand_count, |
| req->user_data)) |
| return; |
| |
| control->processed = g_slist_remove(control->processed, p); |
| pending_destroy(p, NULL); |
| |
| return; |
| } |
| } |
| |
| static void browsing_response(struct avctp_channel *browsing, |
| struct avctp_header *avctp, |
| uint8_t *operands, |
| size_t operand_count) |
| { |
| struct avctp_pending_req *p = browsing->p; |
| struct avctp_browsing_req *req; |
| GSList *l; |
| |
| if (p && p->transaction == avctp->transaction) { |
| browsing->processed = g_slist_prepend(browsing->processed, p); |
| |
| if (p->timeout > 0) { |
| g_source_remove(p->timeout); |
| p->timeout = 0; |
| } |
| |
| browsing->p = NULL; |
| |
| if (browsing->process_id == 0) |
| browsing->process_id = g_idle_add(process_queue, |
| browsing); |
| } |
| |
| for (l = browsing->processed; l; l = l->next) { |
| p = l->data; |
| req = p->data; |
| |
| if (p->transaction != avctp->transaction) |
| continue; |
| |
| if (req->func && req->func(browsing->session, operands, |
| operand_count, req->user_data)) |
| return; |
| |
| browsing->processed = g_slist_remove(browsing->processed, p); |
| pending_destroy(p, NULL); |
| |
| return; |
| } |
| } |
| |
| static gboolean session_browsing_cb(GIOChannel *chan, GIOCondition cond, |
| gpointer data) |
| { |
| struct avctp *session = data; |
| struct avctp_channel *browsing = session->browsing; |
| uint8_t *buf = browsing->buffer; |
| uint8_t *operands; |
| struct avctp_header *avctp; |
| int sock, ret, packet_size, operand_count; |
| struct avctp_browsing_pdu_handler *handler; |
| |
| if (cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL)) |
| goto failed; |
| |
| sock = g_io_channel_unix_get_fd(chan); |
| |
| ret = read(sock, buf, browsing->imtu); |
| if (ret <= 0) |
| goto failed; |
| |
| avctp = (struct avctp_header *) buf; |
| |
| if (avctp->packet_type != AVCTP_PACKET_SINGLE) |
| goto failed; |
| |
| operands = buf + AVCTP_HEADER_LENGTH; |
| ret -= AVCTP_HEADER_LENGTH; |
| operand_count = ret; |
| |
| if (avctp->cr == AVCTP_RESPONSE) { |
| browsing_response(browsing, avctp, operands, operand_count); |
| return TRUE; |
| } |
| |
| packet_size = AVCTP_HEADER_LENGTH; |
| avctp->cr = AVCTP_RESPONSE; |
| |
| handler = g_slist_nth_data(browsing->handlers, 0); |
| if (handler == NULL) { |
| DBG("handler not found"); |
| /* FIXME: Add general reject */ |
| /* packet_size += avrcp_browsing_general_reject(operands); */ |
| goto send; |
| } |
| |
| packet_size += handler->cb(session, avctp->transaction, |
| operands, operand_count, |
| handler->user_data); |
| |
| send: |
| if (packet_size != 0) { |
| ret = write(sock, buf, packet_size); |
| if (ret != packet_size) |
| goto failed; |
| } |
| |
| return TRUE; |
| |
| failed: |
| DBG("AVCTP Browsing: disconnected"); |
| |
| if (session->browsing) { |
| avctp_channel_destroy(session->browsing); |
| session->browsing = NULL; |
| } |
| |
| return FALSE; |
| } |
| |
| static gboolean session_cb(GIOChannel *chan, GIOCondition cond, gpointer data) |
| { |
| struct avctp *session = data; |
| struct avctp_channel *control = session->control; |
| uint8_t *buf = control->buffer; |
| uint8_t *operands, code, subunit; |
| struct avctp_header *avctp; |
| struct avc_header *avc; |
| int packet_size, operand_count, sock; |
| struct avctp_pdu_handler *handler; |
| ssize_t ret; |
| |
| if (cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL)) |
| goto failed; |
| |
| sock = g_io_channel_unix_get_fd(chan); |
| |
| ret = read(sock, buf, control->imtu); |
| if (ret <= 0) |
| goto failed; |
| |
| if (ret < AVCTP_HEADER_LENGTH) { |
| error("Too small AVCTP packet"); |
| goto failed; |
| } |
| |
| avctp = (struct avctp_header *) buf; |
| |
| ret -= AVCTP_HEADER_LENGTH; |
| if (ret < AVC_HEADER_LENGTH) { |
| error("Too small AVC packet"); |
| goto failed; |
| } |
| |
| avc = (struct avc_header *) (buf + AVCTP_HEADER_LENGTH); |
| |
| ret -= AVC_HEADER_LENGTH; |
| |
| operands = (uint8_t *) avc + AVC_HEADER_LENGTH; |
| operand_count = ret; |
| |
| if (avctp->cr == AVCTP_RESPONSE) { |
| control_response(control, avctp, avc, operands, operand_count); |
| return TRUE; |
| } |
| |
| packet_size = AVCTP_HEADER_LENGTH + AVC_HEADER_LENGTH; |
| avctp->cr = AVCTP_RESPONSE; |
| |
| if (avctp->packet_type != AVCTP_PACKET_SINGLE) { |
| avc->code = AVC_CTYPE_NOT_IMPLEMENTED; |
| goto done; |
| } |
| |
| if (avctp->pid != htons(AV_REMOTE_SVCLASS_ID)) { |
| avctp->ipid = 1; |
| packet_size = AVCTP_HEADER_LENGTH; |
| goto done; |
| } |
| |
| handler = find_handler(control->handlers, avc->opcode); |
| if (!handler) { |
| DBG("handler not found for 0x%02x", avc->opcode); |
| avc->code = AVC_CTYPE_REJECTED; |
| goto done; |
| } |
| |
| code = avc->code; |
| subunit = avc->subunit_type; |
| |
| ret = handler->cb(session, avctp->transaction, &code, |
| &subunit, operands, operand_count, |
| handler->user_data); |
| if (ret < 0) { |
| if (ret == -EAGAIN) |
| return TRUE; |
| goto failed; |
| } |
| |
| packet_size += ret; |
| avc->code = code; |
| avc->subunit_type = subunit; |
| |
| done: |
| ret = write(sock, buf, packet_size); |
| if (ret != packet_size) |
| goto failed; |
| |
| return TRUE; |
| |
| failed: |
| DBG("AVCTP session %p got disconnected", session); |
| avctp_shutdown(session); |
| return FALSE; |
| } |
| |
| static int uinput_create(const 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); |
| 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); |
| return err; |
| } |
| |
| return fd; |
| } |
| |
| int avctp_init_uinput(struct avctp *session, const char *name, |
| const char *address) |
| { |
| if (g_str_equal(name, "Nokia CK-20W")) { |
| session->key_quirks[AVC_FORWARD] |= QUIRK_NO_RELEASE; |
| session->key_quirks[AVC_BACKWARD] |= QUIRK_NO_RELEASE; |
| session->key_quirks[AVC_PLAY] |= QUIRK_NO_RELEASE; |
| session->key_quirks[AVC_PAUSE] |= QUIRK_NO_RELEASE; |
| } |
| |
| session->uinput = uinput_create(address); |
| if (session->uinput < 0) { |
| error("AVCTP: failed to init uinput for %s", address); |
| return session->uinput; |
| } |
| |
| return 0; |
| } |
| |
| static struct avctp_channel *avctp_channel_create(struct avctp *session, int fd, |
| size_t imtu, size_t omtu, |
| avctp_destroy_cb_t destroy) |
| { |
| struct avctp_channel *chan; |
| |
| chan = g_new0(struct avctp_channel, 1); |
| chan->session = session; |
| chan->io = g_io_channel_unix_new(fd); |
| chan->queue = g_queue_new(); |
| chan->imtu = imtu; |
| chan->omtu = omtu; |
| chan->buffer = g_malloc0(MAX(imtu, omtu)); |
| chan->destroy = destroy; |
| |
| return chan; |
| } |
| |
| static void handler_free(void *data) |
| { |
| struct avctp_browsing_pdu_handler *handler = data; |
| |
| if (handler->destroy) |
| handler->destroy(handler->user_data); |
| |
| g_free(data); |
| } |
| |
| static void avctp_destroy_browsing(void *data) |
| { |
| struct avctp_channel *chan = data; |
| |
| g_slist_free_full(chan->handlers, handler_free); |
| |
| chan->handlers = NULL; |
| } |
| |
| static struct avctp_pending_req *pending_create(struct avctp_channel *chan, |
| avctp_process_cb process, |
| void *data, |
| avctp_destroy_cb_t destroy) |
| { |
| struct avctp_pending_req *p; |
| GSList *l, *tmp; |
| |
| if (!chan->processed) |
| goto done; |
| |
| tmp = g_slist_copy(chan->processed); |
| |
| /* Find first unused transaction id */ |
| for (l = tmp; l; l = g_slist_next(l)) { |
| struct avctp_pending_req *req = l->data; |
| |
| if (req->transaction == chan->transaction) { |
| chan->transaction++; |
| chan->transaction %= 16; |
| tmp = g_slist_delete_link(tmp, l); |
| l = tmp; |
| } |
| } |
| |
| g_slist_free(tmp); |
| |
| done: |
| p = g_new0(struct avctp_pending_req, 1); |
| p->chan = chan; |
| p->transaction = chan->transaction; |
| p->process = process; |
| p->data = data; |
| p->destroy = destroy; |
| |
| chan->transaction++; |
| chan->transaction %= 16; |
| |
| return p; |
| } |
| |
| static int avctp_send_req(struct avctp *session, uint8_t code, |
| uint8_t subunit, uint8_t opcode, |
| uint8_t *operands, size_t operand_count, |
| avctp_rsp_cb func, void *user_data) |
| { |
| struct avctp_channel *control = session->control; |
| struct avctp_pending_req *p; |
| struct avctp_control_req *req; |
| |
| if (control == NULL) |
| return -ENOTCONN; |
| |
| req = g_new0(struct avctp_control_req, 1); |
| req->code = code; |
| req->subunit = subunit; |
| req->op = opcode; |
| req->func = func; |
| req->operands = g_memdup(operands, operand_count); |
| req->operand_count = operand_count; |
| req->user_data = user_data; |
| |
| p = pending_create(control, process_control, req, control_req_destroy); |
| |
| req->p = p; |
| |
| g_queue_push_tail(control->queue, p); |
| |
| if (control->process_id == 0) |
| control->process_id = g_idle_add(process_queue, control); |
| |
| return 0; |
| } |
| |
| int avctp_send_browsing_req(struct avctp *session, |
| uint8_t *operands, size_t operand_count, |
| avctp_browsing_rsp_cb func, void *user_data) |
| { |
| struct avctp_channel *browsing = session->browsing; |
| struct avctp_pending_req *p; |
| struct avctp_browsing_req *req; |
| |
| if (browsing == NULL) |
| return -ENOTCONN; |
| |
| req = g_new0(struct avctp_browsing_req, 1); |
| req->func = func; |
| req->operands = g_memdup(operands, operand_count); |
| req->operand_count = operand_count; |
| req->user_data = user_data; |
| |
| p = pending_create(browsing, process_browsing, req, |
| browsing_req_destroy); |
| |
| req->p = p; |
| |
| g_queue_push_tail(browsing->queue, p); |
| |
| /* Connection did not complete, delay process of the request */ |
| if (browsing->watch == 0) |
| return 0; |
| |
| if (browsing->process_id == 0) |
| browsing->process_id = g_idle_add(process_queue, browsing); |
| |
| return 0; |
| } |
| |
| static const char *op2str(uint8_t op) |
| { |
| int i; |
| |
| for (i = 0; key_map[i].name != NULL; i++) { |
| if ((op & 0x7F) == key_map[i].avc) |
| return key_map[i].name; |
| } |
| |
| return "UNKNOWN"; |
| } |
| |
| static int avctp_passthrough_press(struct avctp *session, uint8_t op, |
| uint8_t *params, size_t params_len) |
| { |
| uint8_t operands[7]; |
| size_t len; |
| |
| DBG("op 0x%02x %s params_len %zd", op, op2str(op), params_len); |
| |
| /* Button pressed */ |
| operands[0] = op & 0x7f; |
| |
| if (op == AVC_VENDOR_UNIQUE && params && |
| params_len == 5) { |
| memcpy(&operands[2], params, params_len); |
| len = params_len + 2; |
| operands[1] = params_len; |
| } else { |
| len = 2; |
| operands[1] = 0; |
| } |
| |
| return avctp_send_req(session, AVC_CTYPE_CONTROL, |
| AVC_SUBUNIT_PANEL, AVC_OP_PASSTHROUGH, |
| operands, len, |
| avctp_passthrough_rsp, NULL); |
| } |
| |
| static int avctp_passthrough_release(struct avctp *session, uint8_t op, |
| uint8_t *params, size_t params_len) |
| { |
| uint8_t operands[7]; |
| size_t len; |
| |
| DBG("%s", op2str(op)); |
| |
| /* Button released */ |
| operands[0] = op | 0x80; |
| operands[1] = 0; |
| |
| if (op == AVC_VENDOR_UNIQUE && params && |
| params_len > sizeof(operands) - 2) { |
| memcpy(&operands[2], params, params_len); |
| len = params_len; |
| } else |
| len = 2; |
| |
| return avctp_send_req(session, AVC_CTYPE_CONTROL, |
| AVC_SUBUNIT_PANEL, AVC_OP_PASSTHROUGH, |
| operands, len, |
| NULL, NULL); |
| } |
| |
| static gboolean repeat_timeout(gpointer user_data) |
| { |
| struct avctp *session = user_data; |
| |
| avctp_passthrough_release(session, session->key.op, session->key.params, |
| session->key.params_len); |
| avctp_passthrough_press(session, session->key.op, session->key.params, |
| session->key.params_len); |
| |
| return TRUE; |
| } |
| |
| static void release_pressed(struct avctp *session) |
| { |
| avctp_passthrough_release(session, session->key.op, session->key.params, |
| session->key.params_len); |
| |
| if (session->key.timer > 0) |
| g_source_remove(session->key.timer); |
| |
| session->key.timer = 0; |
| } |
| |
| static bool set_pressed(struct avctp *session, uint8_t op, uint8_t *params, |
| size_t params_len) |
| { |
| if (session->key.timer > 0) { |
| if (session->key.op == op) |
| return TRUE; |
| release_pressed(session); |
| } |
| |
| if (op != AVC_FAST_FORWARD && op != AVC_REWIND) |
| return FALSE; |
| |
| session->key.op = op; |
| session->key.params = params; |
| session->key.params_len = params_len; |
| session->key.timer = g_timeout_add_seconds(AVC_PRESS_TIMEOUT, |
| repeat_timeout, |
| session); |
| |
| return TRUE; |
| } |
| |
| static gboolean avctp_passthrough_rsp(struct avctp *session, uint8_t code, |
| uint8_t subunit, uint8_t *operands, |
| size_t operand_count, void *user_data) |
| { |
| uint8_t *params; |
| size_t params_len; |
| |
| DBG("code 0x%02x operand_count %zd", code, operand_count); |
| |
| if (code != AVC_CTYPE_ACCEPTED) |
| return FALSE; |
| |
| if (operands[0] == AVC_VENDOR_UNIQUE) { |
| params = &operands[2]; |
| params_len = operand_count - 2; |
| } else { |
| params = NULL; |
| params_len = 0; |
| } |
| |
| if (set_pressed(session, operands[0], params, params_len)) |
| return FALSE; |
| |
| avctp_passthrough_release(session, operands[0], params, params_len); |
| |
| return FALSE; |
| } |
| |
| int avctp_send_passthrough(struct avctp *session, uint8_t op, uint8_t *params, |
| size_t params_len) |
| { |
| /* Auto release if key pressed */ |
| if (session->key.timer > 0) |
| release_pressed(session); |
| |
| return avctp_passthrough_press(session, op, params, params_len); |
| } |
| |
| int avctp_send_vendordep(struct avctp *session, uint8_t transaction, |
| uint8_t code, uint8_t subunit, |
| uint8_t *operands, size_t operand_count) |
| { |
| struct avctp_channel *control = session->control; |
| |
| if (control == NULL) |
| return -ENOTCONN; |
| |
| return avctp_send(control, transaction, AVCTP_RESPONSE, code, subunit, |
| AVC_OP_VENDORDEP, operands, operand_count); |
| } |
| |
| int avctp_send_vendordep_req(struct avctp *session, uint8_t code, |
| uint8_t subunit, uint8_t *operands, |
| size_t operand_count, |
| avctp_rsp_cb func, void *user_data) |
| { |
| return avctp_send_req(session, code, subunit, AVC_OP_VENDORDEP, |
| operands, operand_count, |
| func, user_data); |
| } |
| |
| unsigned int avctp_register_passthrough_handler(struct avctp *session, |
| avctp_passthrough_cb cb, |
| void *user_data) |
| { |
| struct avctp_channel *control = session->control; |
| struct avctp_passthrough_handler *handler; |
| static unsigned int id = 0; |
| |
| if (control == NULL || session->handler != NULL) |
| return 0; |
| |
| handler = g_new(struct avctp_passthrough_handler, 1); |
| handler->cb = cb; |
| handler->user_data = user_data; |
| handler->id = ++id; |
| |
| session->handler = handler; |
| |
| return handler->id; |
| } |
| |
| bool avctp_unregister_passthrough_handler(struct avctp *session, |
| unsigned int id) |
| { |
| if (session->handler == NULL) |
| return false; |
| |
| if (session->handler->id != id) |
| return false; |
| |
| g_free(session->handler); |
| session->handler = NULL; |
| return true; |
| } |
| |
| unsigned int avctp_register_pdu_handler(struct avctp *session, uint8_t opcode, |
| avctp_control_pdu_cb cb, |
| void *user_data) |
| { |
| struct avctp_channel *control = session->control; |
| struct avctp_pdu_handler *handler; |
| static unsigned int id = 0; |
| |
| if (control == NULL) |
| return 0; |
| |
| handler = find_handler(control->handlers, opcode); |
| if (handler) |
| return 0; |
| |
| handler = g_new(struct avctp_pdu_handler, 1); |
| handler->opcode = opcode; |
| handler->cb = cb; |
| handler->user_data = user_data; |
| handler->id = ++id; |
| |
| control->handlers = g_slist_append(control->handlers, handler); |
| |
| return handler->id; |
| } |
| |
| unsigned int avctp_register_browsing_pdu_handler(struct avctp *session, |
| avctp_browsing_pdu_cb cb, |
| void *user_data, |
| avctp_destroy_cb_t destroy) |
| { |
| struct avctp_channel *browsing = session->browsing; |
| struct avctp_browsing_pdu_handler *handler; |
| static unsigned int id = 0; |
| |
| if (browsing == NULL) |
| return 0; |
| |
| if (browsing->handlers != NULL) |
| return 0; |
| |
| handler = g_new(struct avctp_browsing_pdu_handler, 1); |
| handler->cb = cb; |
| handler->user_data = user_data; |
| handler->id = ++id; |
| handler->destroy = destroy; |
| |
| browsing->handlers = g_slist_append(browsing->handlers, handler); |
| |
| return handler->id; |
| } |
| |
| bool avctp_unregister_pdu_handler(struct avctp *session, unsigned int id) |
| { |
| struct avctp_channel *control = session->control; |
| GSList *l; |
| |
| if (!control) |
| return false; |
| |
| for (l = control->handlers; l; l = g_slist_next(l)) { |
| struct avctp_pdu_handler *handler = l->data; |
| |
| if (handler->id != id) |
| continue; |
| |
| control->handlers = g_slist_remove(control->handlers, handler); |
| g_free(handler); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| bool avctp_unregister_browsing_pdu_handler(struct avctp *session, |
| unsigned int id) |
| { |
| struct avctp_channel *browsing = session->browsing; |
| GSList *l; |
| |
| if (browsing == NULL) |
| return false; |
| |
| for (l = browsing->handlers; l; l = g_slist_next(l)) { |
| struct avctp_browsing_pdu_handler *handler = l->data; |
| |
| if (handler->id != id) |
| continue; |
| |
| browsing->handlers = g_slist_remove(browsing->handlers, |
| handler); |
| g_free(handler); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| struct avctp *avctp_new(int fd, size_t imtu, size_t omtu, uint16_t version) |
| { |
| struct avctp *session; |
| struct avctp_channel *control; |
| GIOCondition cond = G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL; |
| |
| session = g_new0(struct avctp, 1); |
| session->version = version; |
| |
| control = avctp_channel_create(session, fd, imtu, omtu, NULL); |
| if (!control) { |
| g_free(session); |
| return NULL; |
| } |
| |
| session->uinput = -1; |
| session->control = control; |
| session->passthrough_id = avctp_register_pdu_handler(session, |
| AVC_OP_PASSTHROUGH, |
| handle_panel_passthrough, |
| NULL); |
| session->unit_id = avctp_register_pdu_handler(session, |
| AVC_OP_UNITINFO, |
| handle_unit_info, |
| NULL); |
| session->subunit_id = avctp_register_pdu_handler(session, |
| AVC_OP_SUBUNITINFO, |
| handle_subunit_info, |
| NULL); |
| |
| control->watch = g_io_add_watch(session->control->io, cond, |
| (GIOFunc) session_cb, session); |
| |
| return session; |
| } |
| |
| int avctp_connect_browsing(struct avctp *session, int fd, size_t imtu, |
| size_t omtu) |
| { |
| struct avctp_channel *browsing; |
| GIOCondition cond = G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL; |
| |
| if (session->browsing) |
| return -EISCONN; |
| |
| browsing = avctp_channel_create(session, fd, imtu, omtu, |
| avctp_destroy_browsing); |
| if (!browsing) |
| return -EINVAL; |
| |
| session->browsing = browsing; |
| browsing->watch = g_io_add_watch(session->browsing->io, cond, |
| (GIOFunc) session_browsing_cb, session); |
| |
| return 0; |
| } |
| |
| void avctp_set_destroy_cb(struct avctp *session, avctp_destroy_cb_t cb, |
| void *user_data) |
| { |
| session->destroy = cb; |
| session->data = user_data; |
| } |
| |
| void avctp_shutdown(struct avctp *session) |
| { |
| if (!session) |
| return; |
| |
| if (session->browsing) |
| avctp_channel_destroy(session->browsing); |
| |
| if (session->control) |
| avctp_channel_destroy(session->control); |
| |
| if (session->destroy) |
| session->destroy(session->data); |
| |
| g_free(session->handler); |
| |
| if (session->key.timer > 0) |
| g_source_remove(session->key.timer); |
| |
| if (session->uinput >= 0) { |
| DBG("AVCTP: closing uinput"); |
| |
| ioctl(session->uinput, UI_DEV_DESTROY); |
| close(session->uinput); |
| session->uinput = -1; |
| } |
| |
| g_free(session); |
| } |