1630 lines
35 KiB
C
1630 lines
35 KiB
C
/*
|
|
*
|
|
* 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;
|
|
struct iovec *iov;
|
|
int iov_cnt;
|
|
avctp_rsp_cb func;
|
|
void *user_data;
|
|
};
|
|
|
|
struct avctp_browsing_req {
|
|
struct avctp_pending_req *p;
|
|
struct iovec *iov;
|
|
int iov_cnt;
|
|
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,
|
|
const struct iovec *iov, int iov_cnt)
|
|
{
|
|
struct avctp_header avctp;
|
|
struct avc_header avc;
|
|
struct msghdr msg;
|
|
int sk, err = 0;
|
|
struct iovec pdu[iov_cnt + 2];
|
|
int i;
|
|
size_t len = sizeof(avctp) + sizeof(avc);
|
|
|
|
DBG("");
|
|
|
|
pdu[0].iov_base = &avctp;
|
|
pdu[0].iov_len = sizeof(avctp);
|
|
pdu[1].iov_base = &avc;
|
|
pdu[1].iov_len = sizeof(avc);
|
|
|
|
for (i = 0; i < iov_cnt; i++) {
|
|
pdu[i + 2].iov_base = iov[i].iov_base;
|
|
pdu[i + 2].iov_len = iov[i].iov_len;
|
|
len += iov[i].iov_len;
|
|
}
|
|
|
|
if (control->omtu < len)
|
|
return -EOVERFLOW;
|
|
|
|
sk = g_io_channel_unix_get_fd(control->io);
|
|
|
|
memset(&avctp, 0, sizeof(avctp));
|
|
|
|
avctp.transaction = transaction;
|
|
avctp.packet_type = AVCTP_PACKET_SINGLE;
|
|
avctp.cr = cr;
|
|
avctp.pid = htons(AV_REMOTE_SVCLASS_ID);
|
|
|
|
memset(&avc, 0, sizeof(avc));
|
|
|
|
avc.code = code;
|
|
avc.subunit_type = subunit;
|
|
avc.opcode = opcode;
|
|
|
|
memset(&msg, 0, sizeof(msg));
|
|
msg.msg_iov = pdu;
|
|
msg.msg_iovlen = iov_cnt + 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,
|
|
const struct iovec *iov, int iov_cnt)
|
|
{
|
|
struct avctp_header avctp;
|
|
struct msghdr msg;
|
|
struct iovec pdu[iov_cnt + 1];
|
|
int sk, err = 0;
|
|
int i;
|
|
size_t len = sizeof(avctp);
|
|
|
|
for (i = 0; i < iov_cnt; i++) {
|
|
pdu[i + 1].iov_base = iov[i].iov_base;
|
|
pdu[i + 1].iov_len = iov[i].iov_len;
|
|
len += iov[i].iov_len;
|
|
}
|
|
|
|
pdu[0].iov_base = &avctp;
|
|
pdu[0].iov_len = sizeof(avctp);
|
|
|
|
if (browsing->omtu < len)
|
|
return -EOVERFLOW;
|
|
|
|
sk = g_io_channel_unix_get_fd(browsing->io);
|
|
|
|
memset(&avctp, 0, sizeof(avctp));
|
|
|
|
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 = pdu;
|
|
msg.msg_iovlen = iov_cnt + 1;
|
|
|
|
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;
|
|
int i;
|
|
|
|
if (p->err == 0 || req->func == NULL)
|
|
goto done;
|
|
|
|
req->func(session, AVC_CTYPE_REJECTED, req->subunit, NULL, 0,
|
|
req->user_data);
|
|
|
|
done:
|
|
for (i = 0; i < req->iov_cnt; i++)
|
|
g_free(req->iov[i].iov_base);
|
|
|
|
g_free(req->iov);
|
|
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;
|
|
int i;
|
|
|
|
if (p->err == 0 || req->func == NULL)
|
|
goto done;
|
|
|
|
req->func(session, NULL, 0, req->user_data);
|
|
|
|
done:
|
|
for (i = 0; i < req->iov_cnt; i++)
|
|
g_free(req->iov[i].iov_base);
|
|
|
|
g_free(req->iov);
|
|
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->iov, req->iov_cnt);
|
|
}
|
|
|
|
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->iov, req->iov_cnt);
|
|
}
|
|
|
|
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;
|
|
|
|
if (ret < AVCTP_HEADER_LENGTH) {
|
|
error("Too small AVCTP packet");
|
|
goto failed;
|
|
}
|
|
|
|
avctp = (struct avctp_header *) buf;
|
|
|
|
if (avctp->packet_type != AVCTP_PACKET_SINGLE) {
|
|
error("Invalid packet type");
|
|
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;
|
|
}
|
|
|
|
ret = handler->cb(session, avctp->transaction, operands, operand_count,
|
|
handler->user_data);
|
|
if (ret < 0) {
|
|
if (ret == -EAGAIN)
|
|
return TRUE;
|
|
goto failed;
|
|
}
|
|
|
|
packet_size += ret;
|
|
|
|
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, const struct iovec *iov, int iov_cnt,
|
|
avctp_rsp_cb func, void *user_data)
|
|
{
|
|
struct avctp_channel *control = session->control;
|
|
struct avctp_pending_req *p;
|
|
struct avctp_control_req *req;
|
|
struct iovec *pdu;
|
|
int i;
|
|
|
|
if (control == NULL)
|
|
return -ENOTCONN;
|
|
|
|
pdu = g_new0(struct iovec, iov_cnt);
|
|
|
|
for (i = 0; i < iov_cnt; i++) {
|
|
pdu[i].iov_len = iov[i].iov_len;
|
|
pdu[i].iov_base = g_memdup(iov[i].iov_base, iov[i].iov_len);
|
|
}
|
|
|
|
req = g_new0(struct avctp_control_req, 1);
|
|
req->code = code;
|
|
req->subunit = subunit;
|
|
req->op = opcode;
|
|
req->func = func;
|
|
req->iov = pdu;
|
|
req->iov_cnt = iov_cnt;
|
|
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,
|
|
const struct iovec *iov, int iov_cnt,
|
|
avctp_browsing_rsp_cb func, void *user_data)
|
|
{
|
|
struct avctp_channel *browsing = session->browsing;
|
|
struct avctp_pending_req *p;
|
|
struct avctp_browsing_req *req;
|
|
struct iovec *pdu;
|
|
int i;
|
|
|
|
if (browsing == NULL)
|
|
return -ENOTCONN;
|
|
|
|
pdu = g_new0(struct iovec, iov_cnt);
|
|
|
|
for (i = 0; i < iov_cnt; i++) {
|
|
pdu[i].iov_len = iov[i].iov_len;
|
|
pdu[i].iov_base = g_memdup(iov[i].iov_base, iov[i].iov_len);
|
|
}
|
|
|
|
req = g_new0(struct avctp_browsing_req, 1);
|
|
req->func = func;
|
|
req->iov = pdu;
|
|
req->iov_cnt = iov_cnt;
|
|
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;
|
|
}
|
|
|
|
int avctp_send_browsing(struct avctp *session, uint8_t transaction,
|
|
const struct iovec *iov, int iov_cnt)
|
|
{
|
|
struct avctp_channel *browsing = session->browsing;
|
|
|
|
if (browsing == NULL)
|
|
return -ENOTCONN;
|
|
|
|
return avctp_browsing_send(browsing, transaction, AVCTP_RESPONSE,
|
|
iov, iov_cnt);
|
|
}
|
|
|
|
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)
|
|
{
|
|
struct iovec iov[2];
|
|
int iov_cnt;
|
|
uint8_t operands[2];
|
|
|
|
DBG("%s", op2str(op));
|
|
|
|
iov[0].iov_base = operands;
|
|
iov[0].iov_len = sizeof(operands);
|
|
|
|
/* Button pressed */
|
|
operands[0] = op & 0x7f;
|
|
|
|
if (params_len > 0) {
|
|
iov[1].iov_base = params;
|
|
iov[1].iov_len = params_len;
|
|
iov_cnt = 2;
|
|
operands[1] = params_len;
|
|
} else {
|
|
iov_cnt = 1;
|
|
operands[1] = 0;
|
|
}
|
|
|
|
return avctp_send_req(session, AVC_CTYPE_CONTROL,
|
|
AVC_SUBUNIT_PANEL, AVC_OP_PASSTHROUGH,
|
|
iov, iov_cnt, avctp_passthrough_rsp, NULL);
|
|
}
|
|
|
|
static int avctp_passthrough_release(struct avctp *session, uint8_t op,
|
|
uint8_t *params, size_t params_len)
|
|
{
|
|
struct iovec iov[2];
|
|
int iov_cnt;
|
|
uint8_t operands[2];
|
|
|
|
DBG("%s", op2str(op));
|
|
|
|
iov[0].iov_base = operands;
|
|
iov[0].iov_len = sizeof(operands);
|
|
|
|
/* Button released */
|
|
operands[0] = op | 0x80;
|
|
|
|
if (params_len > 0) {
|
|
iov[1].iov_base = params;
|
|
iov[1].iov_len = params_len;
|
|
iov_cnt = 2;
|
|
operands[1] = params_len;
|
|
} else {
|
|
iov_cnt = 1;
|
|
operands[1] = 0;
|
|
}
|
|
|
|
return avctp_send_req(session, AVC_CTYPE_CONTROL,
|
|
AVC_SUBUNIT_PANEL, AVC_OP_PASSTHROUGH,
|
|
iov, iov_cnt, 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_vendor(struct avctp *session, uint8_t transaction,
|
|
uint8_t code, uint8_t subunit,
|
|
const struct iovec *iov, int iov_cnt)
|
|
{
|
|
struct avctp_channel *control = session->control;
|
|
|
|
if (control == NULL)
|
|
return -ENOTCONN;
|
|
|
|
return avctp_send(control, transaction, AVCTP_RESPONSE, code, subunit,
|
|
AVC_OP_VENDORDEP, iov, iov_cnt);
|
|
}
|
|
|
|
int avctp_send_vendor_req(struct avctp *session, uint8_t code, uint8_t subunit,
|
|
const struct iovec *iov, int iov_cnt,
|
|
avctp_rsp_cb func, void *user_data)
|
|
{
|
|
return avctp_send_req(session, code, subunit, AVC_OP_VENDORDEP, iov,
|
|
iov_cnt, 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);
|
|
}
|