diff options
| author | Jari Vetoniemi <mailroxas@gmail.com> | 2018-07-04 20:25:49 +0300 | 
|---|---|---|
| committer | Jari Vetoniemi <mailroxas@gmail.com> | 2018-07-04 20:56:04 +0300 | 
| commit | 0699cbd82b577a4358d199a627e9bc4497f436e7 (patch) | |
| tree | 3ebd8effe6ce2100077052622ceb8678070ebe9e /clients | |
initial commit
Diffstat (limited to 'clients')
| -rw-r--r-- | clients/linux/Makefile | 11 | ||||
| -rw-r--r-- | clients/linux/linux-uinput.c | 266 | ||||
| -rw-r--r-- | clients/vita/Makefile | 29 | ||||
| -rw-r--r-- | clients/vita/screen.c | 285 | ||||
| -rw-r--r-- | clients/vita/screen.h | 24 | ||||
| -rw-r--r-- | clients/vita/vita-uinput.c | 660 | 
6 files changed, 1275 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; +} | 
