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/vita/vita-uinput.c | 660 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 660 insertions(+) create mode 100644 clients/vita/vita-uinput.c (limited to 'clients/vita/vita-uinput.c') 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; +} -- cgit v1.2.3