481 lines
13 KiB
C
481 lines
13 KiB
C
/* ===== Includes ===== */
|
|
|
|
#include <stdlib.h>
|
|
#include <stddef.h>
|
|
#include <inttypes.h>
|
|
#include <string.h> /* memset */
|
|
#include <stdio.h>
|
|
|
|
#include <unistd.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <libusb-1.0/libusb.h>
|
|
/*#include <linux/uinput.h>*/
|
|
#include <libevdev-1.0/libevdev/libevdev.h>
|
|
#include <libevdev-1.0/libevdev/libevdev-uinput.h>
|
|
#include <sys/types.h> /* ssize_t */
|
|
|
|
/* ===== Defines ===== */
|
|
|
|
#define RC_SUCCESS 0
|
|
#define RC_LIBUSB_ERROR -1
|
|
#define RC_UINPUT_ERROR -2
|
|
#define RC_FILE_IO_ERROR -3
|
|
|
|
#define error_printf(...) printf("[error] " __VA_ARGS__)
|
|
#define warn_printf(...) printf("[warn] " __VA_ARGS__)
|
|
|
|
#ifdef DEBUG
|
|
#define debug_printf(...) printf("[debug] " __VA_ARGS__)
|
|
#else
|
|
#define debug_printf(...)
|
|
#endif
|
|
|
|
#define XBO_MAX_CNT 32
|
|
#define XBO_INTERFACE 0
|
|
#define XBO_ENDPOINT_IN 0x81
|
|
#define XBO_ENDPOINT_OUT 0x01
|
|
#define XBO_MAX_PACKET_SIZE 64
|
|
#define XBO_DEADZONE_WHEEL 512
|
|
#define XBO_DEADZONE_PEDAL 0
|
|
|
|
/* ===== Structs ===== */
|
|
|
|
enum xbo_packet_type {
|
|
XBO_PACKET_WAITING_CONNECTION = 0x02,
|
|
XBO_PACKET_START_INPUT = 0X05,
|
|
XBO_PACKET_HEARTBEAT = 0x03,
|
|
XBO_PACKET_INPUT_DATA = 0x20,
|
|
XBO_PACKET_CRASH = 0x01
|
|
};
|
|
|
|
struct xbo_packet_input_data {
|
|
uint8_t packet_type;
|
|
uint8_t const_00;
|
|
uint16_t id;
|
|
|
|
uint8_t sync : 1;
|
|
uint8_t dummy : 1;
|
|
uint8_t start : 1;
|
|
uint8_t back : 1;
|
|
|
|
uint8_t a : 1;
|
|
uint8_t b : 1;
|
|
uint8_t x : 1;
|
|
uint8_t y : 1;
|
|
|
|
uint8_t dpad_up : 1;
|
|
uint8_t dpad_down : 1;
|
|
uint8_t dpad_left : 1;
|
|
uint8_t dpad_right : 1;
|
|
|
|
uint8_t bumper_left : 1;
|
|
uint8_t bumper_right : 1;
|
|
uint8_t stick_left_click : 1;
|
|
uint8_t stick_right_click : 1;
|
|
|
|
uint16_t trigger_left;
|
|
uint16_t trigger_right;
|
|
|
|
int16_t stick_left_x;
|
|
int16_t stick_left_y;
|
|
int16_t stick_right_x;
|
|
int16_t stick_right_y;
|
|
};
|
|
|
|
struct xbo_input_data {
|
|
uint8_t start : 1;
|
|
uint8_t back : 1;
|
|
|
|
uint8_t a : 1;
|
|
uint8_t b : 1;
|
|
uint8_t x : 1;
|
|
uint8_t y : 1;
|
|
|
|
uint8_t dpad_up : 1;
|
|
uint8_t dpad_down : 1;
|
|
uint8_t dpad_left : 1;
|
|
uint8_t dpad_right : 1;
|
|
|
|
uint8_t bumper_left : 1;
|
|
uint8_t bumper_right : 1;
|
|
|
|
int16_t wheel;
|
|
|
|
uint16_t pedal_gas;
|
|
uint16_t pedal_brake;
|
|
};
|
|
|
|
/* ===== Variables ===== */
|
|
|
|
uint16_t compatible_ids[][2] = {
|
|
{ 0x044f, 0xb671 } };
|
|
|
|
uint8_t xbo_packet_start_input[] = { XBO_PACKET_START_INPUT, 0x20, 0x00, 0x01, 0x00 };
|
|
uint8_t xbo_packet_crash[] = { XBO_PACKET_CRASH, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e };
|
|
|
|
/* ===== Functions ===== */
|
|
|
|
static int32_t claim_device(struct libusb_device *dev, struct libusb_device_handle **handle) {
|
|
int32_t err = 0;
|
|
|
|
err = libusb_open(dev, handle);
|
|
if (err != LIBUSB_SUCCESS) {
|
|
error_printf("libusb: %s\n", libusb_error_name(err));
|
|
return RC_LIBUSB_ERROR;
|
|
}
|
|
|
|
err = libusb_claim_interface(*handle, XBO_INTERFACE);
|
|
if (err != LIBUSB_SUCCESS) {
|
|
error_printf("libusb: %s\n", libusb_error_name(err));
|
|
return RC_LIBUSB_ERROR;
|
|
}
|
|
|
|
return RC_SUCCESS;
|
|
}
|
|
static int32_t unclaim_device(struct libusb_device_handle *handle) {
|
|
int32_t err = libusb_release_interface(handle, XBO_INTERFACE);
|
|
if (err != LIBUSB_SUCCESS) {
|
|
error_printf("libusb: %s\n", libusb_error_name(err));
|
|
return RC_LIBUSB_ERROR;
|
|
}
|
|
|
|
libusb_close(handle);
|
|
|
|
return RC_SUCCESS;
|
|
}
|
|
|
|
static int32_t get_device_array(struct libusb_device_handle **handles, size_t len, size_t *found_cnt) {
|
|
int32_t err = 0;
|
|
|
|
struct libusb_device **devs = NULL;
|
|
ssize_t dev_cnt = libusb_get_device_list(NULL, &devs);
|
|
|
|
if (dev_cnt < 0) {
|
|
error_printf("libusb: %s\n", libusb_error_name(dev_cnt));
|
|
err = RC_LIBUSB_ERROR;
|
|
return 0;
|
|
}
|
|
|
|
struct libusb_device_descriptor desc = { 0 };
|
|
size_t handle_cnt = 0;
|
|
|
|
for (size_t i = 0; i < (size_t)dev_cnt; ++i) {
|
|
err = libusb_get_device_descriptor(devs[i], &desc);
|
|
if (err != LIBUSB_SUCCESS) {
|
|
error_printf("libusb: %s\n", libusb_error_name(err));
|
|
err = RC_LIBUSB_ERROR;
|
|
return 0;
|
|
}
|
|
|
|
debug_printf("Device %04" PRIx16 ":%04" PRIx16 "\n", desc.idVendor, desc.idProduct);
|
|
|
|
for (size_t j = 0; j < sizeof compatible_ids / sizeof compatible_ids[0]; ++j) {
|
|
if (desc.idVendor == compatible_ids[j][0] && desc.idProduct == compatible_ids[j][1]) {
|
|
debug_printf("Found device\n");
|
|
|
|
err = claim_device(devs[i], &handles[handle_cnt]);
|
|
if (err != RC_SUCCESS) {
|
|
return err;
|
|
}
|
|
|
|
++handle_cnt;
|
|
}
|
|
}
|
|
|
|
if (handle_cnt == len) {
|
|
warn_printf("Reached maximum number of devices\n");
|
|
break;
|
|
}
|
|
}
|
|
|
|
libusb_free_device_list(devs, 1);
|
|
|
|
debug_printf("Found %zi compatible devices\n", handle_cnt);
|
|
|
|
*found_cnt = handle_cnt;
|
|
|
|
return RC_SUCCESS;
|
|
}
|
|
|
|
static int32_t update_device(struct libusb_device_handle *handle, struct xbo_input_data *input) {
|
|
int32_t err = 0;
|
|
|
|
uint8_t packet[XBO_MAX_PACKET_SIZE] = { 0x00 };
|
|
size_t packet_size = 0;
|
|
|
|
err = libusb_interrupt_transfer(handle, XBO_ENDPOINT_IN, packet, XBO_MAX_PACKET_SIZE,
|
|
(int32_t *)&packet_size, 0);
|
|
if (err != LIBUSB_SUCCESS) {
|
|
error_printf("libusb: %s\n", libusb_error_name(err));
|
|
return RC_LIBUSB_ERROR;
|
|
}
|
|
|
|
switch (packet[0]) {
|
|
case XBO_PACKET_WAITING_CONNECTION:
|
|
debug_printf("Received XBO_PACKET_WAITING_CONNECTION\n");
|
|
|
|
err = libusb_interrupt_transfer(handle, XBO_ENDPOINT_OUT, xbo_packet_start_input,
|
|
sizeof(xbo_packet_start_input), NULL, 0);
|
|
if (err != LIBUSB_SUCCESS) {
|
|
error_printf("libusb: %s\n", libusb_error_name(err));
|
|
return RC_LIBUSB_ERROR;
|
|
}
|
|
break;
|
|
case XBO_PACKET_HEARTBEAT:
|
|
debug_printf("Received XBO_PACKET_HEARTBEAT\n");
|
|
break;
|
|
case XBO_PACKET_INPUT_DATA:
|
|
;
|
|
struct xbo_packet_input_data ipacket = *(struct xbo_packet_input_data *)packet;
|
|
|
|
input->start = ipacket.start;
|
|
input->back = ipacket.back;
|
|
|
|
input->a = ipacket.a;
|
|
input->b = ipacket.b;
|
|
input->x = ipacket.x;
|
|
input->y = ipacket.y;
|
|
|
|
input->dpad_up = ipacket.dpad_up;
|
|
input->dpad_down = ipacket.dpad_down;
|
|
input->dpad_left = ipacket.dpad_left;
|
|
input->dpad_right = ipacket.dpad_right;
|
|
|
|
input->bumper_left = ipacket.bumper_left;
|
|
input->bumper_right = ipacket.bumper_right;
|
|
|
|
input->pedal_gas = ipacket.trigger_right;
|
|
input->pedal_brake = ipacket.stick_left_x;
|
|
|
|
input->wheel = (ipacket.trigger_left > 32768 + XBO_DEADZONE_WHEEL
|
|
|| ipacket.trigger_left < 32768 - XBO_DEADZONE_WHEEL ?
|
|
ipacket.trigger_left : 32768 ) - 32768;
|
|
|
|
break;
|
|
default:
|
|
warn_printf("Unknown packet type: %02" PRIx8 "\n", packet[0]);
|
|
break;
|
|
}
|
|
|
|
return RC_SUCCESS;
|
|
}
|
|
|
|
static const char * int16_display_bar(int16_t n) {
|
|
int16_t div = n / 4096;
|
|
if (div == -8) { return "| |"; }
|
|
if (div == -7) { return "|= |"; }
|
|
if (div == -6) { return "|== |"; }
|
|
if (div == -5) { return "|=== |"; }
|
|
if (div == -4) { return "|==== |"; }
|
|
if (div == -3) { return "|===== |"; }
|
|
if (div == -2) { return "|====== |"; }
|
|
if (div == -1) { return "|======= |"; }
|
|
if (div == 0) { return "|======== |"; }
|
|
if (div == 1) { return "|========= |"; }
|
|
if (div == 2) { return "|========== |"; }
|
|
if (div == 3) { return "|=========== |"; }
|
|
if (div == 4) { return "|============ |"; }
|
|
if (div == 5) { return "|============= |"; }
|
|
if (div == 6) { return "|============== |"; }
|
|
if (div == 7) { return "|===============|"; }
|
|
return "|ERR |";
|
|
}
|
|
static const char * uint16_display_bar(uint16_t n) {
|
|
uint16_t div = n / 4096;
|
|
if (div == 0) { return "| |"; }
|
|
if (div == 1) { return "|= |"; }
|
|
if (div == 2) { return "|== |"; }
|
|
if (div == 3) { return "|=== |"; }
|
|
if (div == 4) { return "|==== |"; }
|
|
if (div == 5) { return "|===== |"; }
|
|
if (div == 6) { return "|====== |"; }
|
|
if (div == 7) { return "|======= |"; }
|
|
if (div == 8) { return "|======== |"; }
|
|
if (div == 9) { return "|========= |"; }
|
|
if (div == 10) { return "|========== |"; }
|
|
if (div == 11) { return "|=========== |"; }
|
|
if (div == 12) { return "|============ |"; }
|
|
if (div == 13) { return "|============= |"; }
|
|
if (div == 14) { return "|============== |"; }
|
|
if (div == 15) { return "|===============|"; }
|
|
return "|ERR |";
|
|
}
|
|
|
|
static void debug_print_input_data(struct xbo_input_data input) {
|
|
debug_printf("Input Data:\n"
|
|
"\tSt: %" PRIi8 "\tBa: %" PRIi8 "\n"
|
|
"\tA: %" PRIi8 "\tB: %" PRIi8 "\tX: %" PRIi8 "\tY: %" PRIi8 "\n"
|
|
"\tDu: %" PRIi8 "\tDd: %" PRIi8 "\tDl: %" PRIi8 "\tDr: %" PRIi8 "\n"
|
|
"\tLb: %" PRIi8 "\tRb: %" PRIi8 "\n"
|
|
"\tPb: %s\tPg: %s\n\tWh: %s\n",
|
|
input.start, input.back,
|
|
input.a, input.b, input.x, input.y,
|
|
input.dpad_up, input.dpad_down,
|
|
input.dpad_left, input.dpad_right,
|
|
input.bumper_left, input.bumper_right,
|
|
uint16_display_bar(input.pedal_brake * 64), uint16_display_bar(input.pedal_gas * 64),
|
|
int16_display_bar(input.wheel));
|
|
}
|
|
|
|
static int32_t emit(uint16_t uinput, uint16_t type, uint16_t code, int32_t val) {
|
|
struct input_event event = {
|
|
.type = type,
|
|
.code = code,
|
|
.value = val,
|
|
.time.tv_sec = 0,
|
|
.time.tv_usec = 0 };
|
|
|
|
if (write(uinput, &event, sizeof(event)) == -1) {
|
|
error_printf("Failed to write to /dev/uinput\n");
|
|
return RC_FILE_IO_ERROR;
|
|
}
|
|
|
|
return RC_SUCCESS;
|
|
}
|
|
int32_t main(void) {
|
|
int32_t err = 0;
|
|
|
|
/* Initializing libusb */
|
|
err = libusb_init(NULL);
|
|
if (err != LIBUSB_SUCCESS) {
|
|
error_printf("libusb: %s\n", libusb_error_name(err));
|
|
return RC_LIBUSB_ERROR;
|
|
}
|
|
|
|
debug_printf("Initialized libusb\n");
|
|
|
|
#ifdef DEBUG
|
|
err = libusb_set_option(NULL, LIBUSB_OPTION_LOG_LEVEL, LIBUSB_LOG_LEVEL_WARNING);
|
|
if (err != LIBUSB_SUCCESS) {
|
|
error_printf("libusb: %s\n", libusb_error_name(err));
|
|
return RC_LIBUSB_ERROR;
|
|
}
|
|
#endif
|
|
|
|
debug_printf("Set libusb to debug\n");
|
|
|
|
size_t handle_cnt = 0;
|
|
struct libusb_device_handle *handles[XBO_MAX_CNT] = { NULL };
|
|
|
|
err = get_device_array(handles, XBO_MAX_CNT, &handle_cnt);
|
|
if (err != RC_SUCCESS) {
|
|
return err;
|
|
}
|
|
|
|
/* Initialize evdev */
|
|
struct libevdev *evdev = NULL;
|
|
struct libevdev_uinput *ui_evdev = NULL;
|
|
evdev = libevdev_new();
|
|
|
|
libevdev_set_name(evdev, "Thrustmaster Ferrari 458 Spider XBox One Wheel");
|
|
|
|
libevdev_enable_event_type(evdev, EV_KEY);
|
|
libevdev_enable_event_type(evdev, EV_ABS);
|
|
|
|
libevdev_enable_event_code(evdev, EV_KEY, BTN_START, NULL);
|
|
libevdev_enable_event_code(evdev, EV_KEY, BTN_SELECT, NULL);
|
|
|
|
libevdev_enable_event_code(evdev, EV_KEY, BTN_A, NULL);
|
|
libevdev_enable_event_code(evdev, EV_KEY, BTN_B, NULL);
|
|
libevdev_enable_event_code(evdev, EV_KEY, BTN_X, NULL);
|
|
libevdev_enable_event_code(evdev, EV_KEY, BTN_Y, NULL);
|
|
|
|
libevdev_enable_event_code(evdev, EV_KEY, BTN_DPAD_UP, NULL);
|
|
libevdev_enable_event_code(evdev, EV_KEY, BTN_DPAD_DOWN, NULL);
|
|
libevdev_enable_event_code(evdev, EV_KEY, BTN_DPAD_LEFT, NULL);
|
|
libevdev_enable_event_code(evdev, EV_KEY, BTN_DPAD_RIGHT, NULL);
|
|
|
|
libevdev_enable_event_code(evdev, EV_KEY, BTN_TL2, NULL);
|
|
libevdev_enable_event_code(evdev, EV_KEY, BTN_TR2, NULL);
|
|
|
|
struct input_absinfo wheel_info = {
|
|
.value = 0,
|
|
.minimum = -32768,
|
|
.maximum = 32767,
|
|
.fuzz = 0,
|
|
.flat = XBO_DEADZONE_WHEEL,
|
|
.resolution = 1 };
|
|
libevdev_enable_event_code(evdev, EV_ABS, ABS_X, &wheel_info);
|
|
|
|
struct input_absinfo pedal_info = {
|
|
.value = 0,
|
|
.minimum = 0,
|
|
.maximum = 1024,
|
|
.fuzz = 0,
|
|
.flat = XBO_DEADZONE_PEDAL,
|
|
.resolution = 1 };
|
|
libevdev_enable_event_code(evdev, EV_ABS, ABS_RX, &pedal_info);
|
|
libevdev_enable_event_code(evdev, EV_ABS, ABS_RY, &pedal_info);
|
|
|
|
err = libevdev_uinput_create_from_device(evdev, LIBEVDEV_UINPUT_OPEN_MANAGED, &ui_evdev);
|
|
if (err != 0) {
|
|
error_printf("evdev: Failed to create device\n");
|
|
return err;
|
|
}
|
|
|
|
debug_printf("Created evdev device\n");
|
|
|
|
/* Handle input */
|
|
struct xbo_input_data input[XBO_MAX_CNT];
|
|
|
|
for (;;) {
|
|
for (size_t i = 0; i < handle_cnt; ++i) {
|
|
if (handles[i] == NULL) {
|
|
continue;
|
|
}
|
|
|
|
err = update_device(handles[i], &input[i]);
|
|
if (err != RC_SUCCESS) {
|
|
return err;
|
|
}
|
|
|
|
libevdev_uinput_write_event(ui_evdev, EV_KEY, BTN_START, input[i].start);
|
|
libevdev_uinput_write_event(ui_evdev, EV_KEY, BTN_SELECT, input[i].back);
|
|
|
|
libevdev_uinput_write_event(ui_evdev, EV_KEY, BTN_A, input[i].a);
|
|
libevdev_uinput_write_event(ui_evdev, EV_KEY, BTN_B, input[i].b);
|
|
libevdev_uinput_write_event(ui_evdev, EV_KEY, BTN_X, input[i].x);
|
|
libevdev_uinput_write_event(ui_evdev, EV_KEY, BTN_Y, input[i].y);
|
|
|
|
libevdev_uinput_write_event(ui_evdev, EV_KEY, BTN_DPAD_UP, input[i].dpad_up);
|
|
libevdev_uinput_write_event(ui_evdev, EV_KEY, BTN_DPAD_DOWN, input[i].dpad_down);
|
|
libevdev_uinput_write_event(ui_evdev, EV_KEY, BTN_DPAD_LEFT, input[i].dpad_left);
|
|
libevdev_uinput_write_event(ui_evdev, EV_KEY, BTN_DPAD_RIGHT, input[i].dpad_right);
|
|
|
|
libevdev_uinput_write_event(ui_evdev, EV_KEY, BTN_TL2, input[i].bumper_left);
|
|
libevdev_uinput_write_event(ui_evdev, EV_KEY, BTN_TR2, input[i].bumper_right);
|
|
|
|
libevdev_uinput_write_event(ui_evdev, EV_ABS, ABS_RX, input[i].pedal_brake);
|
|
libevdev_uinput_write_event(ui_evdev, EV_ABS, ABS_RY, input[i].pedal_gas);
|
|
|
|
libevdev_uinput_write_event(ui_evdev, EV_ABS, ABS_X, input[i].wheel);
|
|
|
|
libevdev_uinput_write_event(ui_evdev, EV_SYN, SYN_REPORT, 0);
|
|
|
|
|
|
debug_print_input_data(input[i]);
|
|
}
|
|
|
|
printf("\x1b[7A");
|
|
}
|
|
|
|
for (size_t i = 0; i < handle_cnt; ++i) {
|
|
err = unclaim_device(handles[i]);
|
|
if (err != RC_SUCCESS) {
|
|
return err;
|
|
}
|
|
}
|
|
|
|
libevdev_uinput_destroy(ui_evdev);
|
|
libevdev_free(evdev);
|
|
|
|
debug_printf("Destroyed evdev device\n");
|
|
|
|
libusb_exit(NULL);
|
|
|
|
debug_printf("Exited libusb\n");
|
|
|
|
return RC_SUCCESS;
|
|
}
|