mirror of
https://git.kernel.org/pub/scm/bluetooth/bluez.git
synced 2024-11-25 21:24:16 +08:00
Add uinput support for AVRCP
This commit is contained in:
parent
8c29a4d5d2
commit
589d60855e
155
audio/control.c
155
audio/control.c
@ -32,6 +32,9 @@
|
||||
#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 <glib.h>
|
||||
@ -45,6 +48,7 @@
|
||||
#include "dbus.h"
|
||||
#include "dbus-helper.h"
|
||||
#include "logging.h"
|
||||
#include "uinput.h"
|
||||
#include "device.h"
|
||||
#include "manager.h"
|
||||
#include "avdtp.h"
|
||||
@ -84,6 +88,8 @@
|
||||
#define PLAY_OP 0x44
|
||||
#define STOP_OP 0x45
|
||||
#define PAUSE_OP 0x46
|
||||
#define REWIND_OP 0x48
|
||||
#define FAST_FORWARD_OP 0x49
|
||||
#define NEXT_OP 0x4b
|
||||
#define PREV_OP 0x4c
|
||||
|
||||
@ -150,6 +156,8 @@ struct avctp {
|
||||
bdaddr_t src;
|
||||
bdaddr_t dst;
|
||||
|
||||
int uinput;
|
||||
|
||||
int sock;
|
||||
|
||||
guint io;
|
||||
@ -163,7 +171,6 @@ struct control {
|
||||
struct avctp *session;
|
||||
};
|
||||
|
||||
|
||||
static int avrcp_ct_record(sdp_buf_t *buf)
|
||||
{
|
||||
sdp_list_t *svclass_id, *pfseq, *apseq, *root;
|
||||
@ -385,10 +392,77 @@ static void avctp_unref(struct avctp *session)
|
||||
close(session->sock);
|
||||
if (session->io)
|
||||
g_source_remove(session->io);
|
||||
|
||||
session->dev->control->session = NULL;
|
||||
|
||||
if (session->uinput >= 0) {
|
||||
ioctl(session->uinput, UI_DEV_DESTROY);
|
||||
close(session->uinput);
|
||||
}
|
||||
|
||||
g_free(session);
|
||||
}
|
||||
|
||||
static int uinput_create(char *name)
|
||||
{
|
||||
struct uinput_dev dev;
|
||||
int fd, err;
|
||||
|
||||
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);
|
||||
|
||||
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_KEYBIT, KEY_PLAYPAUSE);
|
||||
ioctl(fd, UI_SET_KEYBIT, KEY_STOP);
|
||||
ioctl(fd, UI_SET_KEYBIT, KEY_NEXT);
|
||||
ioctl(fd, UI_SET_KEYBIT, KEY_PREVIOUS);
|
||||
ioctl(fd, UI_SET_KEYBIT, KEY_REWIND);
|
||||
ioctl(fd, UI_SET_KEYBIT, KEY_FORWARD);
|
||||
|
||||
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 struct avctp *avctp_get(bdaddr_t *src, bdaddr_t *dst)
|
||||
{
|
||||
struct avctp *session;
|
||||
@ -406,6 +480,7 @@ static struct avctp *avctp_get(bdaddr_t *src, bdaddr_t *dst)
|
||||
|
||||
session = g_new0(struct avctp, 1);
|
||||
|
||||
session->uinput = -1;
|
||||
session->sock = -1;
|
||||
bacpy(&session->src, src);
|
||||
bacpy(&session->dst, dst);
|
||||
@ -415,33 +490,88 @@ static struct avctp *avctp_get(bdaddr_t *src, bdaddr_t *dst)
|
||||
return session;
|
||||
}
|
||||
|
||||
static void handle_panel_passthrough(const unsigned char *operands, int operand_count)
|
||||
static void init_uinput(struct avctp *session)
|
||||
{
|
||||
char address[18], *name;
|
||||
|
||||
ba2str(&session->dst, address);
|
||||
|
||||
name = session->dev->name ? session->dev->name : address;
|
||||
|
||||
session->uinput = uinput_create(name);
|
||||
if (session->uinput < 0)
|
||||
error("AVRCP: failed to init uinput for %s", name);
|
||||
else
|
||||
debug("AVRCP: uinput initialized for %s", name);
|
||||
}
|
||||
|
||||
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 avctp *session,
|
||||
const unsigned char *operands,
|
||||
int operand_count)
|
||||
{
|
||||
const char *status;
|
||||
int pressed;
|
||||
|
||||
if (operand_count == 0)
|
||||
return;
|
||||
|
||||
if (operands[0] & 0x80)
|
||||
if (operands[0] & 0x80) {
|
||||
status = "released";
|
||||
else
|
||||
pressed = 0;
|
||||
} else {
|
||||
status = "pressed";
|
||||
pressed = 1;
|
||||
}
|
||||
|
||||
switch (operands[0] & 0x7F) {
|
||||
case PLAY_OP:
|
||||
debug("AVRCP: PLAY %s", status);
|
||||
send_key(session->uinput, KEY_PLAYPAUSE, pressed);
|
||||
break;
|
||||
case STOP_OP:
|
||||
debug("AVRCP: STOP %s", status);
|
||||
send_key(session->uinput, KEY_STOP, pressed);
|
||||
break;
|
||||
case PAUSE_OP:
|
||||
debug("AVRCP: PAUSE %s", status);
|
||||
send_key(session->uinput, KEY_PLAYPAUSE, pressed);
|
||||
break;
|
||||
case NEXT_OP:
|
||||
debug("AVRCP: NEXT %s", status);
|
||||
send_key(session->uinput, KEY_NEXT, pressed);
|
||||
break;
|
||||
case PREV_OP:
|
||||
debug("AVRCP: PREV %s", status);
|
||||
send_key(session->uinput, KEY_PREVIOUS, pressed);
|
||||
break;
|
||||
case REWIND_OP:
|
||||
debug("AVRCP: REWIND %s", status);
|
||||
send_key(session->uinput, KEY_REWIND, pressed);
|
||||
break;
|
||||
case FAST_FORWARD_OP:
|
||||
debug("AVRCP: FAST FORWARD %s", status);
|
||||
send_key(session->uinput, KEY_FORWARD, pressed);
|
||||
break;
|
||||
default:
|
||||
debug("AVRCP: unknown button 0x%02X %s", operands[0] & 0x7F, status);
|
||||
@ -506,7 +636,7 @@ static gboolean session_cb(GIOChannel *chan, GIOCondition cond,
|
||||
avrcp->code == CTYPE_CONTROL &&
|
||||
avrcp->subunit_type == SUBUNIT_PANEL &&
|
||||
avrcp->opcode == OP_PASSTHROUGH) {
|
||||
handle_panel_passthrough(operands, operand_count);
|
||||
handle_panel_passthrough(session, operands, operand_count);
|
||||
avctp->cr = AVCTP_RESPONSE;
|
||||
avrcp->code = CTYPE_ACCEPTED;
|
||||
ret = write(session->sock, buf, packet_size);
|
||||
@ -553,6 +683,7 @@ static void auth_cb(DBusPendingCall *call, void *data)
|
||||
session->dev = manager_device_connected(&session->dst,
|
||||
AVRCP_TARGET_UUID);
|
||||
session->dev->control->session = session;
|
||||
init_uinput(session);
|
||||
|
||||
dbus_connection_emit_signal(session->dev->conn, session->dev->path,
|
||||
AUDIO_CONTROL_INTERFACE, "Connected",
|
||||
@ -623,6 +754,12 @@ static gboolean avctp_server_cb(GIOChannel *chan, GIOCondition cond, void *data)
|
||||
|
||||
session = avctp_get(&src, &dst);
|
||||
|
||||
if (!session) {
|
||||
error("Unable to create new AVCTP session");
|
||||
close(cli_sk);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if (session->sock >= 0) {
|
||||
error("Refusing unexpected connect from %s", address);
|
||||
close(cli_sk);
|
||||
@ -651,6 +788,7 @@ proceed:
|
||||
session->dev = manager_device_connected(&dst,
|
||||
AVRCP_TARGET_UUID);
|
||||
session->dev->control->session = session;
|
||||
init_uinput(session);
|
||||
flags |= G_IO_IN;
|
||||
dbus_connection_emit_signal(session->dev->conn,
|
||||
session->dev->path,
|
||||
@ -712,6 +850,8 @@ static gboolean avctp_connect_cb(GIOChannel *chan, GIOCondition cond,
|
||||
goto failed;
|
||||
}
|
||||
|
||||
init_uinput(session);
|
||||
|
||||
dbus_connection_emit_signal(session->dev->conn, session->dev->path,
|
||||
AUDIO_CONTROL_INTERFACE, "Connected",
|
||||
DBUS_TYPE_INVALID);
|
||||
@ -744,6 +884,11 @@ gboolean avrcp_connect(struct device *dev)
|
||||
return TRUE;
|
||||
|
||||
session = avctp_get(&dev->src, &dev->dst);
|
||||
if (!session) {
|
||||
error("Unable to create new AVCTP session");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
session->dev = dev;
|
||||
|
||||
memset(&l2a, 0, sizeof(l2a));
|
||||
|
@ -73,51 +73,60 @@ static DBusHandlerResult device_get_address(DBusConnection *conn,
|
||||
return send_message_and_unref(conn, reply);
|
||||
}
|
||||
|
||||
static DBusHandlerResult device_get_name(DBusConnection *conn,
|
||||
DBusMessage *msg, void *data)
|
||||
static char *get_dev_name(DBusConnection *conn, const char *adapter_path,
|
||||
bdaddr_t *bda)
|
||||
{
|
||||
struct device *device = data;
|
||||
DBusMessage *reply, *reply2, *msg2;
|
||||
DBusMessage *msg, *reply;
|
||||
DBusError derr;
|
||||
const char *name;
|
||||
char address[18], *addr_ptr = address;
|
||||
char address[18], *addr_ptr = address, *ret;
|
||||
|
||||
msg2 = dbus_message_new_method_call("org.bluez", device->adapter_path,
|
||||
msg = dbus_message_new_method_call("org.bluez", adapter_path,
|
||||
"org.bluez.Adapter", "GetRemoteName");
|
||||
if (!msg2)
|
||||
return DBUS_HANDLER_RESULT_NEED_MEMORY;
|
||||
if (!msg)
|
||||
return NULL;
|
||||
|
||||
ba2str(&device->dst, address);
|
||||
dbus_message_append_args(msg2, DBUS_TYPE_STRING, &addr_ptr,
|
||||
ba2str(bda, address);
|
||||
dbus_message_append_args(msg, DBUS_TYPE_STRING, &addr_ptr,
|
||||
DBUS_TYPE_INVALID);
|
||||
|
||||
dbus_error_init(&derr);
|
||||
reply2 = dbus_connection_send_with_reply_and_block(conn, msg2, -1,
|
||||
reply = dbus_connection_send_with_reply_and_block(conn, msg, -1,
|
||||
&derr);
|
||||
|
||||
dbus_message_unref(msg2);
|
||||
dbus_message_unref(msg);
|
||||
|
||||
if (dbus_error_is_set(&derr)) {
|
||||
error("%s GetRemoteName(): %s", device->adapter_path,
|
||||
derr.message);
|
||||
error("%s GetRemoteName(): %s", adapter_path, derr.message);
|
||||
dbus_error_free(&derr);
|
||||
return err_failed(conn, msg, "Unable to get remote name");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!dbus_message_get_args(reply, NULL,
|
||||
DBUS_TYPE_STRING, &name,
|
||||
DBUS_TYPE_INVALID))
|
||||
return NULL;
|
||||
|
||||
ret = g_strdup(name);
|
||||
|
||||
dbus_message_unref(reply);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static DBusHandlerResult device_get_name(DBusConnection *conn,
|
||||
DBusMessage *msg, void *data)
|
||||
{
|
||||
struct device *dev = data;
|
||||
DBusMessage *reply;
|
||||
const char *name = dev->name ? dev->name : "";
|
||||
|
||||
reply = dbus_message_new_method_return(msg);
|
||||
if (!reply)
|
||||
return DBUS_HANDLER_RESULT_NEED_MEMORY;
|
||||
|
||||
dbus_message_get_args(reply2, NULL,
|
||||
DBUS_TYPE_STRING, &name,
|
||||
DBUS_TYPE_INVALID);
|
||||
|
||||
dbus_message_append_args(reply, DBUS_TYPE_STRING, &name,
|
||||
DBUS_TYPE_INVALID);
|
||||
|
||||
dbus_message_unref(reply2);
|
||||
|
||||
return send_message_and_unref(conn, reply);
|
||||
}
|
||||
|
||||
@ -193,11 +202,9 @@ static void device_free(struct device *dev)
|
||||
if (dev->conn)
|
||||
dbus_connection_unref(dev->conn);
|
||||
|
||||
if (dev->adapter_path)
|
||||
g_free(dev->adapter_path);
|
||||
|
||||
if (dev->path)
|
||||
g_free(dev->path);
|
||||
g_free(dev->name);
|
||||
|
||||
g_free(dev);
|
||||
}
|
||||
@ -303,6 +310,7 @@ struct device *device_register(DBusConnection *conn,
|
||||
return NULL;
|
||||
}
|
||||
|
||||
dev->name = get_dev_name(conn, dev->adapter_path, bda);
|
||||
dev->path = g_strdup(path);
|
||||
bacpy(&dev->dst, bda);
|
||||
bacpy(&dev->src, &src);
|
||||
|
@ -56,6 +56,7 @@ struct device {
|
||||
DBusConnection *conn;
|
||||
char *adapter_path;
|
||||
char *path;
|
||||
char *name;
|
||||
bdaddr_t store;
|
||||
bdaddr_t src;
|
||||
bdaddr_t dst;
|
||||
|
Loading…
Reference in New Issue
Block a user