199 lines
5.3 KiB
C
Raw Permalink Normal View History

2025-01-22 16:18:30 +01:00
#include "gamepad.h"
#include <kinc/input/gamepad.h>
#include <fcntl.h>
#include <libudev.h>
#include <linux/joystick.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
struct HIDGamepad {
int idx;
char gamepad_dev_name[256];
char name[385];
int file_descriptor;
bool connected;
struct js_event gamepadEvent;
};
static void HIDGamepad_open(struct HIDGamepad *pad) {
pad->file_descriptor = open(pad->gamepad_dev_name, O_RDONLY | O_NONBLOCK);
if (pad->file_descriptor < 0) {
pad->connected = false;
}
else {
pad->connected = true;
char buf[128];
if (ioctl(pad->file_descriptor, JSIOCGNAME(sizeof(buf)), buf) < 0) {
strncpy(buf, "Unknown", sizeof(buf));
}
snprintf(pad->name, sizeof(pad->name), "%s(%s)", buf, pad->gamepad_dev_name);
kinc_internal_gamepad_trigger_connect(pad->idx);
}
}
static void HIDGamepad_init(struct HIDGamepad *pad, int index) {
pad->file_descriptor = -1;
pad->connected = false;
pad->gamepad_dev_name[0] = 0;
if (index >= 0 && index < 12) {
pad->idx = index;
snprintf(pad->gamepad_dev_name, sizeof(pad->gamepad_dev_name), "/dev/input/js%d", pad->idx);
HIDGamepad_open(pad);
}
}
static void HIDGamepad_close(struct HIDGamepad *pad) {
if (pad->connected) {
kinc_internal_gamepad_trigger_disconnect(pad->idx);
close(pad->file_descriptor);
pad->file_descriptor = -1;
pad->connected = false;
}
}
void HIDGamepad_processEvent(struct HIDGamepad *pad, struct js_event e) {
switch (e.type) {
case JS_EVENT_BUTTON:
kinc_internal_gamepad_trigger_button(pad->idx, e.number, e.value);
break;
case JS_EVENT_AXIS: {
float value = e.number % 2 == 0 ? e.value : -e.value;
kinc_internal_gamepad_trigger_axis(pad->idx, e.number, value / 32767.0f);
break;
}
default:
break;
}
}
void HIDGamepad_update(struct HIDGamepad *pad) {
if (pad->connected) {
while (read(pad->file_descriptor, &pad->gamepadEvent, sizeof(pad->gamepadEvent)) > 0) {
HIDGamepad_processEvent(pad, pad->gamepadEvent);
}
}
}
struct HIDGamepadUdevHelper {
struct udev *udevPtr;
struct udev_monitor *udevMonitorPtr;
int udevMonitorFD;
};
static struct HIDGamepadUdevHelper udev_helper;
static struct HIDGamepad gamepads[KINC_GAMEPAD_MAX_COUNT];
static void HIDGamepadUdevHelper_openOrCloseGamepad(struct HIDGamepadUdevHelper *helper, struct udev_device *dev) {
const char *action = udev_device_get_action(dev);
if (!action)
action = "add";
const char *joystickDevnodeName = strstr(udev_device_get_devnode(dev), "js");
if (joystickDevnodeName) {
int joystickDevnodeIndex;
sscanf(joystickDevnodeName, "js%d", &joystickDevnodeIndex);
if (!strcmp(action, "add")) {
HIDGamepad_open(&gamepads[joystickDevnodeIndex]);
}
if (!strcmp(action, "remove")) {
HIDGamepad_close(&gamepads[joystickDevnodeIndex]);
}
}
}
static void HIDGamepadUdevHelper_processDevice(struct HIDGamepadUdevHelper *helper, struct udev_device *dev) {
if (dev) {
if (udev_device_get_devnode(dev))
HIDGamepadUdevHelper_openOrCloseGamepad(helper, dev);
udev_device_unref(dev);
}
}
static void HIDGamepadUdevHelper_init(struct HIDGamepadUdevHelper *helper) {
struct udev *udevPtrNew = udev_new();
// enumerate
struct udev_enumerate *enumerate = udev_enumerate_new(udevPtrNew);
udev_enumerate_add_match_subsystem(enumerate, "input");
udev_enumerate_scan_devices(enumerate);
struct udev_list_entry *devices = udev_enumerate_get_list_entry(enumerate);
struct udev_list_entry *entry;
udev_list_entry_foreach(entry, devices) {
const char *path = udev_list_entry_get_name(entry);
struct udev_device *dev = udev_device_new_from_syspath(udevPtrNew, path);
HIDGamepadUdevHelper_processDevice(helper, dev);
}
udev_enumerate_unref(enumerate);
// setup mon
helper->udevMonitorPtr = udev_monitor_new_from_netlink(udevPtrNew, "udev");
udev_monitor_filter_add_match_subsystem_devtype(helper->udevMonitorPtr, "input", NULL);
udev_monitor_enable_receiving(helper->udevMonitorPtr);
helper->udevMonitorFD = udev_monitor_get_fd(helper->udevMonitorPtr);
helper->udevPtr = udevPtrNew;
}
static void HIDGamepadUdevHelper_update(struct HIDGamepadUdevHelper *helper) {
fd_set fds;
FD_ZERO(&fds);
FD_SET(helper->udevMonitorFD, &fds);
if (FD_ISSET(helper->udevMonitorFD, &fds)) {
struct udev_device *dev = udev_monitor_receive_device(helper->udevMonitorPtr);
HIDGamepadUdevHelper_processDevice(helper, dev);
}
}
static void HIDGamepadUdevHelper_close(struct HIDGamepadUdevHelper *helper) {
udev_unref(helper->udevPtr);
}
void kinc_linux_initHIDGamepads() {
for (int i = 0; i < KINC_GAMEPAD_MAX_COUNT; ++i) {
HIDGamepad_init(&gamepads[i], i);
}
HIDGamepadUdevHelper_init(&udev_helper);
}
void kinc_linux_updateHIDGamepads() {
HIDGamepadUdevHelper_update(&udev_helper);
for (int i = 0; i < KINC_GAMEPAD_MAX_COUNT; ++i) {
HIDGamepad_update(&gamepads[i]);
}
}
void kinc_linux_closeHIDGamepads() {
HIDGamepadUdevHelper_close(&udev_helper);
}
const char *kinc_gamepad_vendor(int gamepad) {
return "Linux gamepad";
}
const char *kinc_gamepad_product_name(int gamepad) {
return gamepad >= 0 && gamepad < KINC_GAMEPAD_MAX_COUNT ? gamepads[gamepad].name : "";
}
bool kinc_gamepad_connected(int gamepad) {
return gamepad >= 0 && gamepad < KINC_GAMEPAD_MAX_COUNT && gamepads[gamepad].connected;
}
void kinc_gamepad_rumble(int gamepad, float left, float right) {}