From 0699cbd82b577a4358d199a627e9bc4497f436e7 Mon Sep 17 00:00:00 2001 From: Jari Vetoniemi Date: Wed, 4 Jul 2018 20:25:49 +0300 Subject: initial commit --- clients/linux/Makefile | 11 + clients/linux/linux-uinput.c | 266 +++++++++++++++++ clients/vita/Makefile | 29 ++ clients/vita/screen.c | 285 +++++++++++++++++++ clients/vita/screen.h | 24 ++ clients/vita/vita-uinput.c | 660 +++++++++++++++++++++++++++++++++++++++++++ common/packet.h | 79 ++++++ server/Makefile | 11 + server/uinputd.c | 203 +++++++++++++ 9 files changed, 1568 insertions(+) create mode 100644 clients/linux/Makefile create mode 100644 clients/linux/linux-uinput.c create mode 100644 clients/vita/Makefile create mode 100644 clients/vita/screen.c create mode 100644 clients/vita/screen.h create mode 100644 clients/vita/vita-uinput.c create mode 100644 common/packet.h create mode 100644 server/Makefile create mode 100644 server/uinputd.c diff --git a/clients/linux/Makefile b/clients/linux/Makefile new file mode 100644 index 0000000..c3eff8f --- /dev/null +++ b/clients/linux/Makefile @@ -0,0 +1,11 @@ +version := $(shell git describe --abbrev=0 --tags --match "[0-9]*\.[0-9]*") +override CFLAGS += -std=c11 +override CPPFLAGS += -D_DEFAULT_SOURCE -DUINPUTD_VERSION='"$(version)"' -I../../ + +all: linux-uinput +linux-uinput: linux-uinput.c + +clean: + $(RM) linux-uinput + +.PHONY: clean 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; +} diff --git a/clients/vita/Makefile b/clients/vita/Makefile new file mode 100644 index 0000000..7d64908 --- /dev/null +++ b/clients/vita/Makefile @@ -0,0 +1,29 @@ +title_id := VITAUINPT +version := $(shell git describe --abbrev=0 --tags --match "[0-9]*\.[0-9]*") +override CFLAGS += -std=c11 +override CPPFLAGS += -D_DEFAULT_SOURCE -DVITA_UINPUT_VERSION='"$(version)"' -I../../ +override LDLIBS += -lSceDisplay_stub -lSceCtrl_stub -lSceTouch_stub -lSceSysmodule_stub -lSceNetCtl_stub + +all: vita-uinput.vpk + +vita-uinput: vita-uinput.c screen.c + $(LINK.c) $^ $(LDLIBS) -o $@ + +%.velf: % + strip -g $< + vita-elf-create $< $@ + +%.bin: %.velf + vita-make-fself -s $< $@ + +%.sfo: + vita-mksfoex -s TITLE_ID=$(title_id) $(patsubst %.sfo,%,$@) $@ + +%.vpk: %.bin %.sfo + vita-pack-vpk -b $(word 1,$^) -s $(word 2,$^) $@ + +clean: + $(RM) vita-uinput + $(RM) *.vpk + +.PHONY: clean diff --git a/clients/vita/screen.c b/clients/vita/screen.c new file mode 100644 index 0000000..504aadc --- /dev/null +++ b/clients/vita/screen.c @@ -0,0 +1,285 @@ +#include "screen.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#define SCREEN_WIDTH (960) +#define SCREEN_HEIGHT (544) +#define SCREEN_FB_WIDTH (960) +#define SCREEN_FB_SIZE (2 * 1024 * 1024) +#define SCREEN_GLYPH_W (8) +#define SCREEN_GLYPH_H (8) + +static inline uint8_t* debug_font(void); + +static struct screen { + SceDisplayFrameBuf old_fb, fb; + SceUID memory, mutex; + uint32_t x, y; + uint32_t fg, bg; +} screen; + +void +screen_deinit(void) +{ + if (!screen.mutex) + return; + + // If something goes wrong we really have no way to inform about it anymore, + // Thus no point checking errors here. + + sceKernelLockMutex(screen.mutex, 1, NULL); + sceDisplaySetFrameBuf(&screen.old_fb, SCE_DISPLAY_SETBUF_NEXTFRAME); + + if (screen.memory) + sceKernelFreeMemBlock(screen.memory); + + sceKernelUnlockMutex(screen.mutex, 1); + sceKernelDeleteMutex(screen.mutex); + screen = (struct screen){0}; +} + +bool +screen_init(void) +{ + if (screen.mutex) + return true; + + screen = (struct screen){ + .old_fb = { .size = sizeof(SceDisplayFrameBuf) }, + .fb = { + .size = sizeof(SceDisplayFrameBuf), + .pitch = SCREEN_WIDTH, + .pixelformat = SCE_DISPLAY_PIXELFORMAT_A8B8G8R8, + .width = SCREEN_WIDTH, + .height = SCREEN_HEIGHT + }, + .fg = 0xFFFFFFFF, + .bg = 0xFF000000, + }; + + sceDisplayGetFrameBuf(&screen.old_fb, SCE_DISPLAY_SETBUF_NEXTFRAME); + + if ((screen.mutex = sceKernelCreateMutex("screen.mutex", 0, 0, NULL)) < 0) + goto fail; + + if ((screen.memory = sceKernelAllocMemBlock("display", SCE_KERNEL_MEMBLOCK_TYPE_USER_CDRAM_RW, SCREEN_FB_SIZE, NULL)) < 0) + goto fail; + + sceKernelGetMemBlockBase(screen.memory, (void**)&screen.fb.base); + return (sceDisplaySetFrameBuf(&screen.fb, SCE_DISPLAY_SETBUF_NEXTFRAME) == 0); + +fail: + screen_deinit(); + return false; +} + +void +screen_clear(void) +{ + if (!screen.mutex) + return; + + sceKernelLockMutex(screen.mutex, 1, NULL); + + screen.x = screen.y = 0; + for(uint32_t i = 0; i < SCREEN_WIDTH * SCREEN_HEIGHT; ++i) + ((uint32_t*)screen.fb.base)[i] = screen.bg; + + sceKernelUnlockMutex(screen.mutex, 1); +} + +int +screen_puts(const char *msg) +{ + if (!screen.mutex) + return 0; + + sceKernelLockMutex(screen.mutex, 1, NULL); + + int c; + for (c = 0; c < INT_MAX && msg[c]; ++c) { + if (screen.x + 8 > SCREEN_WIDTH) { + screen.y += SCREEN_GLYPH_H; + screen.x = 0; + } + + if (screen.y + 8 > SCREEN_HEIGHT) + screen_clear(); + + if (msg[c] == '\n') { + screen.x = 0; + screen.y += SCREEN_GLYPH_H; + continue; + } else if (msg[c] == '\r') { + screen.x = 0; + continue; + } + + const uint8_t *font = &debug_font()[(uint32_t)msg[c] * 8]; + uint32_t *vram = ((uint32_t*)screen.fb.base) + screen.x + screen.y * SCREEN_FB_WIDTH; + for (uint32_t i = 0, l = 0; i < SCREEN_GLYPH_W; ++i, l += SCREEN_GLYPH_W, ++font) { + for (uint32_t j = 0, *vram_ptr = vram; j < SCREEN_GLYPH_W; ++j) { + *vram_ptr = ((*font & (128 >> j)) ? screen.fg : screen.bg); + vram_ptr++; + } + vram += SCREEN_FB_WIDTH; + } + screen.x += SCREEN_GLYPH_W; + } + + sceKernelUnlockMutex(screen.mutex, 1); + return c; +} + +int +screen_printf(const char *fmt, ...) +{ + // XXX: Could be TLS realloc'd buffer + // But it's very unlikely any printf exceeds the 512 characters + char buf[512]; + va_list args; + va_start(args, fmt); + const int ret = vsnprintf(buf, sizeof(buf), fmt, args); + screen_puts(buf); + va_end(args); + return ret; +} + +static inline uint8_t* +debug_font(void) { + return (uint8_t*) +"\x00\x00\x00\x00\x00\x00\x00\x00\x3c\x42\xa5\x81\xa5\x99\x42\x3c" +"\x3c\x7e\xdb\xff\xff\xdb\x66\x3c\x6c\xfe\xfe\xfe\x7c\x38\x10\x00" +"\x10\x38\x7c\xfe\x7c\x38\x10\x00\x10\x38\x54\xfe\x54\x10\x38\x00" +"\x10\x38\x7c\xfe\xfe\x10\x38\x00\x00\x00\x00\x30\x30\x00\x00\x00" +"\xff\xff\xff\xe7\xe7\xff\xff\xff\x38\x44\x82\x82\x82\x44\x38\x00" +"\xc7\xbb\x7d\x7d\x7d\xbb\xc7\xff\x0f\x03\x05\x79\x88\x88\x88\x70" +"\x38\x44\x44\x44\x38\x10\x7c\x10\x30\x28\x24\x24\x28\x20\xe0\xc0" +"\x3c\x24\x3c\x24\x24\xe4\xdc\x18\x10\x54\x38\xee\x38\x54\x10\x00" +"\x10\x10\x10\x7c\x10\x10\x10\x10\x10\x10\x10\xff\x00\x00\x00\x00" +"\x00\x00\x00\xff\x10\x10\x10\x10\x10\x10\x10\xf0\x10\x10\x10\x10" +"\x10\x10\x10\x1f\x10\x10\x10\x10\x10\x10\x10\xff\x10\x10\x10\x10" +"\x10\x10\x10\x10\x10\x10\x10\x10\x00\x00\x00\xff\x00\x00\x00\x00" +"\x00\x00\x00\x1f\x10\x10\x10\x10\x00\x00\x00\xf0\x10\x10\x10\x10" +"\x10\x10\x10\x1f\x00\x00\x00\x00\x10\x10\x10\xf0\x00\x00\x00\x00" +"\x81\x42\x24\x18\x18\x24\x42\x81\x01\x02\x04\x08\x10\x20\x40\x80" +"\x80\x40\x20\x10\x08\x04\x02\x01\x00\x10\x10\xff\x10\x10\x00\x00" +"\x00\x00\x00\x00\x00\x00\x00\x00\x20\x20\x20\x20\x00\x00\x20\x00" +"\x50\x50\x50\x00\x00\x00\x00\x00\x50\x50\xf8\x50\xf8\x50\x50\x00" +"\x20\x78\xa0\x70\x28\xf0\x20\x00\xc0\xc8\x10\x20\x40\x98\x18\x00" +"\x40\xa0\x40\xa8\x90\x98\x60\x00\x10\x20\x40\x00\x00\x00\x00\x00" +"\x10\x20\x40\x40\x40\x20\x10\x00\x40\x20\x10\x10\x10\x20\x40\x00" +"\x20\xa8\x70\x20\x70\xa8\x20\x00\x00\x20\x20\xf8\x20\x20\x00\x00" +"\x00\x00\x00\x00\x00\x20\x20\x40\x00\x00\x00\x78\x00\x00\x00\x00" +"\x00\x00\x00\x00\x00\x60\x60\x00\x00\x00\x08\x10\x20\x40\x80\x00" +"\x70\x88\x98\xa8\xc8\x88\x70\x00\x20\x60\xa0\x20\x20\x20\xf8\x00" +"\x70\x88\x08\x10\x60\x80\xf8\x00\x70\x88\x08\x30\x08\x88\x70\x00" +"\x10\x30\x50\x90\xf8\x10\x10\x00\xf8\x80\xe0\x10\x08\x10\xe0\x00" +"\x30\x40\x80\xf0\x88\x88\x70\x00\xf8\x88\x10\x20\x20\x20\x20\x00" +"\x70\x88\x88\x70\x88\x88\x70\x00\x70\x88\x88\x78\x08\x10\x60\x00" +"\x00\x00\x20\x00\x00\x20\x00\x00\x00\x00\x20\x00\x00\x20\x20\x40" +"\x18\x30\x60\xc0\x60\x30\x18\x00\x00\x00\xf8\x00\xf8\x00\x00\x00" +"\xc0\x60\x30\x18\x30\x60\xc0\x00\x70\x88\x08\x10\x20\x00\x20\x00" +"\x70\x88\x08\x68\xa8\xa8\x70\x00\x20\x50\x88\x88\xf8\x88\x88\x00" +"\xf0\x48\x48\x70\x48\x48\xf0\x00\x30\x48\x80\x80\x80\x48\x30\x00" +"\xe0\x50\x48\x48\x48\x50\xe0\x00\xf8\x80\x80\xf0\x80\x80\xf8\x00" +"\xf8\x80\x80\xf0\x80\x80\x80\x00\x70\x88\x80\xb8\x88\x88\x70\x00" +"\x88\x88\x88\xf8\x88\x88\x88\x00\x70\x20\x20\x20\x20\x20\x70\x00" +"\x38\x10\x10\x10\x90\x90\x60\x00\x88\x90\xa0\xc0\xa0\x90\x88\x00" +"\x80\x80\x80\x80\x80\x80\xf8\x00\x88\xd8\xa8\xa8\x88\x88\x88\x00" +"\x88\xc8\xc8\xa8\x98\x98\x88\x00\x70\x88\x88\x88\x88\x88\x70\x00" +"\xf0\x88\x88\xf0\x80\x80\x80\x00\x70\x88\x88\x88\xa8\x90\x68\x00" +"\xf0\x88\x88\xf0\xa0\x90\x88\x00\x70\x88\x80\x70\x08\x88\x70\x00" +"\xf8\x20\x20\x20\x20\x20\x20\x00\x88\x88\x88\x88\x88\x88\x70\x00" +"\x88\x88\x88\x88\x50\x50\x20\x00\x88\x88\x88\xa8\xa8\xd8\x88\x00" +"\x88\x88\x50\x20\x50\x88\x88\x00\x88\x88\x88\x70\x20\x20\x20\x00" +"\xf8\x08\x10\x20\x40\x80\xf8\x00\x70\x40\x40\x40\x40\x40\x70\x00" +"\x00\x00\x80\x40\x20\x10\x08\x00\x70\x10\x10\x10\x10\x10\x70\x00" +"\x20\x50\x88\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf8\x00" +"\x40\x20\x10\x00\x00\x00\x00\x00\x00\x00\x70\x08\x78\x88\x78\x00" +"\x80\x80\xb0\xc8\x88\xc8\xb0\x00\x00\x00\x70\x88\x80\x88\x70\x00" +"\x08\x08\x68\x98\x88\x98\x68\x00\x00\x00\x70\x88\xf8\x80\x70\x00" +"\x10\x28\x20\xf8\x20\x20\x20\x00\x00\x00\x68\x98\x98\x68\x08\x70" +"\x80\x80\xf0\x88\x88\x88\x88\x00\x20\x00\x60\x20\x20\x20\x70\x00" +"\x10\x00\x30\x10\x10\x10\x90\x60\x40\x40\x48\x50\x60\x50\x48\x00" +"\x60\x20\x20\x20\x20\x20\x70\x00\x00\x00\xd0\xa8\xa8\xa8\xa8\x00" +"\x00\x00\xb0\xc8\x88\x88\x88\x00\x00\x00\x70\x88\x88\x88\x70\x00" +"\x00\x00\xb0\xc8\xc8\xb0\x80\x80\x00\x00\x68\x98\x98\x68\x08\x08" +"\x00\x00\xb0\xc8\x80\x80\x80\x00\x00\x00\x78\x80\xf0\x08\xf0\x00" +"\x40\x40\xf0\x40\x40\x48\x30\x00\x00\x00\x90\x90\x90\x90\x68\x00" +"\x00\x00\x88\x88\x88\x50\x20\x00\x00\x00\x88\xa8\xa8\xa8\x50\x00" +"\x00\x00\x88\x50\x20\x50\x88\x00\x00\x00\x88\x88\x98\x68\x08\x70" +"\x00\x00\xf8\x10\x20\x40\xf8\x00\x18\x20\x20\x40\x20\x20\x18\x00" +"\x20\x20\x20\x00\x20\x20\x20\x00\xc0\x20\x20\x10\x20\x20\xc0\x00" +"\x40\xa8\x10\x00\x00\x00\x00\x00\x00\x00\x20\x50\xf8\x00\x00\x00" +"\x70\x88\x80\x80\x88\x70\x20\x60\x90\x00\x00\x90\x90\x90\x68\x00" +"\x10\x20\x70\x88\xf8\x80\x70\x00\x20\x50\x70\x08\x78\x88\x78\x00" +"\x48\x00\x70\x08\x78\x88\x78\x00\x20\x10\x70\x08\x78\x88\x78\x00" +"\x20\x00\x70\x08\x78\x88\x78\x00\x00\x70\x80\x80\x80\x70\x10\x60" +"\x20\x50\x70\x88\xf8\x80\x70\x00\x50\x00\x70\x88\xf8\x80\x70\x00" +"\x20\x10\x70\x88\xf8\x80\x70\x00\x50\x00\x00\x60\x20\x20\x70\x00" +"\x20\x50\x00\x60\x20\x20\x70\x00\x40\x20\x00\x60\x20\x20\x70\x00" +"\x50\x00\x20\x50\x88\xf8\x88\x00\x20\x00\x20\x50\x88\xf8\x88\x00" +"\x10\x20\xf8\x80\xf0\x80\xf8\x00\x00\x00\x6c\x12\x7e\x90\x6e\x00" +"\x3e\x50\x90\x9c\xf0\x90\x9e\x00\x60\x90\x00\x60\x90\x90\x60\x00" +"\x90\x00\x00\x60\x90\x90\x60\x00\x40\x20\x00\x60\x90\x90\x60\x00" +"\x40\xa0\x00\xa0\xa0\xa0\x50\x00\x40\x20\x00\xa0\xa0\xa0\x50\x00" +"\x90\x00\x90\x90\xb0\x50\x10\xe0\x50\x00\x70\x88\x88\x88\x70\x00" +"\x50\x00\x88\x88\x88\x88\x70\x00\x20\x20\x78\x80\x80\x78\x20\x20" +"\x18\x24\x20\xf8\x20\xe2\x5c\x00\x88\x50\x20\xf8\x20\xf8\x20\x00" +"\xc0\xa0\xa0\xc8\x9c\x88\x88\x8c\x18\x20\x20\xf8\x20\x20\x20\x40" +"\x10\x20\x70\x08\x78\x88\x78\x00\x10\x20\x00\x60\x20\x20\x70\x00" +"\x20\x40\x00\x60\x90\x90\x60\x00\x20\x40\x00\x90\x90\x90\x68\x00" +"\x50\xa0\x00\xa0\xd0\x90\x90\x00\x28\x50\x00\xc8\xa8\x98\x88\x00" +"\x00\x70\x08\x78\x88\x78\x00\xf8\x00\x60\x90\x90\x90\x60\x00\xf0" +"\x20\x00\x20\x40\x80\x88\x70\x00\x00\x00\x00\xf8\x80\x80\x00\x00" +"\x00\x00\x00\xf8\x08\x08\x00\x00\x84\x88\x90\xa8\x54\x84\x08\x1c" +"\x84\x88\x90\xa8\x58\xa8\x3c\x08\x20\x00\x00\x20\x20\x20\x20\x00" +"\x00\x00\x24\x48\x90\x48\x24\x00\x00\x00\x90\x48\x24\x48\x90\x00" +"\x28\x50\x20\x50\x88\xf8\x88\x00\x28\x50\x70\x08\x78\x88\x78\x00" +"\x28\x50\x00\x70\x20\x20\x70\x00\x28\x50\x00\x20\x20\x20\x70\x00" +"\x28\x50\x00\x70\x88\x88\x70\x00\x50\xa0\x00\x60\x90\x90\x60\x00" +"\x28\x50\x00\x88\x88\x88\x70\x00\x50\xa0\x00\xa0\xa0\xa0\x50\x00" +"\xfc\x48\x48\x48\xe8\x08\x50\x20\x00\x50\x00\x50\x50\x50\x10\x20" +"\xc0\x44\xc8\x54\xec\x54\x9e\x04\x10\xa8\x40\x00\x00\x00\x00\x00" +"\x00\x20\x50\x88\x50\x20\x00\x00\x88\x10\x20\x40\x80\x28\x00\x00" +"\x7c\xa8\xa8\x68\x28\x28\x28\x00\x38\x40\x30\x48\x48\x30\x08\x70" +"\x00\x00\x00\x00\x00\x00\xff\xff\xf0\xf0\xf0\xf0\x0f\x0f\x0f\x0f" +"\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00" +"\x00\x00\x00\x3c\x3c\x00\x00\x00\xff\xff\xff\xff\xff\xff\x00\x00" +"\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\x0f\x0f\x0f\x0f\xf0\xf0\xf0\xf0" +"\xfc\xfc\xfc\xfc\xfc\xfc\xfc\xfc\x03\x03\x03\x03\x03\x03\x03\x03" +"\x3f\x3f\x3f\x3f\x3f\x3f\x3f\x3f\x11\x22\x44\x88\x11\x22\x44\x88" +"\x88\x44\x22\x11\x88\x44\x22\x11\xfe\x7c\x38\x10\x00\x00\x00\x00" +"\x00\x00\x00\x00\x10\x38\x7c\xfe\x80\xc0\xe0\xf0\xe0\xc0\x80\x00" +"\x01\x03\x07\x0f\x07\x03\x01\x00\xff\x7e\x3c\x18\x18\x3c\x7e\xff" +"\x81\xc3\xe7\xff\xff\xe7\xc3\x81\xf0\xf0\xf0\xf0\x00\x00\x00\x00" +"\x00\x00\x00\x00\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x00\x00\x00\x00" +"\x00\x00\x00\x00\xf0\xf0\xf0\xf0\x33\x33\xcc\xcc\x33\x33\xcc\xcc" +"\x00\x20\x20\x50\x50\x88\xf8\x00\x20\x20\x70\x20\x70\x20\x20\x00" +"\x00\x00\x00\x50\x88\xa8\x50\x00\xff\xff\xff\xff\xff\xff\xff\xff" +"\x00\x00\x00\x00\xff\xff\xff\xff\xf0\xf0\xf0\xf0\xf0\xf0\xf0\xf0" +"\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\xff\xff\xff\xff\x00\x00\x00\x00" +"\x00\x00\x68\x90\x90\x90\x68\x00\x30\x48\x48\x70\x48\x48\x70\xc0" +"\xf8\x88\x80\x80\x80\x80\x80\x00\xf8\x50\x50\x50\x50\x50\x98\x00" +"\xf8\x88\x40\x20\x40\x88\xf8\x00\x00\x00\x78\x90\x90\x90\x60\x00" +"\x00\x50\x50\x50\x50\x68\x80\x80\x00\x50\xa0\x20\x20\x20\x20\x00" +"\xf8\x20\x70\xa8\xa8\x70\x20\xf8\x20\x50\x88\xf8\x88\x50\x20\x00" +"\x70\x88\x88\x88\x50\x50\xd8\x00\x30\x40\x40\x20\x50\x50\x50\x20" +"\x00\x00\x00\x50\xa8\xa8\x50\x00\x08\x70\xa8\xa8\xa8\x70\x80\x00" +"\x38\x40\x80\xf8\x80\x40\x38\x00\x70\x88\x88\x88\x88\x88\x88\x00" +"\x00\xf8\x00\xf8\x00\xf8\x00\x00\x20\x20\xf8\x20\x20\x00\xf8\x00" +"\xc0\x30\x08\x30\xc0\x00\xf8\x00\x18\x60\x80\x60\x18\x00\xf8\x00" +"\x10\x28\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\xa0\x40" +"\x00\x20\x00\xf8\x00\x20\x00\x00\x00\x50\xa0\x00\x50\xa0\x00\x00" +"\x00\x18\x24\x24\x18\x00\x00\x00\x00\x30\x78\x78\x30\x00\x00\x00" +"\x00\x00\x00\x00\x30\x00\x00\x00\x3e\x20\x20\x20\xa0\x60\x20\x00" +"\xa0\x50\x50\x50\x00\x00\x00\x00\x40\xa0\x20\x40\xe0\x00\x00\x00" +"\x00\x38\x38\x38\x38\x38\x38\x00\x00\x00\x00\x00\x00\x00\x00"; +} diff --git a/clients/vita/screen.h b/clients/vita/screen.h new file mode 100644 index 0000000..e1ec9d4 --- /dev/null +++ b/clients/vita/screen.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include + +void screen_deinit(void); +bool screen_init(void); +void screen_clear(void); +int screen_puts(const char *msg); + +__attribute__((format(printf, 1, 2))) +int screen_printf(const char *fmt, ...); + +#define printf screen_printf +#define errx(code, fmt, ...) { \ + screen_init(); \ + printf("ERROR @ %s:%lu "fmt"\n", __func__, (uint32_t)__LINE__, ##__VA_ARGS__); \ + while (true) { sceKernelDelayThread((SceUInt)~0); } \ +} + +#define warnx(fmt, ...) { printf("WARN @ %s:%lu "fmt"\n", __func__, (uint32_t)__LINE__, ##__VA_ARGS__); } +#define err(code, fmt, ...) { errx(code, fmt": %s", ##__VA_ARGS__, strerror(errno)); } +#define warn(fmt, ...) { warnx(code, fmt": %s", ##__VA_ARGS__, strerror(errno)); } +#define assert(x) { if (!(x)) { errx("assertion failed for %s", #x); } } diff --git a/clients/vita/vita-uinput.c b/clients/vita/vita-uinput.c new file mode 100644 index 0000000..3a9b2f8 --- /dev/null +++ b/clients/vita/vita-uinput.c @@ -0,0 +1,660 @@ +#include +#include +#include +#include + +#include "screen.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/packet.h" + +_Static_assert(SCE_TOUCH_SAMPLING_STATE_START == 1, "Stop using old VitaSDK"); + +#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0])) +#define BIT_TOGGLE(w, m, f) (w & ~m) | (-f & m) + +/** + * Neccessary uinput / evdev constants + */ + +#define UINPUT_IOCTL_BASE 'U' +#define EV_SYN 0x00 +#define EV_KEY 0x01 +#define EV_ABS 0x03 + +#define BTN_DPAD_UP 0x220 +#define BTN_DPAD_DOWN 0x221 +#define BTN_DPAD_LEFT 0x222 +#define BTN_DPAD_RIGHT 0x223 + +#define BTN_SOUTH 0x130 +#define BTN_EAST 0x131 +#define BTN_NORTH 0x133 +#define BTN_WEST 0x134 +#define BTN_TL 0x136 +#define BTN_TR 0x137 +#define BTN_TL2 0x138 +#define BTN_TR2 0x139 +#define BTN_SELECT 0x13a +#define BTN_START 0x13b +#define BTN_MODE 0x13c + +#define ABS_X 0x00 +#define ABS_Y 0x01 +#define ABS_RX 0x03 +#define ABS_RY 0x04 +#define ABS_HAT0X 0x10 +#define ABS_HAT0Y 0x11 +#define ABS_MT_POSITION_X 0x35 +#define ABS_MT_POSITION_Y 0x36 +#define ABS_MAX 0x3f +#define ABS_CNT (ABS_MAX+1) + +#define SYN_REPORT 0 + +#define BUS_VIRTUAL 0x06 + +struct input_id { + uint16_t bustype; + uint16_t vendor; + uint16_t product; + uint16_t version; +}; + +#define UINPUT_MAX_NAME_SIZE 80 + +struct uinput_user_dev { + char name[UINPUT_MAX_NAME_SIZE]; + struct input_id id; + uint32_t ff_effects_max; + int32_t absmax[ABS_CNT]; + int32_t absmin[ABS_CNT]; + int32_t absfuzz[ABS_CNT]; + int32_t absflat[ABS_CNT]; +}; + +/** + * We are now expecting 64bit host, see the compatibility notes from uinputd.c + */ +struct timeval { + uint64_t tv_sec, tv_usec; +}; + +struct input_event { + struct timeval time; + uint16_t type; + uint16_t code; + int32_t value; +}; + +/** + * We don't have way to poll for input being ready, so we have to resort for + * having delay to avoid huge power drain while vita-uinput is running. + */ +#define INPUT_LAG 20 // ms + +/** + * Expands the SCE button bitmaks. + * We emulate these buttons by using the 2 touch screens. + */ +enum { + SCE_CTRL_EXTRA_L2TRIGGER = 0x020000, + SCE_CTRL_EXTRA_R2TRIGGER = 0x040000, + SCE_CTRL_EXTRA_MENU = 0x080000, +}; + +static struct timeval +us_to_timeval(const uint64_t us) +{ + return (struct timeval){ + .tv_sec = us / (uint64_t)1e6, + .tv_usec = us % (uint64_t)1e6, + }; +} + +static SceUInt +ms_to_us(const SceUInt ms) +{ + return ms * 1000; +} + +static SceUInt +s_to_us(const SceUInt s) +{ + return ms_to_us(s * 1000); +} + +static void +setup(void) +{ + sceCtrlSetSamplingMode(SCE_CTRL_MODE_ANALOG); + sceTouchSetSamplingState(SCE_TOUCH_PORT_FRONT, SCE_TOUCH_SAMPLING_STATE_START); + sceTouchSetSamplingState(SCE_TOUCH_PORT_BACK, SCE_TOUCH_SAMPLING_STATE_START); + sceTouchEnableTouchForce(SCE_TOUCH_PORT_FRONT); + sceTouchEnableTouchForce(SCE_TOUCH_PORT_BACK); + + if (!screen_init()) + errx(EXIT_FAILURE, "screen_init"); + + if (sceSysmoduleLoadModule(SCE_SYSMODULE_NET) != 0) + errx(EXIT_FAILURE, "sceSysmoduleLoadModule"); + + if ((unsigned int)sceNetShowNetstat() == SCE_NET_ERROR_ENOTINIT) { + static uint8_t sce_net_memory[1024 * 1024]; + SceNetInitParam initparam = { + .memory = sce_net_memory, + .size = sizeof(sce_net_memory), + }; + + if (sceNetInit(&initparam) != 0) + errx(EXIT_FAILURE, "sceNetInit"); + } + + if (sceNetCtlInit() != 0) + errx(EXIT_FAILURE, "sceNetCtlInit"); +} + +static void +terminate(void) +{ + sceNetCtlTerm(); + sceNetTerm(); + sceSysmoduleUnloadModule(SCE_SYSMODULE_NET); + screen_deinit(); +} + +static void +disconnect(int fd) +{ + sceNetSocketClose(fd); +} + +static int +connect(const char *ip, SceNetCtlInfo *info) +{ + assert(ip && info); + + if (sceNetCtlInetGetInfo(SCE_NETCTL_INFO_GET_IP_ADDRESS, info) != 0) { + warnx("sceNetCtlInetGetInfo"); + return -1; + } + + SceNetSockaddrIn addr = { + .sin_family = SCE_NET_AF_INET, + .sin_port = sceNetHtons(5000), + }; + + if (!sceNetInetPton(SCE_NET_AF_INET, ip, &addr.sin_addr)) { + warnx("sceNetInetPton"); + return -1; + } + + int fd; + if ((fd = sceNetSocket("vita_uinput", SCE_NET_AF_INET, SCE_NET_SOCK_STREAM, 0)) < 0) { + warnx("sceNetSocket: %d", fd); + return fd; + } + + if (sceNetConnect(fd, (SceNetSockaddr*)&addr, sizeof(addr))) { + warnx("sceNetConnect: failed connecting to %s", ip); + disconnect(fd); + return -1; + } + + return fd; +} + +static bool +uinputd_ioctl_other(const int fd, 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 (sceNetSend(fd, &packet, sizeof(packet), 0) != sizeof(packet)) { + warnx("sceNetSend: failed to send %u bytes", sizeof(packet)); + return false; + } + + if (bytes > 0 && sceNetSend(fd, arg, bytes, 0) != bytes) { + warnx("sceNetSend: failed to send %u bytes", bytes); + return false; + } + + return true; +} + +static bool +uinputd_ioctl_int(const int fd, 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 (sceNetSend(fd, &packet, sizeof(packet), 0) != sizeof(packet)) { + warnx("sceNetSend: failed to send %u bytes", sizeof(packet)); + return false; + } + + return true; +} + +static bool +uinputd_write(const int fd, const int32_t bytes, const void *data) +{ + if (bytes <= 0 || !data) + return true; + + const struct packet packet = { + .type = PACKET_WRITE, + .write.bytes = bytes, + }; + + if (sceNetSend(fd, &packet, sizeof(packet), 0) != sizeof(packet)) { + warnx("sceNetSend: failed to send %u bytes", sizeof(packet)); + return false; + } + + if (sceNetSend(fd, data, bytes, 0) != bytes) { + warnx("sceNetSend: failed to send %ld bytes", bytes); + return false; + } + + return true; +} + +static bool +process_touch(const SceTouchData *new, SceTouchData *old, SceCtrlData *ctrl, const bool back_touchpad) +{ + // Exposing vita touchpads as multitouch wasn't particularly useful.. + // Thus lets give them more useful function. + // Back touch will be mapped to L2/L2 and front touch to menu button. + + // Button emulation will only be done on new touch. + // e.g. Swiping back touchpad from left to right, will only trigger L2 or R2 from the starting touch point. + if (old->reportNum > 0) + goto end; + + for (size_t i = 0; i < new->reportNum; ++i) { + if (back_touchpad) { + // Touch x resolution is resx * 2, thus 960 is half + // Touch y resolution is resy * 2, thus 544 is half + // We add some padding for the touch area to avoid accidental touches. + if (new->report[i].y > 362 && new->report[i].y < 726) { + if (new->report[i].x < 960 && new->report[i].x > 480) { + ctrl->buttons |= SCE_CTRL_EXTRA_L2TRIGGER; + } else if (new->report[i].x > 960 && new->report[i].x < 1440) { + ctrl->buttons |= SCE_CTRL_EXTRA_R2TRIGGER; + } + } + } else { + ctrl->buttons |= SCE_CTRL_EXTRA_MENU; + } + } + +end: + *old = *new; + return true; +} + +static void +ctrl_query_changed_analogs(const SceCtrlData *new, const SceCtrlData *old, uint32_t out_changed[4]) +{ + assert(new && old); + + for (size_t i = 0; i < 4; ++i) + out_changed[i] = (uint32_t)~0; + + const uint8_t fuzz = 2; + const uint32_t code[4] = { ABS_X, ABS_Y, ABS_RX, ABS_RY }; + const uint8_t new_analogs[4] = { new->lx, new->ly, new->rx, new->ry }; + const uint8_t old_analogs[4] = { old->lx, old->ly, old->rx, old->ry }; + for (size_t i = 0, c = 0; i < ARRAY_SIZE(new_analogs); ++i) { + if (old_analogs[i] - fuzz <= new_analogs[i] && old_analogs[i] + fuzz >= new_analogs[i]) + continue; + + assert(c < ARRAY_SIZE(code)); + out_changed[c++] = code[i]; + } +} + +static bool +process_ctrl(const int fd, const SceCtrlData *new, SceCtrlData *old, bool *out_changed) +{ + assert(new && old && out_changed); + + { + // https://www.kernel.org/doc/Documentation/input/gamepad.txt + // Digital -> Analog emulation + struct { + uint32_t code, value; + bool changed; + } abs[] = { + { ABS_HAT0X, 127, false }, + { ABS_HAT0Y, 127, false }, + }; + + const struct { + uint32_t bit, abs, value; + } map[] = { + { SCE_CTRL_UP, 1, -127 }, + { SCE_CTRL_DOWN, 1, 128 }, + { SCE_CTRL_LEFT, 0, -127 }, + { SCE_CTRL_RIGHT, 0, 128}, + }; + + for (size_t i = 0; i < ARRAY_SIZE(map); ++i) { + if ((new->buttons & map[i].bit) == (old->buttons & map[i].bit)) + continue; + + abs[map[i].abs].changed = true; + abs[map[i].abs].value += (new->buttons & map[i].bit ? map[i].value : 0); + } + + for (size_t i = 0; i < ARRAY_SIZE(abs); ++i) { + if (!abs[i].changed) + continue; + + const struct input_event ev = { + .time = us_to_timeval(new->timeStamp), + .type = EV_ABS, + .code = abs[i].code, + .value = abs[i].value, + }; + + if (!uinputd_write(fd, sizeof(ev), &ev)) { + warnx("write: failed to write %u bytes", sizeof(ev)); + return false; + } + } + } + + { + // https://www.kernel.org/doc/Documentation/input/gamepad.txt + const struct { + uint32_t bit, code; + } map[] = { + { SCE_CTRL_TRIANGLE, BTN_NORTH }, + { SCE_CTRL_CROSS, BTN_SOUTH }, + { SCE_CTRL_SQUARE, BTN_WEST }, + { SCE_CTRL_CIRCLE, BTN_EAST }, + { SCE_CTRL_START, BTN_START }, + { SCE_CTRL_SELECT, BTN_SELECT }, + { SCE_CTRL_UP, BTN_DPAD_UP }, + { SCE_CTRL_DOWN, BTN_DPAD_DOWN }, + { SCE_CTRL_LEFT, BTN_DPAD_LEFT }, + { SCE_CTRL_RIGHT, BTN_DPAD_RIGHT }, + { SCE_CTRL_LTRIGGER, BTN_TL }, + { SCE_CTRL_RTRIGGER, BTN_TR }, + { SCE_CTRL_EXTRA_L2TRIGGER, BTN_TL2 }, + { SCE_CTRL_EXTRA_R2TRIGGER, BTN_TR2 }, + { SCE_CTRL_EXTRA_MENU, BTN_MODE }, + }; + + for (size_t i = 0; i < ARRAY_SIZE(map); ++i) { + if ((new->buttons & map[i].bit) == (old->buttons & map[i].bit)) + continue; + + const struct input_event ev = { + .time = us_to_timeval(new->timeStamp), + .type = EV_KEY, + .code = map[i].code, + .value = !!(new->buttons & map[i].bit), + }; + + if (!uinputd_write(fd, sizeof(ev), &ev)) { + warnx("write: failed to write %u bytes", sizeof(ev)); + return false; + } + + *out_changed = true; + old->buttons = BIT_TOGGLE(old->buttons, map[i].bit, (new->buttons & map[i].bit)); + } + } + + { + // https://www.kernel.org/doc/Documentation/input/gamepad.txt + const uint8_t values[] = { + new->lx, // ABS_X + new->ly, // ABS_Y + 0, // ABS_Z + new->rx, // ABS_RX + new->ry, // ABS_RY + }; + + uint8_t dummy; + uint8_t* old_values_ref[] = { + &old->lx, // ABS_X + &old->ly, // ABS_Y + &dummy, // ABS_Z + &old->rx, // ABS_RX + &old->ry, // ABS_RY + }; + + uint32_t changed[4]; + ctrl_query_changed_analogs(new, old, changed); + for (size_t i = 0; i < ARRAY_SIZE(changed) && changed[i] != (uint32_t)~0; ++i) { + assert(changed[i] < ARRAY_SIZE(values)); + + const struct input_event ev = { + .time = us_to_timeval(new->timeStamp), + .type = EV_ABS, + .code = changed[i], + .value = values[changed[i]], + }; + + if (!uinputd_write(fd, sizeof(ev), &ev)) { + warnx("write: failed to write %u bytes", sizeof(ev)); + return false; + } + + *out_changed = true; + *old_values_ref[changed[i]] = values[changed[i]]; + } + } + + return true; +} + +static bool +process_syn(const int fd) +{ + const struct input_event ev = { + .type = EV_SYN, + .code = SYN_REPORT, + }; + + if (!uinputd_write(fd, sizeof(ev), &ev)) { + warnx("write: failed to write %u bytes", sizeof(ev)); + return false; + } + + return true; +} + +static bool +setup_uinput(const int fd) +{ + if (!uinputd_ioctl_int(fd, IOCTL_IOW, UINPUT_IOCTL_BASE, 100, EV_KEY)) { + warnx("ioctl(UI_SET_EVBIT): EV_KEY"); + return false; + } + + if (!uinputd_ioctl_int(fd, IOCTL_IOW, UINPUT_IOCTL_BASE, 100, EV_ABS)) { + warnx("ioctl(UI_SET_EVBIT): EV_ABS"); + return false; + } + + if (!uinputd_ioctl_int(fd, IOCTL_IOW, UINPUT_IOCTL_BASE, 100, EV_SYN)) { + warnx("ioctl(UI_SET_EVBIT): EV_SYN"); + return false; + } + + { + const int keys[] = { + BTN_NORTH, + BTN_SOUTH, + BTN_WEST, + BTN_EAST, + BTN_START, + BTN_SELECT, + BTN_DPAD_UP, + BTN_DPAD_DOWN, + BTN_DPAD_LEFT, + BTN_DPAD_RIGHT, + BTN_TL, + BTN_TR, + BTN_TL2, + BTN_TR2, + BTN_MODE + }; + + for (size_t i = 0; i < ARRAY_SIZE(keys); ++i) { + if (!uinputd_ioctl_int(fd, IOCTL_IOW, UINPUT_IOCTL_BASE, 101, keys[i])) { + warnx("ioctl (UI_SET_KEYBIT:%d)", keys[i]); + return false; + } + } + } + + { + const int abs[] = { + ABS_X, + ABS_Y, + ABS_RX, + ABS_RY, + ABS_HAT0X, + ABS_HAT0Y, + ABS_MT_POSITION_X, // To disable Xorg from handling the analogs as mouse + ABS_MT_POSITION_Y, // To disable Xorg from handling the analogs as mouse + }; + + for (size_t i = 0; i < ARRAY_SIZE(abs); ++i) { + if (!uinputd_ioctl_int(fd, IOCTL_IOW, UINPUT_IOCTL_BASE, 103, abs[i])) { + warnx("ioctl (UI_SET_ABSBIT:%d)", abs[i]); + return false; + } + } + } + + { + struct uinput_user_dev setup = { + .name = "PS Vita", + .id = { + // http://www.vitadevwiki.com/index.php?title=USB + .bustype = BUS_VIRTUAL, + .vendor = 0x054C, // Sony Corp. + .product = 0x04E4, // PS Vita + .version = 0x0100, + }, + .absmax = { + [ABS_X] = 255, + [ABS_Y] = 255, + [ABS_RX] = 255, + [ABS_RY] = 255, + [ABS_HAT0X] = 255, + [ABS_HAT0Y] = 255, + }, + }; + + if (!uinputd_write(fd, sizeof(setup), &setup)) { + warnx("write: failed to write %u bytes", sizeof(setup)); + return false; + } + + if (!uinputd_ioctl_other(fd, IOCTL_NONE, UINPUT_IOCTL_BASE, 1, 0, NULL)) { + warnx("ioctl (UI_DEV_CREATE)"); + return false; + } + } + + return true; +} + +static void +run(const int fd) +{ + struct { + SceCtrlData ctrl; + SceTouchData touch[2]; + } old = {0}, new = {0}; + + bool error = !setup_uinput(fd); + while (!error) { + sceKernelPowerTick(0); + sceKernelDelayThread(ms_to_us(INPUT_LAG)); + + // NOTE: These don't actually block, thus ^ sleep above. + // Sucks a bit for power usage and latency. + sceTouchRead(SCE_TOUCH_PORT_FRONT, &new.touch[0], 1); + sceTouchRead(SCE_TOUCH_PORT_BACK, &new.touch[1], 1); + sceCtrlReadBufferPositive(0, &new.ctrl, 1); + + bool changed = false; + + for (size_t i = 0; i < ARRAY_SIZE(new.touch); ++i) { + // Doesn't actually send or "change" anything. + // Just simulates button presses that will be appended to new.ctrl.buttons + // Actual change checking and sending is done in process_ctrl. + if (!process_touch(&new.touch[i], &old.touch[i], &new.ctrl, (i == 1))) + break; + } + + if (!process_ctrl(fd, &new.ctrl, &old.ctrl, &changed)) + break; + + if (changed && !process_syn(fd)) + break; + } +} + +int +main(void) +{ + setup(); + atexit(terminate); + + // TODO: Needs way to input ip, if this ever goes out + + while (true) { + int fd; + SceNetCtlInfo info; + const char *ip = "192.168.1.2"; + if ((fd = connect(ip, &info)) >= 0) { + printf("vita-uinput %s\n", VITA_UINPUT_VERSION); + printf("Vita IP: %s\n", info.ip_address); + printf("Server IP: %s\n", ip); + printf("sizeof(struct packet) is %u bytes\n", sizeof(struct packet)); + printf("sizeof(struct input_event) is %u bytes\n", sizeof(struct input_event)); + printf("sizeof(struct uinput_user_dev) is %u bytes\n", sizeof(struct uinput_user_dev)); + run(fd); + } + + disconnect(fd); + + warnx("Reconnecting after 5 seconds"); + sceKernelDelayThread(s_to_us(5)); + screen_clear(); + } + + exit(EXIT_SUCCESS); + return EXIT_SUCCESS; +} diff --git a/common/packet.h b/common/packet.h new file mode 100644 index 0000000..35c57e1 --- /dev/null +++ b/common/packet.h @@ -0,0 +1,79 @@ +#pragma once + +enum packet_type { + PACKET_IOCTL, + PACKET_WRITE, +}; + +enum ioctl_dir { + IOCTL_NONE, + IOCTL_IOR, + IOCTL_IOW, + IOCTL_IOWR, +}; + +enum ioctl_arg { + IOCTL_ARG_INT, + IOCTL_ARG_OTHER, // All bets off, can't assure portability +}; + +/** + * Lackluster "network transparent" ioctl call + * Implements enough to have remote uinput interface. + */ +struct ioctl { + /** + * Because the 32bit encoding of ioctl message is not stable ABI interface between CPU architectures, + * we'll write the raw _IOC arguments on the wire and reconstruct the 32bit encoding on uinputd. + */ + uint8_t dir; // enum ioctl_dir, we can't use bitmask directly, as it's not portable + uint8_t type; // type of ioctl, e.g. UINPUT_IOCTL_BASE + uint8_t nr; // command code (nr), e.g. 100 of UINPUT_IOCTL_BASE which means UI_SET_EVBIT + + /** + * Ioctl ABI also includes argument size part, which is mainly used for typechecking. + * However since the arguments are not exact size types, but again CPU architecture dependant sizes, and even + * compiler dependant for structs (even though they use __u32 and friends, the padding may differ), we can't + * really do generic network abstraction. + * + * To avoid creating wrapper/shim over uinput/evdev (or for other linux interfaces if ever needed to be expanded), we + * use another type enum for ioctl argument type, for structs all bets are off, they may work or not work at all. + * (e.g. the ioctl will fail since it won't pass typecheck due to different sized of structs in between client <-> server) + */ + + uint8_t arg; // enum ioctl_arg, ^ read above + + union { + uint32_t integer; + uint16_t bytes; // max ioctl parameter size can be 16kB -1 + }; + + // payload, for IOCTL_ARG_OTHER, read the amount of bytes indicated by 'bytes' +}; + +/** + * Network transparent write call + * + * The call itself is portable, the underlying data may not be. + * E.g. structs used by evdev aren't packed, and some of them contain non explicit types such as POSIX struct timeval. + */ +struct write { + uint64_t bytes; // amount of bytes to be written + + // payload, read the amount of bytes indicated by 'bytes' +}; + +/** + * Binary specification for uinputd messages. + * All fields must be in little-endian. + * + * All packet operations are applied into the open uinput fd. + */ +struct packet { + uint8_t type; // enum packet_type + + union { + struct ioctl ioctl; + struct write write; + }; +} __attribute__((packed)) __attribute__((scalar_storage_order("little-endian"))); diff --git a/server/Makefile b/server/Makefile new file mode 100644 index 0000000..09c37b9 --- /dev/null +++ b/server/Makefile @@ -0,0 +1,11 @@ +version := $(shell git describe --abbrev=0 --tags --match "[0-9]*\.[0-9]*") +override CFLAGS += -std=c11 +override CPPFLAGS += -D_DEFAULT_SOURCE -DUINPUTD_VERSION='"$(version)"' -I../ + +all: uinputd +uinputd: uinputd.c + +clean: + $(RM) uinputd + +.PHONY: clean diff --git a/server/uinputd.c b/server/uinputd.c new file mode 100644 index 0000000..f0a6e0a --- /dev/null +++ b/server/uinputd.c @@ -0,0 +1,203 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "common/packet.h" + +/** + * This is a proof of concept uinput daemon. + * Mainly what we've learnt here is that linux interfaces aren't really ABI + * compatible around different architectures, which is dissapointing as it means + * all the interfaces basically need to be wrapped around in network transparent containers + * which is a boring work, especially when the interfaces are not small. It also means underlying + * data now needs to be understood and we can't act as simply proxy, which means if interfaces + * are changed the wrappers need to be changed as well. + * + * The host and client will understand each other if stars are aligned right: + * - Host and client have same native word size. + * - Host and client were compiled with similar compiler that pads structures identically, + * even if architectures differ. + */ + +struct core { + int uinput; +}; + +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 void +fd_close_or_exit(const int fd) +{ + if (fd >= 0 && close(fd) != 0) + err(EXIT_FAILURE, "close"); +} + +static void +core_ioctl(const struct core *core, const struct ioctl *ioctl, const size_t bytes, void *data) +{ + const uint32_t dir[] = { + _IOC_NONE, // IOCTL_NONE + _IOC_READ, // IOCTL_IOR + _IOC_WRITE, // IOCTL_IOW, + _IOC_READ | _IOC_WRITE, // IOCTL_IOWR + }; + + if (ioctl->dir > IOCTL_IOWR) + return; + + const unsigned long request = _IOC(dir[ioctl->dir], ioctl->type, ioctl->nr, bytes); + + if (_ioctl(core->uinput, request, data) == -1) + err(EXIT_FAILURE, "ioctl"); +} + +static void +core_write(const struct core *core, const size_t bytes, const uint8_t *data) +{ + if (write(core->uinput, data, bytes) != bytes) + err(EXIT_FAILURE, "write: failed to write %zu bytes", bytes); +} + +static void +core_process_ioctl_variant(struct core *core, const struct ioctl *ioctl, FILE *src) +{ + switch (ioctl->arg) { + case IOCTL_ARG_OTHER: + { + uint64_t bytes; + uint8_t arg[16*1000]; // ioctls have max 16kB arg size + if ((bytes = fread(arg, 1, ioctl->bytes, src)) != ioctl->bytes) + err(EXIT_FAILURE, "fread: ioctl failed to read %zu bytes, got %zu bytes", ioctl->bytes, bytes); + core_ioctl(core, ioctl, ioctl->bytes, arg); + } + break; + + case IOCTL_ARG_INT: + { + int arg = ioctl->integer; + core_ioctl(core, ioctl, sizeof(arg), (void*)(intptr_t)arg); + } + break; + } +} + +static void +core_process(struct core *core, const struct packet *packet, FILE *src) +{ + assert(core && packet); + + switch (packet->type) { + case PACKET_IOCTL: + core_process_ioctl_variant(core, &packet->ioctl, src); + break; + + case PACKET_WRITE: + { + uint64_t bytes; + uint8_t arg[16*1000]; + if ((bytes = fread(arg, 1, packet->write.bytes, src)) != packet->write.bytes) + errx(EXIT_FAILURE, "fread: write failed to read %zu bytes, got %zu bytes", packet->write.bytes, bytes); + core_write(core, packet->write.bytes, arg); + } + break; + } +} + +static void +core_release(struct core *core) +{ + if (!core) + return; + + _ioctl(core->uinput, UI_DEV_DESTROY, NULL); + fd_close_or_exit(core->uinput); + *core = (struct core){0}; +} + +static void +core_init(struct core *core) +{ + assert(core); + + if ((core->uinput = open("/dev/uinput", O_WRONLY | O_CLOEXEC)) < 0) + err(EXIT_FAILURE, "open(%s)", "/dev/uinput"); +} + +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[]) +{ + { + const struct sigaction action = { + .sa_handler = sigterm, + }; + + if (sigaction(SIGTERM, &action, NULL) != 0 || sigaction(SIGINT, &action, NULL) != 0) + err(EXIT_FAILURE, "sigaction"); + } + + info(); + struct core core = {0}; + on_exit(core_on_exit, &core); + core_init(&core); + + struct packet packet; + while (true) { + size_t bytes; + if ((bytes = fread(&packet, 1, sizeof(packet), stdin)) != sizeof(packet)) + errx(EXIT_FAILURE, "invalid packet size %zu, expected %zu", bytes, sizeof(packet)); + + core_process(&core, &packet, stdin); + } + + /** + * The core will be destroyed when main returns, thus we exit(); + * instead to avoid cleanup functions from causing SIGSEGV. + */ + exit(EXIT_SUCCESS); + return EXIT_SUCCESS; +} -- cgit v1.2.3