jsfw/hid.c

397 lines
12 KiB
C
Raw Normal View History

2022-08-30 17:54:56 -05:00
#include "hid.h"
#include "const.h"
#include "util.h"
2022-08-30 17:54:56 -05:00
#include "vec.h"
2022-08-30 08:37:34 -05:00
#include <dirent.h>
#include <fcntl.h>
2022-08-31 11:59:06 -05:00
#include <linux/input-event-codes.h>
2022-08-30 08:37:34 -05:00
#include <linux/input.h>
2022-08-30 12:08:36 -05:00
#include <pthread.h>
2022-08-30 08:37:34 -05:00
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <time.h>
2022-08-30 12:08:36 -05:00
#include <unistd.h>
2022-08-29 17:27:03 -05:00
// List of uniq of the currently known devices
static Vec devices;
// List of the new devices of a poll, static to keep the allocation alive
static Vec new_devices;
// Queue of devices to be taken by connections
static Vec devices_queue;
// Mutex for the device queue
static pthread_mutex_t devices_queue_mutex = PTHREAD_MUTEX_INITIALIZER;
// Condvar notified on device queue update
static pthread_cond_t devices_queue_cond = PTHREAD_COND_INITIALIZER;
// Mutex for devices
static pthread_mutex_t devices_mutex = PTHREAD_MUTEX_INITIALIZER;
// uniqs are just hexadecimal numbers with colons in between each byte
uniq_t parse_uniq(char uniq[17]) {
uniq_t res = 0;
2022-08-30 08:37:34 -05:00
for (int i = 0; i < 17; i++) {
2022-08-29 17:27:03 -05:00
char c = uniq[i];
2022-08-30 08:37:34 -05:00
int digit;
if (c >= '0' && c <= '9')
digit = c - '0';
else if (c >= 'a' && c <= 'f')
digit = c - 'a' + 10;
else if (c >= 'A' && c <= 'F')
digit = c - 'A' + 10;
else
continue;
2022-08-29 17:27:03 -05:00
res <<= 4;
res += digit;
}
return res;
}
// Finish setup of a partially initialized device (set device_info and mapping)
2022-08-30 08:37:34 -05:00
void setup_device(PhysicalDevice *dev) {
dev->device_info.code = DeviceInfo;
dev->device_info.abs_count = 0;
dev->device_info.rel_count = 0;
dev->device_info.key_count = 0;
2022-08-30 12:08:36 -05:00
for (int i = 0; i < ABS_CNT; i++)
2022-08-30 11:28:29 -05:00
dev->mapping.abs_indices[i] = -1;
2022-08-30 12:08:36 -05:00
for (int i = 0; i < REL_CNT; i++)
2022-08-30 17:54:56 -05:00
dev->mapping.rel_indices[i] = -1;
2022-08-30 12:08:36 -05:00
for (int i = 0; i < KEY_CNT; i++)
2022-08-30 11:28:29 -05:00
dev->mapping.key_indices[i] = -1;
uint8_t type_bits[EV_MAX] = {};
uint8_t feat_bits[(KEY_MAX + 7) / 8] = {};
ioctl(dev->event, EVIOCGBIT(0, EV_MAX), type_bits);
// Loop over all event types
for (int type = 0; type < EV_MAX; type++) {
2022-09-11 04:23:31 -05:00
// Ignore if the the device doesn't have any of this event type
if (!bit_set(type_bits, type)) {
continue;
}
2022-09-11 04:23:31 -05:00
// Clear feat_bits to only have the features of the current type
memset(feat_bits, 0, sizeof(feat_bits));
ioctl(dev->event, EVIOCGBIT(type, KEY_MAX), feat_bits);
// Loop over "instances" of type (i.e Each axis of a controller for EV_ABS)
for (int i = 0; i < KEY_MAX; i++) {
2022-09-11 04:23:31 -05:00
// "instances" don't have to be consecutive (this is why we do all this instead of just worrying
// about the count)
if (!bit_set(feat_bits, i)) {
continue;
}
if (type == EV_ABS) {
struct input_absinfo abs;
ioctl(dev->event, EVIOCGABS(i), &abs);
uint16_t index = dev->device_info.abs_count++;
dev->device_info.abs_min[index] = abs.minimum;
dev->device_info.abs_max[index] = abs.maximum;
dev->device_info.abs_fuzz[index] = abs.fuzz;
dev->device_info.abs_flat[index] = abs.flat;
dev->device_info.abs_res[index] = abs.resolution;
2022-09-11 04:23:31 -05:00
// Bidirectional mapping id <-> index
// We need this to avoid wasting space in packets because ids are sparse
dev->device_info.abs_id[index] = i;
dev->mapping.abs_indices[i] = index;
} else if (type == EV_REL) {
uint16_t index = dev->device_info.rel_count++;
dev->device_info.rel_id[index] = i;
dev->mapping.rel_indices[i] = index;
} else if (type == EV_KEY) {
uint16_t index = dev->device_info.key_count++;
dev->device_info.key_id[index] = i;
dev->mapping.key_indices[i] = index;
2022-08-30 08:37:34 -05:00
}
}
}
}
// Function used to filter out devices that we don't want.
// This is pretty arbritrary
2022-08-30 08:37:34 -05:00
bool filter_event(int fd, char *event) {
// Check for existance of a js* directory in /sys/class/input/eventXX/device
// This is used to filter out the touchpad of PS4 controller (which have the same product and vendor id as
// the controller)
{
char device_path[64];
snprintf(device_path, 64, "/sys/class/input/%s/device", event);
2022-08-29 17:27:03 -05:00
DIR *device_dir = opendir(device_path);
struct dirent *device_dirent;
2022-08-29 17:27:03 -05:00
bool found = false;
while ((device_dirent = readdir(device_dir)) != NULL) {
if (device_dirent->d_type == DT_DIR && strncmp(device_dirent->d_name, "js", 2) == 0) {
found = true;
break;
}
2022-08-29 17:27:03 -05:00
}
closedir(device_dir);
2022-08-29 18:06:25 -05:00
if (!found) {
return false;
}
2022-08-29 17:27:03 -05:00
}
2022-08-30 08:37:34 -05:00
// Check product and vendor id 054c:05c4 => Dualshock 4 (09cc is for the second generation)
2022-08-29 17:27:03 -05:00
uint16_t info[4];
ioctl(fd, EVIOCGID, info);
return info[1] == 0x054c && (info[2] == 0x05c4 || info[2] == 0x09cc);
2022-08-29 17:27:03 -05:00
}
// Initialize vectors for polling
2022-08-29 17:27:03 -05:00
void poll_devices_init() {
2022-08-30 08:37:34 -05:00
devices = vec_of(uniq_t);
new_devices = vec_of(PhysicalDevice);
2022-08-29 17:27:03 -05:00
devices_queue = vec_of(PhysicalDevice);
}
// Block to get a device, this is thread safe
2022-08-29 17:27:03 -05:00
PhysicalDevice get_device() {
// Check if we can get one right away
2022-08-29 17:27:03 -05:00
pthread_mutex_lock(&devices_queue_mutex);
2022-08-30 08:37:34 -05:00
if (devices_queue.len > 0) {
2022-08-29 17:27:03 -05:00
PhysicalDevice r;
vec_pop(&devices_queue, &r);
pthread_mutex_unlock(&devices_queue_mutex);
return r;
}
// Wait on condvar until there's a device and we can unlock the mutex
2022-08-30 08:37:34 -05:00
while (devices_queue.len == 0) {
2022-08-29 17:27:03 -05:00
pthread_cond_wait(&devices_queue_cond, &devices_queue_mutex);
}
2022-08-30 08:37:34 -05:00
// Take a device from the queue
2022-08-29 17:27:03 -05:00
PhysicalDevice res;
vec_pop(&devices_queue, &res);
// Signal another thread if there are still device(s) left in the queue
2022-08-30 08:37:34 -05:00
if (devices_queue.len > 0) {
2022-08-29 17:27:03 -05:00
pthread_cond_signal(&devices_queue_cond);
}
2022-08-29 17:27:03 -05:00
pthread_mutex_unlock(&devices_queue_mutex);
return res;
}
// Forget about a device. This is used on two cases:
// - If the connection to a client is lost, the device is forgotten, picked up by the next device poll, and
// put back in the queue
// - If the device dies (i.e unplugged), the connection to the client is closed and the device forgotten.
//
// This is thread safe
2022-08-30 08:37:34 -05:00
void return_device(PhysicalDevice *dev) {
if (dev->name != NULL && dev->name != DEVICE_DEFAULT_NAME) {
// Free the name if it was allocated
2022-08-30 17:54:56 -05:00
printf("HID: Returning device '%s' (%012lx)\n", dev->name, dev->uniq);
2022-08-29 18:06:25 -05:00
free(dev->name);
} else {
2022-08-30 17:54:56 -05:00
printf("HID: Returning device %012lx\n", dev->uniq);
2022-08-29 18:06:25 -05:00
}
// try to close the file descriptor, they may be already closed if the device was unpugged.
2022-08-29 17:27:03 -05:00
close(dev->event);
close(dev->hidraw);
// Safely remove device from the known device list
2022-08-29 17:27:03 -05:00
pthread_mutex_lock(&devices_mutex);
2022-08-30 08:37:34 -05:00
for (int i = 0; i < devices.len; i++) {
uniq_t *uniq = vec_get(&devices, i);
if (*uniq == dev->uniq) {
2022-08-29 17:27:03 -05:00
vec_remove(&devices, i, NULL);
break;
}
}
pthread_mutex_unlock(&devices_mutex);
}
// Find all available devices and pick up on new ones
2022-08-29 17:27:03 -05:00
void poll_devices() {
vec_clear(&new_devices);
// loop over all entries of /sys/class/input
2022-08-30 08:37:34 -05:00
DIR *input_dir = opendir("/sys/class/input");
struct dirent *input;
while ((input = readdir(input_dir)) != NULL) {
// Ignore if the entry isn't a link or doesn't start with event
2022-08-30 08:37:34 -05:00
if (input->d_type != DT_LNK || strncmp(input->d_name, "event", 5) != 0) {
2022-08-29 17:27:03 -05:00
continue;
}
2022-08-30 08:37:34 -05:00
PhysicalDevice dev;
2022-08-29 17:27:03 -05:00
// Open /dev/input/eventXX
2022-08-29 17:27:03 -05:00
char event_path[64];
snprintf(event_path, 64, "/dev/input/%s", input->d_name);
dev.event = open(event_path, O_RDONLY);
if (dev.event < 0) { // Ignore device if we couldn't open
2022-08-29 17:27:03 -05:00
continue;
}
// Try to get the name, default to DEFAULT_NAME if impossible
char name_buf[256] = {};
const char *name;
2022-09-11 04:23:31 -05:00
if (ioctl(dev.event, EVIOCGNAME(256), name_buf) >= 0) {
2022-08-29 18:06:25 -05:00
name = name_buf;
2022-09-11 04:23:31 -05:00
} else {
name = DEVICE_DEFAULT_NAME;
2022-09-11 04:23:31 -05:00
}
2022-08-29 17:27:03 -05:00
// Filter events we don't care about
2022-09-11 04:23:31 -05:00
if (!filter_event(dev.event, input->d_name)) {
2022-08-30 08:37:34 -05:00
goto skip;
2022-09-11 04:23:31 -05:00
}
2022-08-29 17:27:03 -05:00
// Try to get uniq, drop device if we can't
2022-08-29 17:27:03 -05:00
uniq_t uniq;
{
char uniq_str[17] = {};
2022-08-30 08:37:34 -05:00
ioctl(dev.event, EVIOCGUNIQ(17), uniq_str);
2022-08-29 17:27:03 -05:00
uniq = parse_uniq(uniq_str);
// If we couldn't parse the uniq (this assumes uniq can't be zero, which is probably alright)
2022-09-11 04:23:31 -05:00
if (uniq == 0) {
2022-08-30 08:37:34 -05:00
goto skip;
2022-09-11 04:23:31 -05:00
}
2022-08-29 17:27:03 -05:00
}
// Check if we already know of this device
2022-08-29 17:27:03 -05:00
bool found = false;
pthread_mutex_lock(&devices_mutex);
2022-08-30 08:37:34 -05:00
for (int i = 0; i < devices.len; i++) {
uniq_t *dev_uniq = vec_get(&devices, i);
if (*dev_uniq == uniq) {
2022-08-29 17:27:03 -05:00
found = true;
break;
}
}
pthread_mutex_unlock(&devices_mutex);
if (found) { // Device isn't new
2022-08-30 08:37:34 -05:00
goto skip;
}
2022-08-29 17:27:03 -05:00
dev.uniq = uniq;
// Try to find hidraw path for the device, drop the device if we can't
2022-08-29 17:27:03 -05:00
char hidraw_path[64];
{
char hidraw_dir_path[256];
snprintf(hidraw_dir_path, 256, "/sys/class/input/%s/device/device/hidraw", input->d_name);
2022-08-29 17:27:03 -05:00
DIR *hidraw_dir = opendir(hidraw_dir_path);
2022-08-30 08:37:34 -05:00
struct dirent *hidraw = NULL;
while ((hidraw = readdir(hidraw_dir)) != NULL) {
if (strncmp(hidraw->d_name, "hidraw", 6) == 0) {
2022-08-30 08:37:34 -05:00
break;
}
2022-08-29 17:27:03 -05:00
}
2022-08-30 08:37:34 -05:00
if (hidraw == NULL) {
2022-08-29 17:27:03 -05:00
printf("Couldn't get hidraw of %s", input->d_name);
continue;
}
snprintf(hidraw_path, 64, "/dev/%s", hidraw->d_name);
closedir(hidraw_dir);
}
dev.hidraw = open(hidraw_path, O_WRONLY);
if (dev.hidraw < 0) {
2022-08-30 08:37:34 -05:00
goto skip;
}
2022-08-29 17:27:03 -05:00
if (name != DEVICE_DEFAULT_NAME) {
dev.name = malloc(256);
if (dev.name == NULL) {
dev.name = (char *)DEVICE_DEFAULT_NAME;
} else {
strcpy(dev.name, name);
}
}
2022-08-29 18:06:25 -05:00
2022-08-30 08:37:34 -05:00
setup_device(&dev);
2022-08-29 17:27:03 -05:00
pthread_mutex_lock(&devices_mutex);
vec_push(&devices, &uniq);
pthread_mutex_unlock(&devices_mutex);
2022-08-29 17:27:03 -05:00
vec_push(&new_devices, &dev);
2022-08-30 17:54:56 -05:00
printf("HID: New device, %s (%s: %012lx)\n", name, input->d_name, dev.uniq);
// Continue here to avoid running cleanup code of skip
2022-08-29 17:27:03 -05:00
continue;
// close open file descriptor and continue
2022-08-30 08:37:34 -05:00
skip:
2022-08-29 17:27:03 -05:00
close(dev.event);
continue;
};
closedir(input_dir);
// Safely add new devices to the queue
2022-08-30 08:37:34 -05:00
if (new_devices.len > 0) {
2022-08-29 17:27:03 -05:00
pthread_mutex_lock(&devices_queue_mutex);
vec_extend(&devices_queue, new_devices.data, new_devices.len);
// Signal that there are new devices
pthread_cond_signal(&devices_queue_cond);
pthread_mutex_unlock(&devices_queue_mutex);
}
}
2022-09-11 04:23:31 -05:00
// "Execute" a MessageControllerState: set the led color, rumble and flash using the hidraw interface
2022-08-30 08:37:34 -05:00
void apply_controller_state(PhysicalDevice *dev, MessageControllerState *state) {
2022-09-11 04:23:31 -05:00
printf("HID: (%012lx) Controller state: #%02x%02x%02x flash: (%d, %d) rumble: (%d, %d)\n", dev->uniq,
state->led[0], state->led[1], state->led[2], state->flash_on, state->flash_off,
state->small_rumble, state->big_rumble);
2022-08-30 08:37:34 -05:00
uint8_t buf[32] = {0x05, 0xff, 0x00, 0x00};
buf[4] = state->small_rumble;
buf[5] = state->big_rumble;
buf[6] = state->led[0];
buf[7] = state->led[1];
buf[8] = state->led[2];
buf[9] = state->flash_on;
buf[10] = state->flash_off;
write(dev->hidraw, buf, 32);
2022-08-30 12:08:36 -05:00
if (state->flash_on == 0 && state->flash_off == 0) {
// May not be necessary
2022-08-30 08:37:34 -05:00
fsync(dev->hidraw);
2022-09-11 04:23:31 -05:00
// Send a second time, to reenable the led
2022-08-30 08:37:34 -05:00
write(dev->hidraw, buf, 32);
};
}
// Body of the hid thread
2022-08-30 08:37:34 -05:00
void *hid_thread() {
2022-08-30 17:54:56 -05:00
printf("HID: start\n");
2022-08-29 17:27:03 -05:00
poll_devices_init();
2022-08-30 08:37:34 -05:00
while (1) {
2022-08-29 17:27:03 -05:00
poll_devices();
nanosleep(&POLL_DEVICE_INTERVAL, NULL);
2022-08-29 17:27:03 -05:00
}
2022-08-29 17:27:03 -05:00
return NULL;
2022-08-27 19:29:43 -05:00
}