Last active
July 2, 2018 18:12
-
-
Save Cloudef/03713c27eb3f23d659b3f902587c020e to your computer and use it in GitHub Desktop.
poc uinputd and vita client, TL;DR it sucks
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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"))); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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_NONBLOCK | 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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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 uint8_t *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), (const uint8_t*)&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), (const uint8_t*)&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), (const uint8_t*)&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), (const uint8_t*)&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), (const uint8_t*)&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; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment