Created
July 3, 2025 19:57
-
-
Save luser/b57e35817df4b07c3c57499147b2f729 to your computer and use it in GitHub Desktop.
This file contains hidden or 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 "platform.h" | |
#include <stdint.h> | |
#include <fcntl.h> | |
#include <sys/ioctl.h> | |
#include <unistd.h> | |
#include <string.h> | |
#include <time.h> | |
#include <stdlib.h> | |
#include <stdio.h> | |
#include <libudev.h> | |
#include <linux/limits.h> | |
#include <linux/joystick.h> | |
const char* kOS = "linux"; | |
static const char kJoystickPath[] = "/dev/input/js"; | |
static const unsigned short kAxisRange = 65535; | |
bool is_gamepad(struct udev_device* dev) | |
{ | |
if (!udev_device_get_property_value(dev, "ID_INPUT_JOYSTICK")) | |
return false; | |
const char* devpath = udev_device_get_devnode(dev); | |
if (!devpath) { | |
return false; | |
} | |
if (strncmp(kJoystickPath, devpath, sizeof(kJoystickPath) - 1) != 0) { | |
return false; | |
} | |
return true; | |
} | |
bool find_gamepad(Gamepad* gamepad) | |
{ | |
struct udev* udev = udev_new(); | |
struct udev_enumerate* en = udev_enumerate_new(udev); | |
udev_enumerate_add_match_subsystem(en, "input"); | |
udev_enumerate_scan_devices(en); | |
struct udev_list_entry* dev_list_entry; | |
bool found = false; | |
// Output information | |
char devpath[PATH_MAX]; | |
memset(gamepad->vendor_id, 0, sizeof(gamepad->vendor_id)); | |
memset(gamepad->product_id, 0, sizeof(gamepad->product_id)); | |
memset(gamepad->driver, 0, sizeof(gamepad->driver)); | |
for (dev_list_entry = udev_enumerate_get_list_entry(en); | |
dev_list_entry != nullptr && !found; | |
dev_list_entry = udev_list_entry_get_next(dev_list_entry)) { | |
const char* path = udev_list_entry_get_name(dev_list_entry); | |
struct udev_device* dev = udev_device_new_from_syspath(udev, path); | |
if (is_gamepad(dev)) { | |
found = true; | |
snprintf(devpath, sizeof(devpath), "%s", udev_device_get_devnode(dev)); | |
const char* vid = udev_device_get_property_value(dev, "ID_VENDOR_ID"); | |
const char* pid = udev_device_get_property_value(dev, "ID_MODEL_ID"); | |
const char* dr = udev_device_get_driver(dev); | |
if (!dr) | |
dr = udev_device_get_property_value(dev, "ID_USB_DRIVER"); | |
if (!vid || !pid) { | |
struct udev_device* parent = udev_device_get_parent_with_subsystem_devtype(dev, "input", NULL); | |
if (parent) { | |
vid = udev_device_get_sysattr_value(parent, "id/vendor"); | |
pid = udev_device_get_sysattr_value(parent, "id/product"); | |
if (!dr) | |
dr = udev_device_get_driver(parent); | |
if (!dr) { | |
struct udev_device* bt_parent = udev_device_get_parent_with_subsystem_devtype(dev, "bluetooth", NULL); | |
if (bt_parent) { | |
dr = "bluetooth"; | |
} | |
} | |
} | |
} | |
if (vid && pid) { | |
snprintf(gamepad->vendor_id, sizeof(gamepad->vendor_id), "%s", vid); | |
snprintf(gamepad->product_id, sizeof(gamepad->product_id), "%s", pid); | |
} | |
if (dr) | |
snprintf(gamepad->driver, sizeof(gamepad->driver), "%s", dr); | |
} | |
udev_device_unref(dev); | |
} | |
udev_enumerate_unref(en); | |
if (found) { | |
int fd = open(devpath, O_RDONLY|O_NONBLOCK); | |
if (fd == -1) | |
return false; | |
int raw_version; | |
if (ioctl(fd, JSIOCGVERSION, &raw_version) < 0) { | |
close(fd); | |
return false; | |
} | |
gamepad->device = fd; | |
char num_axes, num_buttons; | |
char controller_name[80]; | |
memset(controller_name, 0, sizeof(controller_name)); | |
ioctl(fd, JSIOCGAXES, &num_axes); | |
ioctl(fd, JSIOCGBUTTONS, &num_buttons); | |
ioctl(fd, JSIOCGNAME(80), &controller_name); | |
snprintf(gamepad->name, sizeof(gamepad->name), "%s", controller_name); | |
gamepad->num_axes = num_axes; | |
gamepad->num_buttons = num_buttons; | |
} | |
return found; | |
} | |
void close_gamepad(Gamepad* gamepad) | |
{ | |
close(gamepad->device); | |
} | |
void read_initial_state(DeviceHandle fd, raw_gamepad& state) | |
{ | |
bool done = false; | |
while (!done) { | |
struct js_event e; | |
if (read(fd, &e, sizeof(struct js_event)) != sizeof(struct js_event)) | |
break; | |
if (!(e.type & JS_EVENT_INIT)) { | |
done = true; | |
} | |
switch (e.type) { | |
case JS_EVENT_BUTTON: | |
state.buttons[e.number] = e.value; | |
break; | |
case JS_EVENT_AXIS: | |
state.axes[e.number] = e.value; | |
break; | |
} | |
} | |
} | |
int64_t time_ms() | |
{ | |
struct timespec t = {}; | |
clock_gettime(CLOCK_MONOTONIC, &t); | |
return (int64_t)(t.tv_sec)*1000LL + t.tv_nsec/1000000; | |
} | |
bool get_one_report(DeviceHandle fd, int wait_ms, InputType* what, int* which, double* value) | |
{ | |
fd_set read_fds, write_fds, error_fds; | |
struct timeval timeout = {}; | |
timeout.tv_usec = wait_ms * 1000 / 2; | |
FD_ZERO(&read_fds); | |
FD_ZERO(&write_fds); | |
FD_ZERO(&error_fds); | |
FD_SET(fd, &read_fds); | |
if (select(fd+1, &read_fds, &write_fds, &error_fds, &timeout) > 0) { | |
struct js_event e; | |
if (read(fd, &e, sizeof(struct js_event)) == sizeof(struct js_event)) { | |
switch (e.type) { | |
case JS_EVENT_BUTTON: | |
*what = InputButton; | |
*which = e.number; | |
*value = e.value ? 1.0 : 0.0; | |
return true; | |
case JS_EVENT_AXIS: | |
*what = InputAxis; | |
*which = e.number; | |
*value = (double)e.value / (double)kAxisRange; | |
return true; | |
} | |
} | |
} | |
return false; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment