blob: f1f2b4ec14d99bd14156b74a50465648b208ba81 [file]
/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
*
* BlueZ - Bluetooth protocol stack for Linux
*
* Copyright (C) 2025 Pauli Virtanen
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#include <linux/uinput.h>
#include "bluetooth/bluetooth.h"
#include "src/shared/util.h"
#include "src/shared/uinput.h"
#define DBG(uinput, fmt, arg...) \
uinput_debug(uinput->debug_func, uinput->debug_data, "%s:%s() " fmt, \
__FILE__, __func__, ## arg)
struct bt_uinput {
struct input_id dev_id;
char name[UINPUT_MAX_NAME_SIZE];
bdaddr_t addr;
int fd;
bt_uinput_debug_func_t debug_func;
void *debug_data;
};
static void uinput_debug(bt_uinput_debug_func_t debug_func, void *debug_data,
const char *format, ...)
{
va_list ap;
if (!debug_func || !format)
return;
va_start(ap, format);
util_debug_va(debug_func, debug_data, format, ap);
va_end(ap);
}
static int uinput_emit(struct bt_uinput *uinput, uint16_t type, uint16_t code,
int32_t val)
{
struct input_event ie;
memset(&ie, 0, sizeof(ie));
ie.type = type;
ie.code = code;
ie.value = val;
return write(uinput->fd, &ie, sizeof(ie));
}
void bt_uinput_send_key(struct bt_uinput *uinput, uint16_t key, bool pressed)
{
if (!uinput)
return;
DBG(uinput, "%d", key);
uinput_emit(uinput, EV_KEY, key, pressed ? 1 : 0);
uinput_emit(uinput, EV_SYN, SYN_REPORT, 0);
}
struct bt_uinput *bt_uinput_new(const char *name, const char *suffix,
const bdaddr_t *addr,
const struct input_id *dev_id)
{
struct bt_uinput *uinput;
const size_t name_max = sizeof(uinput->name);
uinput = new0(struct bt_uinput, 1);
uinput->fd = -1;
if (name)
snprintf(uinput->name, name_max, "%s", name);
if (suffix) {
size_t name_len = strlen(uinput->name);
size_t suffix_len = strlen(suffix);
size_t pos = name_len;
if (suffix_len > name_max - 1)
suffix_len = name_max - 1;
if (pos + suffix_len > name_max - 1)
pos = name_max - 1 - suffix_len;
snprintf(uinput->name + pos, name_max - pos, "%s", suffix);
}
if (addr)
bacpy(&uinput->addr, addr);
if (dev_id) {
uinput->dev_id.bustype = dev_id->bustype;
uinput->dev_id.product = dev_id->product;
uinput->dev_id.vendor = dev_id->vendor;
uinput->dev_id.version = dev_id->version;
} else {
uinput->dev_id.bustype = BUS_BLUETOOTH;
}
return uinput;
}
void bt_uinput_set_debug(struct bt_uinput *uinput,
bt_uinput_debug_func_t debug_func,
void *user_data)
{
if (!uinput)
return;
uinput->debug_func = debug_func;
uinput->debug_data = user_data;
}
int bt_uinput_create(struct bt_uinput *uinput,
const struct bt_uinput_key_map *key_map)
{
int fd = -1;
struct uinput_user_dev dev;
size_t i;
int err;
char addr[18];
if (!uinput || uinput->fd >= 0)
return -EINVAL;
fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK);
if (fd < 0)
fd = open("/dev/input/uinput", O_WRONLY | O_NONBLOCK);
if (fd < 0)
fd = open("/dev/misc/uinput", O_WRONLY | O_NONBLOCK);
if (fd < 0) {
err = -errno;
DBG(uinput, "Failed to open /dev/uinput: %s", strerror(-err));
goto fail;
}
ioctl(fd, UI_SET_EVBIT, EV_KEY);
ioctl(fd, UI_SET_EVBIT, EV_SYN);
for (i = 0; key_map[i].name; ++i)
ioctl(fd, UI_SET_KEYBIT, key_map[i].uinput);
ba2strlc(&uinput->addr, addr);
ioctl(fd, UI_SET_PHYS, addr);
memset(&dev, 0, sizeof(dev));
dev.id = uinput->dev_id;
snprintf(dev.name, sizeof(dev.name), "%s", uinput->name);
if (write(fd, &dev, sizeof(dev)) < 0) {
err = -errno;
DBG(uinput, "Failed to write setup: %s", strerror(-err));
goto fail;
}
if (ioctl(fd, UI_DEV_CREATE) < 0) {
err = -errno;
DBG(uinput, "Failed to create device: %s", strerror(-err));
goto fail;
}
DBG(uinput, "%p", uinput);
uinput->fd = fd;
return 0;
fail:
if (fd >= 0)
close(fd);
return err;
}
void bt_uinput_destroy(struct bt_uinput *uinput)
{
if (!uinput)
return;
DBG(uinput, "%p", uinput);
if (uinput->fd >= 0) {
ioctl(uinput->fd, UI_DEV_DESTROY);
close(uinput->fd);
}
free(uinput);
}