Skip to content

Instantly share code, notes, and snippets.

@Cloudef
Last active July 2, 2018 18:12
Show Gist options
  • Save Cloudef/03713c27eb3f23d659b3f902587c020e to your computer and use it in GitHub Desktop.
Save Cloudef/03713c27eb3f23d659b3f902587c020e to your computer and use it in GitHub Desktop.
poc uinputd and vita client, TL;DR it sucks
#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")));
#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;
}
#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