diff options
Diffstat (limited to 'clients/linux/linux-uinput.c')
-rw-r--r-- | clients/linux/linux-uinput.c | 266 |
1 files changed, 266 insertions, 0 deletions
diff --git a/clients/linux/linux-uinput.c b/clients/linux/linux-uinput.c new file mode 100644 index 0000000..0a1588b --- /dev/null +++ b/clients/linux/linux-uinput.c @@ -0,0 +1,266 @@ +#include <stdlib.h> +#include <stdint.h> +#include <stdio.h> +#include <stdbool.h> +#include <unistd.h> +#include <signal.h> +#include <errno.h> +#include <limits.h> +#include <assert.h> +#include <err.h> +#include <linux/uinput.h> +#include <fcntl.h> + +#include "common/packet.h" + +struct core { + int input; +}; + +static int +_ioctl(const int fd, const unsigned long request, void *arg) +{ + int ret; + do { + ret = ioctl(fd, request, arg); + } while (ret == -1 && (errno == EINTR || errno == EAGAIN)); + return ret; +} + +#define ioctl(...) error "Use _ioctl instead" + +static bool +uinputd_ioctl_other(const enum ioctl_dir dir, const uint8_t type, const uint8_t nr, const uint16_t bytes, void *arg) +{ + const struct packet packet = { + .type = PACKET_IOCTL, + .ioctl.dir = dir, + .ioctl.type = type, + .ioctl.nr = nr, + .ioctl.arg = IOCTL_ARG_OTHER, + .ioctl.bytes = bytes, + }; + + if (fwrite(&packet, 1, sizeof(packet), stdout) != sizeof(packet)) { + warn("fwrite: failed to write %u bytes", sizeof(packet)); + return false; + } + + if (bytes > 0 && fwrite(arg, 1, bytes, stdout) != bytes) { + warn("fwrite: failed to write %u bytes", bytes); + return false; + } + + return true; +} + +static bool +uinputd_ioctl_int(const enum ioctl_dir dir, const uint8_t type, const uint8_t nr, int arg) +{ + const struct packet packet = { + .type = PACKET_IOCTL, + .ioctl.dir = dir, + .ioctl.type = type, + .ioctl.nr = nr, + .ioctl.arg = IOCTL_ARG_INT, + .ioctl.integer = arg, + }; + + if (fwrite(&packet, 1, sizeof(packet), stdout) != sizeof(packet)) { + warn("fwrite: failed to write %u bytes", sizeof(packet)); + return false; + } + + return true; +} + +static bool +uinputd_write(const int32_t bytes, const void *data) +{ + if (bytes <= 0 || !data) + return true; + + const struct packet packet = { + .type = PACKET_WRITE, + .write.bytes = bytes, + }; + + if (fwrite(&packet, 1, sizeof(packet), stdout) != sizeof(packet)) { + warn("fwrite: failed to write %u bytes", sizeof(packet)); + return false; + } + + if (fwrite(data, 1, bytes, stdout) != bytes) { + warn("fwrite: failed to write %u bytes", bytes); + return false; + } + + return true; +} + +static void +fd_close_or_exit(const int fd) +{ + if (fd >= 0 && close(fd) != 0) + err(EXIT_FAILURE, "close"); +} + +static void +core_release(struct core *core) +{ + if (!core) + return; + + fd_close_or_exit(core->input); + *core = (struct core){0}; +} + +static void +core_init(struct core *core, const char *path) +{ + assert(core); + + if ((core->input = open(path, O_RDONLY | O_CLOEXEC)) < 0) + err(EXIT_FAILURE, "open(%s)", path); + + int evbit; + if (_ioctl(core->input, EVIOCGBIT(0, sizeof(evbit)), &evbit) == -1) + err(EXIT_FAILURE, "ioctl(EVIOCGBIT)"); + + for (int i = 0; i < EV_MAX; ++i) { + if (!(evbit & (1 << i))) + continue; + + if (!uinputd_ioctl_int(IOCTL_IOW, UINPUT_IOCTL_BASE, 100, i)) + errx(EXIT_FAILURE, "ioctl(UI_SET_EVBIT): %d", i); + } + + if ((evbit & (1 << EV_KEY))) { + unsigned char keybit[KEY_MAX / 8 + 1]; + if (_ioctl(core->input, EVIOCGBIT(EV_KEY, sizeof(keybit)), &keybit) == -1) + err(EXIT_FAILURE, "ioctl(EVIOCGBIT:EV_KEY)"); + + for (int i = 0; i < KEY_MAX; ++i) { + if (!(keybit[i / 8] & (1 << (i % 8)))) + continue; + + if (!uinputd_ioctl_int(IOCTL_IOW, UINPUT_IOCTL_BASE, 101, i)) + errx(EXIT_FAILURE, "ioctl(UI_SET_KEYBIT): %d", i); + } + } + + if ((evbit & (1 << EV_REL))) { + int keybit; + if (_ioctl(core->input, EVIOCGBIT(EV_REL, sizeof(keybit)), &keybit) == -1) + err(EXIT_FAILURE, "ioctl(EVIOCGBIT:EV_ABS)"); + + for (int i = 0; i < REL_MAX; ++i) { + if (!(keybit & (1 << i))) + continue; + + if (!uinputd_ioctl_int(IOCTL_IOW, UINPUT_IOCTL_BASE, 102, i)) + errx(EXIT_FAILURE, "ioctl(UI_SET_RELBIT): %d", i); + } + } + + if ((evbit & (1 << EV_ABS))) { + int keybit; + if (_ioctl(core->input, EVIOCGBIT(EV_ABS, sizeof(keybit)), &keybit) == -1) + err(EXIT_FAILURE, "ioctl(EVIOCGBIT:EV_ABS)"); + + for (int i = 0; i < ABS_MAX; ++i) { + if (!(keybit & (1 << i))) + continue; + + if (!uinputd_ioctl_int(IOCTL_IOW, UINPUT_IOCTL_BASE, 103, i)) + errx(EXIT_FAILURE, "ioctl(UI_SET_ABSBIT): %d", i); + } + } + + struct uinput_user_dev setup = {0}; + + if (_ioctl(core->input, EVIOCGNAME(sizeof(setup.name)), setup.name) == -1) + err(EXIT_FAILURE, "ioctl(EVIOCGNAME)"); + + for (size_t i = 0; (evbit & (1 << EV_ABS)) && i < ABS_MAX; ++i) { + struct input_absinfo info; + if (_ioctl(core->input, EVIOCGABS(i), &info) == -1) + err(EXIT_FAILURE, "ioctl(EVIOCGABS)"); + + setup.absmax[i] = info.maximum; + setup.absmin[i] = info.minimum; + setup.absfuzz[i] = info.fuzz; + setup.absflat[i] = info.flat; + } + + if (_ioctl(core->input, EVIOCGID, &setup.id) == -1) + err(EXIT_FAILURE, "ioctl(EVIOCGID)"); + + if (!uinputd_write(sizeof(setup), &setup)) + errx(EXIT_FAILURE, "write(uinput_user_dev)"); + + if (!uinputd_ioctl_other(IOCTL_NONE, UINPUT_IOCTL_BASE, 1, 0, NULL)) + errx(EXIT_FAILURE, "ioctl(UI_DEV_CREATE)"); +} + +static void +core_on_exit(const int signal, void *core) +{ + assert(core); + warnx("core_on_exit (%d)", signal); + core_release(core); +} + +static void +info(void) +{ + const char *remote = getenv("TCPREMOTEIP"); + warnx("version %s", UINPUTD_VERSION); + warnx("remote ip: %s", (remote ? remote : "none")); + warnx("sizeof(struct packet) is %zu bytes", sizeof(struct packet)); + warnx("sizeof(struct input_event) is %zu bytes", sizeof(struct input_event)); + warnx("sizeof(struct uinput_user_dev) is %zu bytes", sizeof(struct uinput_user_dev)); +} + +static void +sigterm(const int signal) +{ + warnx("%s", (signal == SIGTERM ? "SIGTERM" : "SIGINT")); + exit(EXIT_SUCCESS); +} + +int +main(int argc, const char *argv[]) +{ + if (argc < 2) + errx(EXIT_FAILURE, "usage: %s input-device [write-fd]", argv[0]); + + { + const struct sigaction action = { + .sa_handler = sigterm, + }; + + if (sigaction(SIGTERM, &action, NULL) != 0 || sigaction(SIGINT, &action, NULL) != 0) + err(EXIT_FAILURE, "sigaction"); + } + + if (argc > 2) + dup2(strtol(argv[2], NULL, 10), STDOUT_FILENO); + + setvbuf(stdout, NULL, _IONBF, 0); + + info(); + struct core core = {0}; + on_exit(core_on_exit, &core); + core_init(&core, argv[1]); + + { + struct input_event event; + while (read(core.input, &event, sizeof(event)) == sizeof(event)) + uinputd_write(sizeof(event), &event); + err(EXIT_FAILURE, "read"); + } + + exit(EXIT_SUCCESS); + return EXIT_SUCCESS; +} |