Skip to content

Instantly share code, notes, and snippets.

@luser
Created July 3, 2025 19:57
Show Gist options
  • Save luser/b57e35817df4b07c3c57499147b2f729 to your computer and use it in GitHub Desktop.
Save luser/b57e35817df4b07c3c57499147b2f729 to your computer and use it in GitHub Desktop.
#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