Skip to content

Instantly share code, notes, and snippets.

@TheGammaSqueeze
Last active July 9, 2025 10:59
Show Gist options
  • Save TheGammaSqueeze/b4d3fcbf3403c8c38a00ea85c5d3312f to your computer and use it in GitHub Desktop.
Save TheGammaSqueeze/b4d3fcbf3403c8c38a00ea85c5d3312f to your computer and use it in GitHub Desktop.
Retroid Pocket 4 - controller userspace driver
/*
* RetroidPad.c
*
* − Uses select() + ioctl(FIONREAD) to batch‐read all available bytes in one
* go (instead of VMIN) so that we only wake up once per MCU‐burst.
* − Adds an “idle skip” mechanism: if the button state remains unchanged for
* several consecutive packets, we skip the detailed per-packet parsing until
* a new packet arrives or a threshold is reached.
* − Performs a simple 50‐frame auto‐center calibration of both analog sticks
* at startup.
*
* Compile (on AArch64):
* gcc -O3 -march=armv8-a -o RetroidPad RetroidPad.c -lm
*
* Run (as root):
* chmod +x RetroidPad
* ./RetroidPad "Retroid Pocket Controller"
*
* © 2025
*/
#define _POSIX_C_SOURCE 199309L /* for clock_gettime() */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <termios.h>
#include <sys/ioctl.h>
#include <linux/uinput.h>
#include <time.h>
#include <sys/time.h>
#include <stdint.h>
#include <sys/select.h>
#include <math.h> /* for powf() */
/* Mapping of bits 4..7 and 10..15 to EV_KEY codes */
typedef struct {
uint16_t mask;
int code;
} face_map_t;
static const face_map_t face_map[] = {
{ 1u << 4, BTN_NORTH }, /* bit 4 */
{ 1u << 5, BTN_WEST }, /* bit 5 */
{ 1u << 6, BTN_SOUTH }, /* bit 6 */
{ 1u << 7, BTN_EAST }, /* bit 7 */
{ 1u << 10, BTN_SELECT }, /* bit 10 */
{ 1u << 11, BTN_START }, /* bit 11 */
{ 1u << 12, BTN_THUMBL }, /* bit 12 */
{ 1u << 13, BTN_THUMBR }, /* bit 13 */
{ 1u << 14, BTN_MODE }, /* bit 14 */
{ 1u << 15, KEY_BACK } /* bit 15 */
};
static const size_t face_map_count = sizeof(face_map)/sizeof(face_map[0]);
/* Mask covering bits 4..7 and 10..15 */
#define FACE_MASK ((uint16_t)0xFCF0) /* 0x00F0 | 0xFC00 */
/* Threshold: number of consecutive “no-change” packets to skip */
#define IDLE_SKIP_THRESH 5
/* Raw ADC bits (12-bit) → uinput range */
#define TRIGGER_MAX 0x0FFF /* 4095 */
/* Deadzone in output units (0..4095) */
#define TRIGGER_DEADZONE 200
/* Fraction of raw travel that maps to full output */
#define TRIGGER_THRESHOLD 0.85f
/* Number of frames to sample for stick centering */
#define STICK_CAL_FRAMES 50
/* stick axis clipping range */
#define STICK_CLIP 1300
/* file‐scope stick zero offsets (auto‐calibrated) */
static int calib_zero_LX = 0, calib_zero_LY = 0, calib_zero_RX = 0, calib_zero_RY = 0;
/* Write exactly len bytes (handling partial writes) */
static inline ssize_t write_all(int fd, const void *buf, size_t len) {
const uint8_t *p = buf;
size_t total = 0;
while (total < len) {
ssize_t ret = write(fd, p + total, len - total);
if (ret < 0) {
if (errno == EINTR) continue;
return -1;
}
total += ret;
}
return total;
}
/* Compute the 1‐byte XOR checksum over bytes [4..(count–2)] */
static inline uint8_t compute_checksum(const uint8_t *buf, size_t count) {
uint8_t x = buf[4];
for (size_t i = 5; i < count - 1; i++) {
x ^= buf[i];
}
return x;
}
/* Send the 6 initialization sequences, with 100 ms between each */
static int send_init_sequences(int serial_fd) {
static const uint8_t seq1[] = {
0xA5,0xD3,0x5A,0x3D, 0x00,0x01,0x02,0x00, 0x07,0x01,0x05
};
static const uint8_t seq2[] = {
0xA5,0xD3,0x5A,0x3D, 0x01,0x01,0x01,0x00, 0x06,0x07
};
static const uint8_t seq3[] = {
0xA5,0xD3,0x5A,0x3D, 0x02,0x01,0x01,0x00, 0x02,0x00
};
static const uint8_t seq4[] = {
0xA5,0xD3,0x5A,0x3D,
0x03,0x01,0x0A,0x00, 0x05,0x01,0x00,0x00,
0x00,0x28,0x00,0x00,0x00,0x07,0x23
};
static const uint8_t seq5[] = {
0xA5,0xD3,0x5A,0x3D, 0x04,0x01,0x01,0x00, 0x06,0x02
};
static const uint8_t seq6[] = {
0xA5,0xD3,0x5A,0x3D, 0x05,0x01,0x01,0x00, 0x02,0x07
};
const uint8_t *seqs[] = { seq1, seq2, seq3, seq4, seq5, seq6 };
const size_t lens[] = { sizeof(seq1), sizeof(seq2), sizeof(seq3),
sizeof(seq4), sizeof(seq5), sizeof(seq6) };
for (int i = 0; i < 6; i++) {
if (write_all(serial_fd, seqs[i], lens[i]) < 0) return -1;
usleep(100000);
}
return 0;
}
/*
* Open /dev/ttyS1 as 115200 8N1, no flow control, raw mode,
* with VMIN=1, VTIME=0 (so read() will return as soon as ≥1 byte is ready).
* Returns a *blocking* fd or -1 on error.
*/
static int open_serial(const char *path) {
int fd = open(path, O_RDWR | O_NOCTTY | O_NONBLOCK);
if (fd < 0) { perror("open serial"); return -1; }
struct termios t;
if (tcgetattr(fd, &t) < 0) { perror("tcgetattr"); close(fd); return -1; }
t.c_iflag &= ~(INPCK|ISTRIP|IXON|IXOFF|BRKINT|ICRNL|IGNCR|IGNBRK);
t.c_oflag &= ~OPOST;
t.c_cflag &= ~(CSIZE|PARENB|CRTSCTS);
t.c_cflag |= CS8|CLOCAL|CREAD;
t.c_lflag &= ~(ICANON|ECHO|ECHOE|ISIG);
cfsetispeed(&t, B115200); cfsetospeed(&t, B115200);
t.c_cc[VMIN] = 1; t.c_cc[VTIME] = 0;
if (tcsetattr(fd, TCSANOW, &t) < 0) { perror("tcsetattr"); close(fd); return -1; }
int flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flags & ~O_NONBLOCK);
return fd;
}
/*
* Create a virtual gamepad via /dev/uinput, enabling:
* • face buttons + Select/Start/Thumb/Back (10 codes)
* • L1 (BTN_TL), R1 (BTN_TR), L2 (BTN_TL2), R2 (BTN_TR2)
* • ABS_HAT0X, ABS_HAT0Y (−1..+1) for the D‐pad.
* • ABS_X/ABS_Y, ABS_Z/ABS_RZ for analog sticks.
* • ABS_GAS and ABS_BRAKE (0..4095) for analog triggers.
* Returns uinput fd or -1 on error.
*/
static int uinput_create(const char *devname) {
int fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK);
if (fd < 0) { perror("open uinput"); return -1; }
#define UI_CHECK(x) if ((x)<0){perror(#x);close(fd);return -1;}
UI_CHECK(ioctl(fd, UI_SET_EVBIT, EV_KEY));
for (size_t i = 0; i < face_map_count; i++)
UI_CHECK(ioctl(fd, UI_SET_KEYBIT, face_map[i].code));
UI_CHECK(ioctl(fd, UI_SET_KEYBIT, BTN_TL));
UI_CHECK(ioctl(fd, UI_SET_KEYBIT, BTN_TR));
UI_CHECK(ioctl(fd, UI_SET_KEYBIT, BTN_TL2));
UI_CHECK(ioctl(fd, UI_SET_KEYBIT, BTN_TR2));
UI_CHECK(ioctl(fd, UI_SET_EVBIT, EV_ABS));
UI_CHECK(ioctl(fd, UI_SET_ABSBIT, ABS_HAT0X));
UI_CHECK(ioctl(fd, UI_SET_ABSBIT, ABS_HAT0Y));
UI_CHECK(ioctl(fd, UI_SET_ABSBIT, ABS_X));
UI_CHECK(ioctl(fd, UI_SET_ABSBIT, ABS_Y));
UI_CHECK(ioctl(fd, UI_SET_ABSBIT, ABS_Z)); /* second stick X */
UI_CHECK(ioctl(fd, UI_SET_ABSBIT, ABS_RZ)); /* second stick Y */
UI_CHECK(ioctl(fd, UI_SET_ABSBIT, ABS_GAS));
UI_CHECK(ioctl(fd, UI_SET_ABSBIT, ABS_BRAKE));
struct uinput_user_dev uidev;
memset(&uidev, 0, sizeof(uidev));
snprintf(uidev.name, UINPUT_MAX_NAME_SIZE, "%s", devname);
uidev.id.bustype = BUS_USB;
uidev.id.vendor = 0x2020;
uidev.id.product = 0x3001;
uidev.absmin[ABS_HAT0X] = -1; uidev.absmax[ABS_HAT0X] = 1;
uidev.absmin[ABS_HAT0Y] = -1; uidev.absmax[ABS_HAT0Y] = 1;
uidev.absmin[ABS_X] = -STICK_CLIP; uidev.absmax[ABS_X] = STICK_CLIP;
uidev.absmin[ABS_Y] = -STICK_CLIP; uidev.absmax[ABS_Y] = STICK_CLIP;
uidev.absmin[ABS_Z] = -STICK_CLIP; uidev.absmax[ABS_Z] = STICK_CLIP;
uidev.absmin[ABS_RZ] = -STICK_CLIP; uidev.absmax[ABS_RZ] = STICK_CLIP;
uidev.absmin[ABS_GAS] = 0; uidev.absmax[ABS_GAS] = TRIGGER_MAX;
uidev.absmin[ABS_BRAKE] = 0; uidev.absmax[ABS_BRAKE] = TRIGGER_MAX;
UI_CHECK(write_all(fd, &uidev, sizeof(uidev)));
UI_CHECK(ioctl(fd, UI_DEV_CREATE));
#undef UI_CHECK
return fd;
}
/* Returns current monotonic time in microseconds */
static inline uint64_t now_us_monotonic(void) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (uint64_t)ts.tv_sec*1000000ULL + ts.tv_nsec/1000ULL;
}
/* ─── Fan control via MCU commands (no extra library) ───────────────────── */
static int fun_pwm_init(int sfd) {
uint8_t pkt[17] = {
0xA5,0xD3,0x5A,0x3D,
0x05,0x02,0x08,0x00,
0x00,0x00,0x00,0xEF,
0x00,0x00,0x00,0x0B,
0x00
};
pkt[16] = compute_checksum(pkt,17);
return write_all(sfd,pkt,17)==17?0:-1;
}
static int set_fan_enable(int sfd, uint8_t e, uint32_t sp) {
uint8_t pkt[17] = {
0xA5,0xD3,0x5A,0x3D,
0x05,0x03,0x08,0x00,
0x00,0x00,0x00, e,
(uint8_t)(sp>>24),(uint8_t)(sp>>16),
(uint8_t)(sp>>8),(uint8_t)sp,
0x00
};
pkt[16] = compute_checksum(pkt,17);
return write_all(sfd,pkt,17)==17?0:-1;
}
/* ─── MAIN ──────────────────────────────────────────────────────────────── */
int main(int argc, char *argv[]) {
if (argc != 2) {
fprintf(stderr, "Usage: %s \"Retroid Pocket Controller\"\n", argv[0]);
return EXIT_FAILURE;
}
/* 1) Create uinput device */
int ufd = uinput_create(argv[1]);
if (ufd < 0) return EXIT_FAILURE;
printf("[INFO] Virtual gamepad \"%s\" created (uinput_fd=%d)\n", argv[1], ufd);
/* 2) Open and configure /dev/ttyS1 */
int sfd = open_serial("/dev/ttyS1");
if (sfd < 0) return EXIT_FAILURE;
printf("[INFO] Opened MCU serial /dev/ttyS1 (fd=%d)\n", sfd);
/* 3) Send MCU init sequences */
if (send_init_sequences(sfd) < 0) {
fprintf(stderr, "[ERROR] send_init_sequences failed\n");
close(sfd);
return EXIT_FAILURE;
}
printf("[INFO] Sent init sequences\n");
/***** Stick Auto‐Center Calibration *****/
{
printf("[INFO] Calibrating sticks: sampling %d frames...\n", STICK_CAL_FRAMES);
int64_t sum_LX = 0, sum_LY = 0, sum_RX = 0, sum_RY = 0;
int count = 0;
uint8_t buf[1500]; size_t buf_len = 0;
while (count < STICK_CAL_FRAMES) {
/* wait for data */
fd_set rfds; FD_ZERO(&rfds); FD_SET(sfd, &rfds);
if (select(sfd+1, &rfds, NULL, NULL, NULL) < 0) {
if (errno == EINTR) continue;
perror("select"); break;
}
/* read available */
int waiting=0;
ioctl(sfd, FIONREAD, &waiting);
if (waiting <= 0) continue;
if (waiting > (int)(sizeof(buf)-buf_len)) waiting = sizeof(buf)-buf_len;
ssize_t r = read(sfd, buf+buf_len, waiting);
if (r <= 0) continue;
buf_len += r;
/* parse for first 0x02 packet */
size_t off = 0;
while (buf_len - off >= 8 && count < STICK_CAL_FRAMES) {
if (buf[off]!=0xA5||buf[off+1]!=0xD3||buf[off+2]!=0x5A||buf[off+3]!=0x3D) {
off++; continue;
}
uint8_t cmd = buf[off+5];
uint16_t dlen = buf[off+6] | (buf[off+7]<<8);
size_t plen = 4+1+1+2+dlen+1;
if (buf_len-off < plen) break;
/* checksum */
uint8_t cs = buf[off+4];
for (size_t i=off+5; i<off+plen-1; i++) cs ^= buf[i];
if (cs != buf[off+plen-1]) { off++; continue; }
if (cmd == 0x02 && dlen >= 14) {
const uint8_t *d = buf + off + 8;
int16_t lx = (int16_t)(d[6] | (d[7]<<8));
int16_t ly = (int16_t)(d[8] | (d[9]<<8));
int16_t rx = (int16_t)(d[10] | (d[11]<<8));
int16_t ry = (int16_t)(d[12] | (d[13]<<8));
sum_LX += lx; sum_LY += ly;
sum_RX += rx; sum_RY += ry;
count++;
}
off += plen;
}
/* shift leftover */
if (off>0) {
memmove(buf, buf+off, buf_len-off);
buf_len -= off;
}
}
/* compute averages */
calib_zero_LX = (int)(sum_LX / STICK_CAL_FRAMES);
calib_zero_LY = (int)(sum_LY / STICK_CAL_FRAMES);
calib_zero_RX = (int)(sum_RX / STICK_CAL_FRAMES);
calib_zero_RY = (int)(sum_RY / STICK_CAL_FRAMES);
printf("[INFO] Stick centers: LX=%d, LY=%d, RX=%d, RY=%d\n",
calib_zero_LX, calib_zero_LY, calib_zero_RX, calib_zero_RY);
}
/***** End calibration *****/
/* 5) Main loop: read and parse button packets */
uint8_t buf[1500]; size_t buf_len = 0;
uint64_t last_valid_us = now_us_monotonic();
uint16_t prev_buttons = 0;
int prev_hatX = 0, prev_hatY = 0;
int prev_L1 = 0, prev_R1 = 0;
int prev_L2_analog = 0, prev_R2_analog = 0;
int prev_btn_L2 = 0, prev_btn_R2 = 0; /* digital trigger state */
int prev_LX = 0, prev_LY = 0, prev_Z = 0, prev_RZ = 0;
int idle_skip_counter = 0;
/* dynamic rest calibration triggers */
int rest_L2 = -1, rest_R2 = -1;
while (1) {
/* 5a) Resend init if >1 s without a valid packet */
uint64_t now_us = now_us_monotonic();
if (now_us - last_valid_us > 1000000ULL) {
send_init_sequences(sfd);
last_valid_us = now_us_monotonic();
buf_len = 0;
idle_skip_counter = 0;
/* re-kick fan each reconnect */
fun_pwm_init(sfd);
set_fan_enable(sfd, 1, 0xFFFFFFFFu);
}
/* 5b) Wait for data */
fd_set rfds; FD_ZERO(&rfds); FD_SET(sfd, &rfds);
if (select(sfd+1, &rfds, NULL, NULL, NULL) < 0) {
if (errno == EINTR) continue;
perror("select"); break;
}
/* 5c) Check available bytes */
int waiting = 0;
if (ioctl(sfd, FIONREAD, &waiting) < 0) {
perror("ioctl FIONREAD"); break;
}
if (waiting <= 0) continue;
if (waiting > (int)(sizeof(buf) - buf_len)) waiting = sizeof(buf) - buf_len;
/* 5d) Read all waiting bytes */
ssize_t r = read(sfd, buf + buf_len, waiting);
if (r < 0) {
if (errno == EINTR) continue;
perror("read"); break;
}
if (r > 0) buf_len += r;
/* 5e) Parse packets */
size_t offset = 0;
while (buf_len - offset >= 8) {
if (buf[offset]!=0xA5 || buf[offset+1]!=0xD3 ||
buf[offset+2]!=0x5A || buf[offset+3]!=0x3D) {
offset++;
continue;
}
uint8_t cmd = buf[offset+5];
uint16_t data_len = buf[offset+6] | (buf[offset+7]<<8);
size_t packet_len = 4 + 1 + 1 + 2 + data_len + 1;
if (buf_len - offset < packet_len) break;
/* Validate checksum */
uint8_t cs = buf[offset+4];
for (size_t i = offset+5; i < offset+packet_len-1; i++) {
cs ^= buf[i];
}
if (cs != buf[offset+packet_len-1]) {
offset++;
continue;
}
/* Process button state packets (cmd=0x02) */
if (cmd == 0x02 && data_len >= 14) {
const uint8_t *data = buf + offset + 8;
uint16_t buttons = data[0] | (data[1]<<8);
if (buttons == prev_buttons) {
idle_skip_counter++;
if (idle_skip_counter < IDLE_SKIP_THRESH) {
offset += packet_len;
continue;
}
idle_skip_counter = 0;
} else {
idle_skip_counter = 0;
}
last_valid_us = now_us_monotonic();
struct timeval tv; gettimeofday(&tv, NULL);
struct input_event evs[32];
int ev_count = 0;
/* D-pad → HAT axes */
int hatX = ((buttons&(1u<<3))?1:0) - ((buttons&(1u<<2))?1:0);
int hatY = ((buttons&(1u<<1))?1:0) - ((buttons&(1u<<0))?1:0);
if (hatX != prev_hatX) {
evs[ev_count++] = (struct input_event){
.time=tv, .type=EV_ABS,
.code=ABS_HAT0X, .value=hatX
};
}
if (hatY != prev_hatY) {
evs[ev_count++] = (struct input_event){
.time=tv, .type=EV_ABS,
.code=ABS_HAT0Y, .value=hatY
};
}
/* Face & Select/Start/Thumb/Back */
uint16_t changed_face = (buttons ^ prev_buttons) & FACE_MASK;
if (changed_face) {
for (size_t i = 0; i < face_map_count; i++) {
if (changed_face & face_map[i].mask) {
evs[ev_count++] = (struct input_event){
.time=tv, .type=EV_KEY,
.code=face_map[i].code,
.value=(buttons & face_map[i].mask)?1:0
};
}
}
}
/* Shoulder buttons L1/R1 (digital) */
int pressed_L1 = (buttons & (1u<<8))?1:0;
int pressed_R1 = (buttons & (1u<<9))?1:0;
if (pressed_L1 != prev_L1) {
evs[ev_count++] = (struct input_event){
.time=tv, .type=EV_KEY,
.code=BTN_TL, .value=pressed_L1
};
}
if (pressed_R1 != prev_R1) {
evs[ev_count++] = (struct input_event){
.time=tv, .type=EV_KEY,
.code=BTN_TR, .value=pressed_R1
};
}
/* ─── Analog sticks ───────────────────────────────── */
int16_t raw_LX = (int16_t)(data[6] | (data[7]<<8));
int16_t raw_LY = (int16_t)(data[8] | (data[9]<<8));
int16_t raw_RX = (int16_t)(data[10] | (data[11]<<8));
int16_t raw_RY = (int16_t)(data[12] | (data[13]<<8));
/* subtract calibrated center */
raw_LX -= calib_zero_LX;
raw_LY -= calib_zero_LY;
raw_RX -= calib_zero_RX;
raw_RY -= calib_zero_RY;
/* flip X/Y */
raw_LX = -raw_LX;
raw_LY = -raw_LY;
raw_RX = -raw_RX;
raw_RY = -raw_RY;
/* clamp to ±STICK_CLIP */
if (raw_LX > STICK_CLIP) raw_LX = STICK_CLIP;
if (raw_LX < -STICK_CLIP) raw_LX = -STICK_CLIP;
if (raw_LY > STICK_CLIP) raw_LY = STICK_CLIP;
if (raw_LY < -STICK_CLIP) raw_LY = -STICK_CLIP;
if (raw_RX > STICK_CLIP) raw_RX = STICK_CLIP;
if (raw_RX < -STICK_CLIP) raw_RX = -STICK_CLIP;
if (raw_RY > STICK_CLIP) raw_RY = STICK_CLIP;
if (raw_RY < -STICK_CLIP) raw_RY = -STICK_CLIP;
if (raw_LX != prev_LX) {
evs[ev_count++] = (struct input_event){
.time=tv, .type=EV_ABS,
.code=ABS_X, .value=raw_LX
};
}
if (raw_LY != prev_LY) {
evs[ev_count++] = (struct input_event){
.time=tv, .type=EV_ABS,
.code=ABS_Y, .value=raw_LY
};
}
/* right stick → Z/RZ */
if (raw_RX != prev_Z) {
evs[ev_count++] = (struct input_event){
.time=tv, .type=EV_ABS,
.code=ABS_Z, .value=raw_RX
};
}
if (raw_RY != prev_RZ) {
evs[ev_count++] = (struct input_event){
.time=tv, .type=EV_ABS,
.code=ABS_RZ, .value=raw_RY
};
}
/* ─── Analog triggers ───────────────────────────────── */
uint16_t raw_L2_12 = (data[2] | (data[3]<<8)) >> 4;
uint16_t raw_R2_12 = (data[4] | (data[5]<<8)) >> 4;
if (rest_R2 < 0) rest_R2 = raw_R2_12;
if (rest_L2 < 0) rest_L2 = raw_L2_12;
float frac_gas = (rest_R2 - raw_R2_12) / (float)rest_R2;
float frac_brake = (rest_L2 - raw_L2_12) / (float)rest_L2;
if (frac_gas<0) frac_gas=0; if (frac_gas>1) frac_gas=1;
if (frac_brake<0) frac_brake=0; if (frac_brake>1) frac_brake=1;
float dz = TRIGGER_DEADZONE / (float)TRIGGER_MAX;
if (frac_gas < dz) frac_gas = 0;
if (frac_brake < dz) frac_brake = 0;
float sg = frac_gas / TRIGGER_THRESHOLD; if (sg > 1) sg = 1;
float sb = frac_brake / TRIGGER_THRESHOLD; if (sb > 1) sb = 1;
int analog_GAS = (int)(sg * TRIGGER_MAX + 0.5f);
int analog_BRAKE = (int)(sb * TRIGGER_MAX + 0.5f);
if (analog_GAS != prev_R2_analog) {
evs[ev_count++] = (struct input_event){
.time=tv, .type=EV_ABS,
.code=ABS_GAS, .value=analog_GAS
};
}
if (analog_BRAKE != prev_L2_analog) {
evs[ev_count++] = (struct input_event){
.time=tv, .type=EV_ABS,
.code=ABS_BRAKE, .value=analog_BRAKE
};
}
/* ─── Digital trigger fallback at 60% ───────────────────── */
int thresh = (int)(0.6f * (float)TRIGGER_MAX + 0.5f);
int btn_R2 = analog_GAS >= thresh ? 1 : 0;
int btn_L2 = analog_BRAKE >= thresh ? 1 : 0;
if (btn_R2 != prev_btn_R2) {
evs[ev_count++] = (struct input_event){
.time=tv, .type=EV_KEY,
.code=BTN_TR2, .value=btn_R2
};
}
if (btn_L2 != prev_btn_L2) {
evs[ev_count++] = (struct input_event){
.time=tv, .type=EV_KEY,
.code=BTN_TL2, .value=btn_L2
};
}
/* EV_SYN + write */
if (ev_count > 0) {
evs[ev_count++] = (struct input_event){
.time=tv, .type=EV_SYN,
.code=SYN_REPORT, .value=0
};
write_all(ufd, evs, ev_count * sizeof(evs[0]));
}
/* Update previous state */
prev_buttons = buttons;
prev_hatX = hatX;
prev_hatY = hatY;
prev_L1 = pressed_L1;
prev_R1 = pressed_R1;
prev_L2_analog = analog_BRAKE;
prev_R2_analog = analog_GAS;
prev_btn_L2 = btn_L2;
prev_btn_R2 = btn_R2;
prev_LX = raw_LX;
prev_LY = raw_LY;
prev_Z = raw_RX;
prev_RZ = raw_RY;
}
offset += packet_len;
}
/* Shift leftover bytes down */
if (offset > 0) {
memmove(buf, buf + offset, buf_len - offset);
buf_len -= offset;
}
}
/* Cleanup */
close(sfd);
ioctl(ufd, UI_DEV_DESTROY);
close(ufd);
return EXIT_SUCCESS;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment