summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--clients/linux/Makefile11
-rw-r--r--clients/linux/linux-uinput.c266
-rw-r--r--clients/vita/Makefile29
-rw-r--r--clients/vita/screen.c285
-rw-r--r--clients/vita/screen.h24
-rw-r--r--clients/vita/vita-uinput.c660
-rw-r--r--common/packet.h79
-rw-r--r--server/Makefile11
-rw-r--r--server/uinputd.c203
9 files changed, 1568 insertions, 0 deletions
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 <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;
+}
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 <stdio.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdarg.h>
+#include <limits.h>
+#include <psp2/display.h>
+#include <psp2/kernel/sysmem.h>
+#include <psp2/kernel/threadmgr.h>
+
+#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 <stdbool.h>
+#include <errno.h>
+
+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 <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdbool.h>
+
+#include "screen.h"
+
+#include <psp2/ctrl.h>
+#include <psp2/touch.h>
+#include <psp2/types.h>
+#include <psp2/sysmodule.h>
+#include <psp2/net/net.h>
+#include <psp2/net/netctl.h>
+#include <psp2/kernel/threadmgr.h>
+#include <psp2/kernel/processmgr.h>
+
+#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 <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <unistd.h>
+#include <assert.h>
+#include <errno.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <err.h>
+
+#include <linux/uinput.h>
+
+#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;
+}