199 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			199 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| #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) {}
 |