summaryrefslogtreecommitdiff
path: root/clients/linux/linux-uinput.c
diff options
context:
space:
mode:
Diffstat (limited to 'clients/linux/linux-uinput.c')
-rw-r--r--clients/linux/linux-uinput.c266
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;
+}